├── .gitattributes
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── AuthorizeSetup
├── AddClaimsToCookie.cs
├── AddPermissionsDataKeyToUserClaims.cs
├── AddPermissionsToUserClaims.cs
├── AuthCookieValidateEverything.cs
├── AuthCookieValidateImpersonation.cs
├── AuthCookieValidatePermissionsDataKey.cs
├── AuthCookieValidatePermissionsOnly.cs
├── AuthCookieValidateRefreshClaims.cs
├── AuthorizeSetup.csproj
└── IAuthCookieValidate.cs
├── CommonCache
└── CommonCache.csproj
├── DataAuthorize
├── CalcDataKey.cs
└── DataAuthorize.csproj
├── DataKeyParts
├── DataAuthConstants.cs
├── DataKeyParts.csproj
├── DbContextExtensions.cs
├── DemoSetupOptions.cs
├── GetClaimsFromUser.cs
├── IDataKey.cs
├── IGetClaimsProvider.cs
├── IShopLevelDataKey.cs
├── NoQueryFilterNeeded.cs
└── ShopLevelDataKeyBase.cs
├── DataLayer
├── DataLayer.csproj
├── EfCode
│ ├── CombinedDbContext.cs
│ ├── CompanyDbContext.cs
│ ├── Configurations
│ │ └── ConfigExtensions.cs
│ └── ExtraAuthorizeDbContext.cs
├── ExtraAuthClasses
│ ├── ExtraAuthConstants.cs
│ ├── ModulesForUser.cs
│ ├── RoleToPermissions.cs
│ ├── Support
│ │ ├── ChangeExtensions.cs
│ │ ├── IAddRemoveEffectsUser.cs
│ │ └── IChangeEffectsUser.cs
│ ├── TimeStore.cs
│ ├── UserDataAccess.cs
│ ├── UserDataAccessBase.cs
│ ├── UserDataHierarchical.cs
│ └── UserToRole.cs
└── MultiTenantClasses
│ ├── Company.cs
│ ├── RetailOutlet.cs
│ ├── ShopSale.cs
│ ├── ShopStock.cs
│ ├── SubGroup.cs
│ └── TenantBase.cs
├── FeatureAuthorize
├── CalcFeaturePermissions.cs
├── FeatureAuthorize.csproj
├── PermissionConstants.cs
├── PermissionExtensions.cs
└── PolicyCode
│ ├── AuthorizationPolicyProvider.cs
│ ├── HasPermissionAttribute.cs
│ ├── PermissionHandler.cs
│ └── PermissionRequirement.cs
├── LICENSE
├── PermissionAccessControl2.sln
├── PermissionAccessControl2.sln.DotSettings
├── PermissionAccessControl2
├── AddDatabases.cs
├── Areas
│ └── Identity
│ │ └── Pages
│ │ └── _ViewStart.cshtml
├── Controllers
│ ├── CacheController.cs
│ ├── CompanyController.cs
│ ├── FrontEndController.cs
│ ├── HomeController.cs
│ ├── ImpersonateController.cs
│ ├── ShopController.cs
│ └── UsersController.cs
├── Data
│ ├── ApplicationDbContext.cs
│ └── Migrations
│ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs
│ │ ├── 00000000000000_CreateIdentitySchema.cs
│ │ └── ApplicationDbContextModelSnapshot.cs
├── Models
│ └── ErrorViewModel.cs
├── PermissionAccessControl2.csproj
├── Program.cs
├── Properties
│ └── launchSettings.json
├── SeedDemo
│ ├── Internal
│ │ ├── AspNetUserExtension.cs
│ │ ├── DemoUsersSetup.cs
│ │ ├── HierarchicalSeeder.cs
│ │ └── SetupShopStock.cs
│ ├── SeedExtensions.cs
│ └── SuperAdminSetup.cs
├── Startup.cs
├── Views
│ ├── Cache
│ │ ├── Index.cshtml
│ │ └── ShowUpdateTime.cshtml
│ ├── Company
│ │ └── Index.cshtml
│ ├── Home
│ │ ├── Index.cshtml
│ │ └── Privacy.cshtml
│ ├── Impersonate
│ │ ├── Index.cshtml
│ │ └── Message.cshtml
│ ├── Shared
│ │ ├── Error.cshtml
│ │ ├── _CookieConsentPartial.cshtml
│ │ ├── _Layout.cshtml
│ │ ├── _LoginPartial.cshtml
│ │ └── _ValidationScriptsPartial.cshtml
│ ├── Shop
│ │ ├── BuySuccess.cshtml
│ │ ├── Sales.cshtml
│ │ ├── Stock.cshtml
│ │ └── Till.cshtml
│ ├── Users
│ │ ├── AllRoles.cshtml
│ │ ├── Index.cshtml
│ │ ├── UserPermissions.cshtml
│ │ └── Users.cshtml
│ ├── _ViewImports.cshtml
│ └── _ViewStart.cshtml
├── appsettings.Development.json
├── appsettings.json
└── wwwroot
│ ├── SeedData
│ ├── Companies.txt
│ ├── Roles.txt
│ ├── ShopStock.txt
│ └── Users.json
│ ├── css
│ └── site.css
│ ├── favicon.ico
│ ├── js
│ └── site.js
│ └── lib
│ ├── bootstrap
│ ├── LICENSE
│ └── dist
│ │ ├── css
│ │ ├── bootstrap-grid.css
│ │ ├── bootstrap-grid.css.map
│ │ ├── bootstrap-grid.min.css
│ │ ├── bootstrap-grid.min.css.map
│ │ ├── bootstrap-reboot.css
│ │ ├── bootstrap-reboot.css.map
│ │ ├── bootstrap-reboot.min.css
│ │ ├── bootstrap-reboot.min.css.map
│ │ ├── bootstrap.css
│ │ ├── bootstrap.css.map
│ │ ├── bootstrap.min.css
│ │ └── bootstrap.min.css.map
│ │ └── js
│ │ ├── bootstrap.bundle.js
│ │ ├── bootstrap.bundle.js.map
│ │ ├── bootstrap.bundle.min.js
│ │ ├── bootstrap.bundle.min.js.map
│ │ ├── bootstrap.js
│ │ ├── bootstrap.js.map
│ │ ├── bootstrap.min.js
│ │ └── bootstrap.min.js.map
│ ├── jquery-validation-unobtrusive
│ ├── LICENSE.txt
│ ├── jquery.validate.unobtrusive.js
│ └── jquery.validate.unobtrusive.min.js
│ ├── jquery-validation
│ ├── LICENSE.md
│ └── dist
│ │ ├── additional-methods.js
│ │ ├── additional-methods.min.js
│ │ ├── jquery.validate.js
│ │ └── jquery.validate.min.js
│ └── jquery
│ ├── LICENSE.txt
│ └── dist
│ ├── jquery.js
│ ├── jquery.min.js
│ └── jquery.min.map
├── PermissionParts
├── LinkedToModuleAttribute.cs
├── PaidForModules.cs
├── PermissionChecker.cs
├── PermissionDisplay.cs
├── PermissionPackers.cs
├── PermissionParts.csproj
└── Permissions.cs
├── README.md
├── RefreshClaimsParts
├── AuthChanges.cs
├── AuthChangesConsts.cs
├── IAuthChanges.cs
├── ITimeStore.cs
└── RefreshClaimsParts.csproj
├── ServiceLayer
├── AppStart
│ └── StartupExtensions.cs
├── CompanyServices
│ ├── Concrete
│ │ └── ListCompaniesService.cs
│ └── IListCompaniesService.cs
├── ServiceLayer.csproj
├── Shop
│ ├── ListSalesDto.cs
│ ├── ListStockDto.cs
│ ├── SellItemDto.cs
│ └── StockSelectDto.cs
└── UserServices
│ ├── Concrete
│ ├── CacheRoleService.cs
│ └── ListUsersService.cs
│ ├── ExtraAuthUsersSetup.cs
│ ├── ICacheRoleService.cs
│ ├── IListUsersService.cs
│ ├── ListUsersDto.cs
│ └── UserExtensions.cs
├── Test
├── DiConfigHelpers
│ ├── ConfigureServices.cs
│ └── MockHostingEnvironment.cs
├── EfHelpers
│ ├── AuthRoleServiceHelpers.cs
│ └── CheckEntitiesHelpers.cs
├── FakesAndMocks
│ ├── CookieRequestHelpers.cs
│ ├── FakeAuthChanges.cs
│ ├── FakeGetClaimsProvider.cs
│ └── FakeTimeStore.cs
├── Test.csproj
├── TestData
│ └── SeedData
│ │ ├── Companies.txt
│ │ ├── Roles.txt
│ │ ├── ShopStock.txt
│ │ └── Users.json
├── UnitCommands
│ └── DeleteAllUnitTestDatabases.cs
├── UnitTests
│ ├── DataAuthorizeTests
│ │ ├── TestHierarchicalBuildingChanging.cs
│ │ ├── TestHierarchicalFiltering.cs
│ │ └── TestShopData.cs
│ ├── DataLayerTests
│ │ └── TestCombinedDbContext.cs
│ ├── FeatureAuthorizeTests
│ │ ├── TestAuthChanges.cs
│ │ ├── TestCacheRoleService.cs
│ │ ├── TestCalcAllowedPermissions.cs
│ │ ├── TestExtraAuthorizeDbContext.cs
│ │ ├── TestPermissionsChangedDatabase.cs
│ │ ├── TestPermissionsChangedFakeDatabase.cs
│ │ └── TestTimeStore.cs
│ ├── ImpersonateTests
│ │ ├── TestImpersonationCookie.cs
│ │ └── TestImpersonationHandler.cs
│ ├── SecurityChecks
│ │ ├── CheckEntitiesAreSecure.cs
│ │ └── CheckPermissions.cs
│ ├── ShopServices
│ │ └── TestShopServices.cs
│ └── TestSeedDemo
│ │ └── TestSeedExtensions.cs
├── appsettings.json
└── demosettings.json
└── UserImpersonation
├── AppStart
└── StartupExtensions.cs
├── Concrete
├── AuthCookieSignout.cs
├── ImpersonationCookie.cs
├── ImpersonationData.cs
├── ImpersonationHandler.cs
└── ImpersonationService.cs
├── IImpersonationService.cs
├── ImpersonateExtensions.cs
└── UserImpersonation.csproj
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (web)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceFolder}/PermissionAccessControl2/bin/Debug/netcoreapp2.2/PermissionAccessControl2.dll",
14 | "args": [],
15 | "cwd": "${workspaceFolder}/PermissionAccessControl2",
16 | "stopAtEntry": false,
17 | "launchBrowser": {
18 | "enabled": true
19 | },
20 | "env": {
21 | "ASPNETCORE_ENVIRONMENT": "Development"
22 | },
23 | "sourceFileMap": {
24 | "/Views": "${workspaceFolder}/Views"
25 | }
26 | },
27 | {
28 | "name": ".NET Core Attach",
29 | "type": "coreclr",
30 | "request": "attach",
31 | "processId": "${command:pickProcess}"
32 | }
33 | ]
34 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/PermissionAccessControl2/PermissionAccessControl2.csproj"
11 | ],
12 | "problemMatcher": "$tsc"
13 | },
14 | {
15 | "label": "publish",
16 | "command": "dotnet",
17 | "type": "process",
18 | "args": [
19 | "publish",
20 | "${workspaceFolder}/PermissionAccessControl2/PermissionAccessControl2.csproj"
21 | ],
22 | "problemMatcher": "$tsc"
23 | },
24 | {
25 | "label": "watch",
26 | "command": "dotnet",
27 | "type": "process",
28 | "args": [
29 | "watch",
30 | "run",
31 | "${workspaceFolder}/PermissionAccessControl2/PermissionAccessControl2.csproj"
32 | ],
33 | "problemMatcher": "$tsc"
34 | }
35 | ]
36 | }
--------------------------------------------------------------------------------
/AuthorizeSetup/AddPermissionsDataKeyToUserClaims.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Security.Claims;
5 | using System.Threading.Tasks;
6 | using DataAuthorize;
7 | using DataKeyParts;
8 | using DataLayer.EfCode;
9 | using FeatureAuthorize;
10 | using Microsoft.AspNetCore.Identity;
11 | using Microsoft.Extensions.Options;
12 |
13 | namespace AuthorizeSetup
14 | {
15 | ///
16 | /// This version provides:
17 | /// - Adds Permissions to the user's claims.
18 | /// - Adds DataKey to the user's claims
19 | ///
20 | // Thanks to https://korzh.com/blogs/net-tricks/aspnet-identity-store-user-data-in-claims
21 | public class AddPermissionsDataKeyToUserClaims : UserClaimsPrincipalFactory
22 | {
23 | private readonly ExtraAuthorizeDbContext _extraAuthDbContext;
24 |
25 | public AddPermissionsDataKeyToUserClaims(UserManager userManager, IOptions optionsAccessor,
26 | ExtraAuthorizeDbContext extraAuthDbContext)
27 | : base(userManager, optionsAccessor)
28 | {
29 | _extraAuthDbContext = extraAuthDbContext;
30 | }
31 |
32 | protected override async Task GenerateClaimsAsync(IdentityUser user)
33 | {
34 | var identity = await base.GenerateClaimsAsync(user);
35 | var userId = identity.Claims.GetUserIdFromClaims();
36 | var rtoPCalcer = new CalcAllowedPermissions(_extraAuthDbContext);
37 | identity.AddClaim(new Claim(PermissionConstants.PackedPermissionClaimType, await rtoPCalcer.CalcPermissionsForUserAsync(userId)));
38 | var dataKeyCalcer = new CalcDataKey(_extraAuthDbContext);
39 | identity.AddClaim(new Claim(DataAuthConstants.HierarchicalKeyClaimName, dataKeyCalcer.CalcDataKeyForUser(userId)));
40 | return identity;
41 | }
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/AuthorizeSetup/AddPermissionsToUserClaims.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Security.Claims;
5 | using System.Threading.Tasks;
6 | using DataAuthorize;
7 | using DataKeyParts;
8 | using DataLayer.EfCode;
9 | using FeatureAuthorize;
10 | using Microsoft.AspNetCore.Identity;
11 | using Microsoft.Extensions.Options;
12 |
13 | namespace AuthorizeSetup
14 | {
15 | ///
16 | /// This version provides:
17 | /// - Adds Permissions to the user's claims.
18 | ///
19 | // Thanks to https://korzh.com/blogs/net-tricks/aspnet-identity-store-user-data-in-claims
20 | public class AddPermissionsToUserClaims : UserClaimsPrincipalFactory
21 | {
22 | private readonly ExtraAuthorizeDbContext _extraAuthDbContext;
23 |
24 | public AddPermissionsToUserClaims(UserManager userManager, IOptions optionsAccessor,
25 | ExtraAuthorizeDbContext extraAuthDbContext)
26 | : base(userManager, optionsAccessor)
27 | {
28 | _extraAuthDbContext = extraAuthDbContext;
29 | }
30 |
31 | protected override async Task GenerateClaimsAsync(IdentityUser user)
32 | {
33 | var identity = await base.GenerateClaimsAsync(user);
34 | var userId = identity.Claims.GetUserIdFromClaims();
35 | var rtoPCalcer = new CalcAllowedPermissions(_extraAuthDbContext);
36 | identity.AddClaim(new Claim(PermissionConstants.PackedPermissionClaimType, await rtoPCalcer.CalcPermissionsForUserAsync(userId)));
37 | return identity;
38 | }
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/AuthorizeSetup/AuthCookieValidatePermissionsDataKey.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Security.Claims;
7 | using System.Threading.Tasks;
8 | using DataAuthorize;
9 | using DataKeyParts;
10 | using DataLayer.EfCode;
11 | using FeatureAuthorize;
12 | using Microsoft.AspNetCore.Authentication.Cookies;
13 | using Microsoft.Extensions.DependencyInjection;
14 |
15 | namespace AuthorizeSetup
16 | {
17 | ///
18 | /// This version provides:
19 | /// - Adds Permissions to the user's claims.
20 | /// - Adds DataKey to the user's claims
21 | ///
22 | public class AuthCookieValidatePermissionsDataKey : IAuthCookieValidate
23 | {
24 | public async Task ValidateAsync(CookieValidatePrincipalContext context)
25 | {
26 | if (context.Principal.Claims.Any(x => x.Type == PermissionConstants.PackedPermissionClaimType))
27 | return;
28 |
29 | //No permissions in the claims, so we need to add it. This is only happen once after the user has logged in
30 | var extraContext = context.HttpContext.RequestServices.GetRequiredService();
31 | var rtoPCalcer = new CalcAllowedPermissions(extraContext);
32 | var dataKeyCalc = new CalcDataKey(extraContext);
33 |
34 | var claims = new List();
35 | claims.AddRange(context.Principal.Claims); //Copy over existing claims
36 | var userId = context.Principal.Claims.GetUserIdFromClaims();
37 | //Now calculate the Permissions Claim value and add it
38 | claims.Add(new Claim(PermissionConstants.PackedPermissionClaimType,
39 | await rtoPCalcer.CalcPermissionsForUserAsync(userId)));
40 | //and the same for the DataKey
41 | claims.Add(new Claim(DataAuthConstants.HierarchicalKeyClaimName,
42 | dataKeyCalc.CalcDataKeyForUser(userId)));
43 |
44 | //Build a new ClaimsPrincipal and use it to replace the current ClaimsPrincipal
45 | var identity = new ClaimsIdentity(claims, "Cookie");
46 | var newPrincipal = new ClaimsPrincipal(identity);
47 | context.ReplacePrincipal(newPrincipal);
48 | //THIS IS IMPORTANT: This updates the cookie, otherwise this calc will be done every HTTP request
49 | context.ShouldRenew = true;
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/AuthorizeSetup/AuthCookieValidatePermissionsOnly.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Security.Claims;
7 | using System.Threading.Tasks;
8 | using DataLayer.EfCode;
9 | using FeatureAuthorize;
10 | using Microsoft.AspNetCore.Authentication.Cookies;
11 | using Microsoft.Extensions.DependencyInjection;
12 |
13 | namespace AuthorizeSetup
14 | {
15 | ///
16 | /// This is the very basic version which only provides:
17 | /// - Adds Permissions to the user's claims.
18 | /// - NO DataKey
19 | ///
20 | public class AuthCookieValidatePermissionsOnly : IAuthCookieValidate
21 | {
22 | public async Task ValidateAsync(CookieValidatePrincipalContext context)
23 | {
24 | if (context.Principal.Claims.Any(x => x.Type == PermissionConstants.PackedPermissionClaimType))
25 | return;
26 |
27 | //No permissions in the claims, so we need to add it. This is only happen once after the user has logged in
28 | var dbContext = context.HttpContext.RequestServices.GetRequiredService();
29 | var rtoPCalcer = new CalcAllowedPermissions(dbContext);
30 |
31 | var claims = new List();
32 | claims.AddRange(context.Principal.Claims); //Copy over existing claims
33 | //Now calculate the Permissions Claim value and add it
34 | claims.Add(new Claim(PermissionConstants.PackedPermissionClaimType,
35 | await rtoPCalcer.CalcPermissionsForUserAsync(context.Principal.Claims.GetUserIdFromClaims())));
36 |
37 | //Build a new ClaimsPrincipal and use it to replace the current ClaimsPrincipal
38 | var identity = new ClaimsIdentity(claims, "Cookie");
39 | var newPrincipal = new ClaimsPrincipal(identity);
40 | context.ReplacePrincipal(newPrincipal);
41 | //THIS IS IMPORTANT: This updates the cookie, otherwise this calc will be done every HTTP request
42 | context.ShouldRenew = true;
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/AuthorizeSetup/AuthorizeSetup.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/AuthorizeSetup/IAuthCookieValidate.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Authentication.Cookies;
6 |
7 | namespace AuthorizeSetup
8 | {
9 | public interface IAuthCookieValidate
10 | {
11 | Task ValidateAsync(CookieValidatePrincipalContext context);
12 | }
13 | }
--------------------------------------------------------------------------------
/CommonCache/CommonCache.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/DataAuthorize/CalcDataKey.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Linq;
6 | using DataLayer.EfCode;
7 |
8 | namespace DataAuthorize
9 | {
10 | public class CalcDataKey
11 | {
12 | private readonly ExtraAuthorizeDbContext _context;
13 |
14 | public CalcDataKey(ExtraAuthorizeDbContext context)
15 | {
16 | _context = context;
17 | }
18 |
19 | ///
20 | /// This looks for a DataKey for the current user, which can be missing
21 | ///
22 | ///
23 | /// The found data key, or random guid string to stop it matching anything
24 | public string CalcDataKeyForUser(string userId)
25 | {
26 | return _context.DataAccess.Where(x => x.UserId == userId)
27 | .Select(x => x.LinkedTenant.DataKey).SingleOrDefault()
28 | //If no data key then set to random guid to stop it matching anything
29 | ?? Guid.NewGuid().ToString("N");
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/DataAuthorize/DataAuthorize.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/DataKeyParts/DataAuthConstants.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | namespace DataKeyParts
5 | {
6 | public static class DataAuthConstants
7 | {
8 | public const int HierarchicalKeySize = 64;
9 | public const int AccessKeySize = 64;
10 |
11 | public const string HierarchicalKeyClaimName = "DataKey";
12 | }
13 | }
--------------------------------------------------------------------------------
/DataKeyParts/DataKeyParts.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/DataKeyParts/DbContextExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using Microsoft.EntityFrameworkCore;
6 |
7 | namespace DataKeyParts
8 | {
9 | public static class DbContextExtensions
10 | {
11 | ///
12 | /// This is called in the overridden SaveChanges in the application's DbContext
13 | /// Its job is to see if a entity has the IShopLevelDataKey interface and set the appropriate key
14 | ///
15 | ///
16 | ///
17 | public static void MarkWithDataKeyIfNeeded(this DbContext context, string accessKey)
18 | {
19 | //at startup access key can be null. The demo setup sets the DataKey directly.
20 | if (accessKey == null)
21 | return;
22 |
23 | foreach (var entityEntry in context.ChangeTracker.Entries()
24 | .Where(e => e.State == EntityState.Added))
25 | {
26 | if (entityEntry.Entity is IShopLevelDataKey {DataKey: null} hasDataKey)
27 | hasDataKey.SetShopLevelDataKey(accessKey);
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/DataKeyParts/DemoSetupOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | namespace DataKeyParts
5 | {
6 | public enum AuthCookieVersions
7 | {
8 | Off,
9 | //These use UserClaimsPrincipalFactory on login
10 | LoginPermissions, LoginPermissionsDataKey,
11 | //These use The Cookie OnValidatePrincipal event
12 | PermissionsOnly, PermissionsDataKey, Impersonation, RefreshClaims, Everything
13 | }
14 |
15 | public class DemoSetupOptions
16 | {
17 | public string DatabaseSetup { get; set; }
18 | public bool CreateAndSeed { get; set; }
19 | public AuthCookieVersions AuthVersion { get; set; }
20 | }
21 |
22 |
23 | }
--------------------------------------------------------------------------------
/DataKeyParts/GetClaimsFromUser.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using Microsoft.AspNetCore.Http;
6 |
7 | namespace DataKeyParts
8 | {
9 | public class GetClaimsFromUser : IGetClaimsProvider
10 | {
11 | public GetClaimsFromUser(IHttpContextAccessor accessor)
12 | {
13 | DataKey = accessor.HttpContext?.User.Claims
14 | .SingleOrDefault(x => x.Type == DataAuthConstants.HierarchicalKeyClaimName)?.Value;
15 | }
16 |
17 | public string DataKey { get; }
18 | }
19 | }
--------------------------------------------------------------------------------
/DataKeyParts/IDataKey.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | namespace DataKeyParts
5 | {
6 | public interface IDataKey
7 | {
8 | string DataKey { get; }
9 | }
10 | }
--------------------------------------------------------------------------------
/DataKeyParts/IGetClaimsProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 | namespace DataKeyParts
4 | {
5 | public interface IGetClaimsProvider
6 | {
7 | string DataKey { get; }
8 | }
9 | }
--------------------------------------------------------------------------------
/DataKeyParts/IShopLevelDataKey.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | namespace DataKeyParts
5 | {
6 | public interface IShopLevelDataKey : IDataKey
7 | {
8 | //This method is used to set the shop-level classes' DataKey - the TenantBase classes set the property directly.
9 | void SetShopLevelDataKey(string tenantKey);
10 | }
11 | }
--------------------------------------------------------------------------------
/DataKeyParts/NoQueryFilterNeeded.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 |
6 | namespace DataKeyParts
7 | {
8 | ///
9 | /// This is used to mark a database class that doesn't need a query filter.
10 | /// This is only there so that you can unit test that all filters are set up for the classes that do have an access key
11 | ///
12 | public class NoQueryFilterNeeded : Attribute
13 | {
14 |
15 | }
16 | }
--------------------------------------------------------------------------------
/DataKeyParts/ShopLevelDataKeyBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.ComponentModel.DataAnnotations;
6 |
7 | namespace DataKeyParts
8 | {
9 | public class ShopLevelDataKeyBase : IShopLevelDataKey
10 | {
11 | [Required] //This means SQL will throw an error if we don't fill it in
12 | [MaxLength(DataAuthConstants.HierarchicalKeySize)]
13 | public string DataKey { get; private set; }
14 |
15 | //This method is used to set the shop-level classes' DataKey - the TenantBase classes set the property directly.
16 | public void SetShopLevelDataKey(string key)
17 | {
18 | if (key != null && !key.EndsWith("*"))
19 | //The shop key must end in "*" (if null then we assume something else will set the DataKey
20 | throw new ApplicationException("You tried to set a shop-level DataKey but your key didn't end with *");
21 |
22 | DataKey = key;
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/DataLayer/DataLayer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/DataLayer/EfCode/CombinedDbContext.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using DataLayer.EfCode.Configurations;
5 | using DataLayer.ExtraAuthClasses;
6 | using DataLayer.MultiTenantClasses;
7 | using Microsoft.EntityFrameworkCore;
8 |
9 | namespace DataLayer.EfCode
10 | {
11 | ///
12 | /// This only exists to create one database that covers both ExtraAuthorizeDbContext and CompanyDbContext
13 | ///
14 | public class CombinedDbContext : DbContext
15 | {
16 | //ExtraAuthorizeDbContext
17 | public DbSet UserToRoles { get; set; }
18 | public DbSet RolesToPermissions { get; set; }
19 | public DbSet ModulesForUsers { get; set; }
20 | public DbSet DataAccess { get; set; }
21 |
22 | //CompanyDbContext
23 | public DbSet Tenants { get; set; }
24 | public DbSet ShopStocks { get; set; }
25 | public DbSet ShopSales { get; set; }
26 |
27 | //TimeStore data
28 | public DbSet TimeStores { get; set; }
29 |
30 | public CombinedDbContext(DbContextOptions options)
31 | : base(options) { }
32 |
33 | protected override void OnModelCreating(ModelBuilder modelBuilder)
34 | {
35 | modelBuilder.TenantBaseConfig();
36 | modelBuilder.ExtraAuthorizeConfig();
37 | modelBuilder.CompanyDbConfig(null);
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/DataLayer/EfCode/CompanyDbContext.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using Microsoft.EntityFrameworkCore;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using DataKeyParts;
8 | using DataLayer.EfCode.Configurations;
9 | using DataLayer.ExtraAuthClasses;
10 | using DataLayer.MultiTenantClasses;
11 |
12 | namespace DataLayer.EfCode
13 | {
14 | public class CompanyDbContext : DbContext
15 | {
16 | internal readonly string DataKey;
17 |
18 | public DbSet Tenants { get; set; }
19 | public DbSet ShopStocks { get; set; }
20 | public DbSet ShopSales { get; set; }
21 |
22 | public DbSet TimeStores { get; set; }
23 |
24 | public CompanyDbContext(DbContextOptions options, IGetClaimsProvider claimsProvider)
25 | : base(options)
26 | {
27 | DataKey = claimsProvider.DataKey;
28 | }
29 |
30 | //I only have to override these two version of SaveChanges, as the other two SaveChanges versions call these
31 | public override int SaveChanges(bool acceptAllChangesOnSuccess)
32 | {
33 | this.MarkWithDataKeyIfNeeded(DataKey);
34 | return base.SaveChanges(acceptAllChangesOnSuccess);
35 | }
36 |
37 | public override async Task SaveChangesAsync(bool acceptAllChangesOnSuccess,
38 | CancellationToken cancellationToken = default(CancellationToken))
39 | {
40 | this.MarkWithDataKeyIfNeeded(DataKey);
41 | return await base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
42 | }
43 |
44 | protected override void OnModelCreating(ModelBuilder modelBuilder)
45 | {
46 | modelBuilder.TenantBaseConfig();
47 | modelBuilder.CompanyDbConfig(this);
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/DataLayer/EfCode/Configurations/ConfigExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using DataKeyParts;
5 | using DataLayer.ExtraAuthClasses;
6 | using DataLayer.MultiTenantClasses;
7 | using Microsoft.EntityFrameworkCore;
8 | using Microsoft.EntityFrameworkCore.Metadata.Builders;
9 |
10 | namespace DataLayer.EfCode.Configurations
11 | {
12 | ///
13 | /// I need to place the configs for the databases in one place because I use context.Database.EnsureCreated to create it.
14 | /// This is only for a demo app - I would normally do this via SQL scripts and EFSchemaCompare
15 | /// https://www.thereformedprogrammer.net/handling-entity-framework-core-database-migrations-in-production-part-1/#2b-hand-coding-sql-migration-scripts
16 | ///
17 | public static class ConfigExtensions
18 | {
19 | public static void TenantBaseConfig(this ModelBuilder modelBuilder)
20 | {
21 | //for some reason ExtraAuthorizeConfig doesn't config this properly so I need to add this
22 | modelBuilder.Entity()
23 | .HasDiscriminator("TenantType")
24 | .HasValue(nameof(Company))
25 | .HasValue(nameof(SubGroup))
26 | .HasValue(nameof(RetailOutlet));
27 | }
28 |
29 | public static void ExtraAuthorizeConfig(this ModelBuilder modelBuilder)
30 | {
31 | modelBuilder.Entity().HasKey(x => new { x.UserId, x.RoleName });
32 |
33 | modelBuilder.Entity()
34 | .Property("_permissionsInRole")
35 | .HasColumnName("PermissionsInRole");
36 | }
37 |
38 | public static void CompanyDbConfig(this ModelBuilder modelBuilder, CompanyDbContext context)
39 | {
40 | AddHierarchicalQueryFilter(modelBuilder.Entity(), context);
41 | AddHierarchicalQueryFilter(modelBuilder.Entity(), context);
42 | AddHierarchicalQueryFilter(modelBuilder.Entity(), context);
43 | }
44 |
45 | private static void AddHierarchicalQueryFilter(EntityTypeBuilder builder, CompanyDbContext context) where T : class, IDataKey
46 | {
47 | builder.HasQueryFilter(x => x.DataKey.StartsWith(context.DataKey));
48 | builder.HasIndex(x => x.DataKey);
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/ExtraAuthConstants.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 | namespace DataLayer.ExtraAuthClasses
4 | {
5 | public static class ExtraAuthConstants
6 | {
7 | public const int UserIdSize = 36; //This is the size of a GUID when returned as a string
8 |
9 | public const int RoleNameSize = 100;
10 | }
11 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/ModulesForUser.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.ComponentModel.DataAnnotations;
6 | using DataLayer.ExtraAuthClasses.Support;
7 | using PermissionParts;
8 |
9 | namespace DataLayer.ExtraAuthClasses
10 | {
11 | ///
12 | /// This holds what modules a user or tenant has
13 | ///
14 | public class ModulesForUser : IChangeEffectsUser, IAddRemoveEffectsUser
15 | {
16 | ///
17 | /// This links modules to a user
18 | ///
19 | ///
20 | ///
21 | public ModulesForUser(string userId, PaidForModules allowedPaidForModules)
22 | {
23 | UserId = userId ?? throw new ArgumentNullException(nameof(userId));
24 | AllowedPaidForModules = allowedPaidForModules;
25 | }
26 |
27 | [Key]
28 | [MaxLength(ExtraAuthConstants.UserIdSize)]
29 | public string UserId { get; private set; }
30 |
31 | public PaidForModules AllowedPaidForModules { get; private set; }
32 | }
33 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/Support/ChangeExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using Microsoft.EntityFrameworkCore;
6 |
7 | namespace DataLayer.ExtraAuthClasses.Support
8 | {
9 | public static class ChangeExtensions
10 | {
11 | public static bool UserPermissionsMayHaveChanged(this DbContext context)
12 | {
13 | return context.ChangeTracker.Entries()
14 | .Any(x => (x.Entity is IChangeEffectsUser && x.State == EntityState.Modified) ||
15 | (x.Entity is IAddRemoveEffectsUser &&
16 | (x.State == EntityState.Added || x.State == EntityState.Deleted)));
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/Support/IAddRemoveEffectsUser.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | namespace DataLayer.ExtraAuthClasses.Support
5 | {
6 | public interface IAddRemoveEffectsUser
7 | {
8 |
9 | }
10 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/Support/IChangeEffectsUser.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | namespace DataLayer.ExtraAuthClasses.Support
5 | {
6 | public interface IChangeEffectsUser
7 | {
8 |
9 | }
10 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/TimeStore.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using DataKeyParts;
3 | using RefreshClaimsParts;
4 |
5 | namespace DataLayer.ExtraAuthClasses
6 | {
7 | [NoQueryFilterNeeded]
8 | public class TimeStore
9 | {
10 | [Key]
11 | [Required]
12 | [MaxLength(AuthChangesConsts.CacheKeyMaxSize)]
13 | public string Key { get; set; }
14 |
15 | public long LastUpdatedTicks { get; set; }
16 | }
17 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/UserDataAccess.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.ComponentModel.DataAnnotations;
6 | using DataKeyParts;
7 |
8 | namespace DataLayer.ExtraAuthClasses
9 | {
10 | ///
11 | /// This handles the data access authorization
12 | ///
13 | public class UserDataAccessKey : UserDataAccessBase
14 | {
15 | public UserDataAccessKey(string userId, string accessKey) : base(userId)
16 | {
17 | AccessKey = accessKey;
18 | }
19 |
20 | [MaxLength(DataAuthConstants.AccessKeySize)]
21 | public string AccessKey { get; private set; }
22 | }
23 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/UserDataAccessBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.ComponentModel.DataAnnotations;
6 |
7 | namespace DataLayer.ExtraAuthClasses
8 | {
9 | ///
10 | /// This handles the data access authorization
11 | ///
12 | public class UserDataAccessBase
13 | {
14 | public UserDataAccessBase(string userId)
15 | {
16 | UserId = userId ?? throw new ArgumentNullException(nameof(userId));
17 | }
18 |
19 | [Key]
20 | [Required(AllowEmptyStrings = false)]
21 | [MaxLength(ExtraAuthConstants.UserIdSize)]
22 | public string UserId { get; private set; }
23 | }
24 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/UserDataHierarchical.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.ComponentModel.DataAnnotations.Schema;
6 | using DataLayer.MultiTenantClasses;
7 |
8 | namespace DataLayer.ExtraAuthClasses
9 | {
10 | ///
11 | /// This handles a link to a hierarchical class that holds the required DataKey
12 | ///
13 | public class UserDataHierarchical : UserDataAccessBase
14 | {
15 | //This is needed by EF Core
16 | private UserDataHierarchical(string userId, int linkedTenantId) : base(userId)
17 | {
18 | LinkedTenantId = linkedTenantId;
19 | }
20 |
21 | public UserDataHierarchical(string userId, TenantBase linkedTenant) : base(userId)
22 | {
23 | Update(linkedTenant);
24 | }
25 |
26 | public void Update(TenantBase linkedTenant)
27 | {
28 | if (linkedTenant.TenantItemId == 0)
29 | throw new ApplicationException("The linkedTenant must be already in the database.");
30 | LinkedTenant = linkedTenant;
31 | }
32 |
33 | ///
34 | /// This holds the primary key of the Tenant the user is linked to
35 | ///
36 | public int LinkedTenantId { get; private set; }
37 |
38 | [ForeignKey(nameof(LinkedTenantId))]
39 | public TenantBase LinkedTenant { get; private set; }
40 | }
41 | }
--------------------------------------------------------------------------------
/DataLayer/ExtraAuthClasses/UserToRole.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.ComponentModel.DataAnnotations;
6 | using System.ComponentModel.DataAnnotations.Schema;
7 | using DataLayer.EfCode;
8 | using DataLayer.ExtraAuthClasses.Support;
9 | using GenericServices;
10 | using StatusGeneric;
11 |
12 | namespace DataLayer.ExtraAuthClasses
13 | {
14 | ///
15 | /// This is a one-to-many relationship between the User (represented by the UserId) and their Roles (represented by RoleToPermissions)
16 | ///
17 | public class UserToRole : IAddRemoveEffectsUser, IChangeEffectsUser
18 | {
19 | private UserToRole() { } //needed by EF Core
20 |
21 | public UserToRole(string userId, RoleToPermissions role)
22 | {
23 | UserId = userId;
24 | Role = role;
25 | }
26 |
27 | //I use a composite key for this table: combination of UserId and RoleName
28 | //That has to be defined by EF Core's fluent API
29 | [Required(AllowEmptyStrings = false)]
30 | [MaxLength(ExtraAuthConstants.UserIdSize)]
31 | public string UserId { get; private set; }
32 |
33 | [Required(AllowEmptyStrings = false)]
34 | [MaxLength(ExtraAuthConstants.RoleNameSize)]
35 | public string RoleName { get; private set; }
36 |
37 | [ForeignKey(nameof(RoleName))]
38 | public RoleToPermissions Role { get; private set; }
39 |
40 |
41 | public static IStatusGeneric AddRoleToUser(string userId, string roleName, ExtraAuthorizeDbContext context)
42 | {
43 | if (roleName == null) throw new ArgumentNullException(nameof(roleName));
44 |
45 | var status = new StatusGenericHandler();
46 | if (context.Find(userId, roleName) != null)
47 | {
48 | status.AddError($"The user already has the Role '{roleName}'.");
49 | return status;
50 | }
51 | var roleToAdd = context.Find(roleName);
52 | if (roleToAdd == null)
53 | {
54 | status.AddError($"I could not find the Role '{roleName}'.");
55 | return status;
56 | }
57 |
58 | return status.SetResult(new UserToRole(userId, roleToAdd));
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/DataLayer/MultiTenantClasses/Company.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using DataLayer.EfCode;
5 | using PermissionParts;
6 |
7 | namespace DataLayer.MultiTenantClasses
8 | {
9 | ///
10 | /// This is the root entry for a multi-tenant entry. There should be only one Company per multi-tenant
11 | ///
12 | public class Company : TenantBase
13 | {
14 | private Company(string name) : base(name) { } //Needed by EF Core
15 |
16 | private Company(string name, PaidForModules allowedPaidForModules) : base(name, null)
17 | {
18 | AllowedPaidForModules = allowedPaidForModules;
19 | }
20 |
21 | public static Company AddTenantToDatabaseWithSaveChanges(string name, PaidForModules allowedPaidForModules,
22 | CompanyDbContext context)
23 | {
24 | var newTenant = new Company(name, allowedPaidForModules);
25 | TenantBase.AddTenantToDatabaseWithSaveChanges(newTenant, context);
26 | return newTenant;
27 | }
28 |
29 | ///
30 | /// This holds the modules this company have purchased
31 | ///
32 | public PaidForModules AllowedPaidForModules { get; set; }
33 | }
34 | }
--------------------------------------------------------------------------------
/DataLayer/MultiTenantClasses/RetailOutlet.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using DataLayer.EfCode;
6 |
7 | namespace DataLayer.MultiTenantClasses
8 | {
9 | public class RetailOutlet : TenantBase
10 | {
11 | private RetailOutlet(string name) : base(name) { } //Needed by EF Core
12 |
13 | private RetailOutlet(string name, TenantBase parent) : base(name, parent)
14 | {
15 | }
16 |
17 | public static RetailOutlet AddTenantToDatabaseWithSaveChanges(string name, TenantBase parent, CompanyDbContext context)
18 | {
19 | var newTenant = new RetailOutlet(name, parent);
20 | TenantBase.AddTenantToDatabaseWithSaveChanges(newTenant, context);
21 | return newTenant;
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/DataLayer/MultiTenantClasses/ShopSale.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.ComponentModel.DataAnnotations.Schema;
6 | using DataKeyParts;
7 | using Microsoft.EntityFrameworkCore;
8 | using StatusGeneric;
9 |
10 | namespace DataLayer.MultiTenantClasses
11 | {
12 | public class ShopSale : ShopLevelDataKeyBase
13 | {
14 | private ShopSale() { } //needed by EF Core
15 |
16 | private ShopSale(int numSoldReturned, string returnReason, int shopStockId)
17 | {
18 | if (numSoldReturned == 0) throw new ArgumentException("cannot be zero", nameof(numSoldReturned));
19 | if (numSoldReturned < 0 && returnReason == null) throw new ArgumentException("cannot be null if its a return", nameof(returnReason));
20 | if (shopStockId == 0) throw new ArgumentException("cannot be zero", nameof(shopStockId));
21 |
22 | NumSoldReturned = numSoldReturned;
23 | ReturnReason = returnReason;
24 | ShopStockId = shopStockId;
25 | }
26 |
27 | public int ShopSaleId { get; private set; }
28 |
29 | ///
30 | /// positive number for sale, negative number for return
31 | ///
32 | public int NumSoldReturned { get; private set; }
33 |
34 | ///
35 | /// Will be null if sale
36 | ///
37 | public string ReturnReason { get; private set; }
38 |
39 | //------------------------------------------
40 | //relationships
41 |
42 | public int ShopStockId { get; private set; }
43 |
44 | [ForeignKey(nameof(ShopStockId))]
45 | public ShopStock StockItem { get; private set; }
46 |
47 | //----------------------------------------------
48 | //create methods
49 |
50 | ///
51 | /// This creates a Sale entry, and also update the ShopStock number in stock
52 | ///
53 | ///
54 | ///
55 | ///
56 | ///
57 | public static IStatusGeneric CreateSellAndUpdateStock(int numBought, int shopStockId,
58 | DbContext context)
59 | {
60 | if (numBought < 0) throw new ArgumentException("must be positive", nameof(numBought));
61 | var status = new StatusGenericHandler();
62 |
63 | var stock = context.Find(shopStockId);
64 | if (stock == null)
65 | {
66 | status.AddError("Could not find the stock item you requested.");
67 | return status;
68 | }
69 | stock.NumInStock = stock.NumInStock - numBought;
70 | if (stock.NumInStock < 0)
71 | {
72 | status.AddError("There are not enough items of that product to sell.");
73 | return status;
74 | }
75 | var sale = new ShopSale(numBought, null, shopStockId);
76 | return status.SetResult(sale);
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------
/DataLayer/MultiTenantClasses/ShopStock.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.ComponentModel.DataAnnotations.Schema;
5 | using DataKeyParts;
6 | using Microsoft.EntityFrameworkCore.Metadata.Internal;
7 |
8 | namespace DataLayer.MultiTenantClasses
9 | {
10 | ///
11 | /// This contains an item stocked in the shop, and how many they have
12 | ///
13 | public class ShopStock : ShopLevelDataKeyBase
14 | {
15 | public int ShopStockId { get; set; }
16 | public string Name { get; set; }
17 | public decimal RetailPrice { get; set; }
18 | public int NumInStock { get; set; }
19 |
20 | //------------------------------------------
21 | //relationships
22 |
23 | public int TenantItemId { get; set; }
24 |
25 | [ForeignKey(nameof(TenantItemId))]
26 | public RetailOutlet Shop { get; set; }
27 |
28 | public override string ToString()
29 | {
30 | return $"{nameof(Name)}: {Name}, {nameof(RetailPrice)}: {RetailPrice}, {nameof(NumInStock)}: {NumInStock}";
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/DataLayer/MultiTenantClasses/SubGroup.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using DataLayer.EfCode;
5 |
6 | namespace DataLayer.MultiTenantClasses
7 | {
8 | public class SubGroup : TenantBase
9 | {
10 | private SubGroup(string name) : base(name) { } //Needed by EF Core
11 |
12 | private SubGroup(string name, TenantBase parent) : base(name, parent)
13 | {
14 | }
15 |
16 | public static SubGroup AddTenantToDatabaseWithSaveChanges(string name, TenantBase parent, CompanyDbContext context)
17 | {
18 | var newTenant = new SubGroup(name, parent);
19 | TenantBase.AddTenantToDatabaseWithSaveChanges(newTenant, context);
20 | return newTenant;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/FeatureAuthorize/CalcFeaturePermissions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using System.Reflection;
6 | using System.Threading.Tasks;
7 | using DataLayer.EfCode;
8 | using Microsoft.EntityFrameworkCore;
9 | using PermissionParts;
10 |
11 | namespace FeatureAuthorize
12 | {
13 | ///
14 | /// This is the code that calculates what feature permissions a user has
15 | ///
16 | public class CalcAllowedPermissions
17 | {
18 | private readonly ExtraAuthorizeDbContext _context;
19 |
20 | public CalcAllowedPermissions(ExtraAuthorizeDbContext context)
21 | {
22 | _context = context;
23 | }
24 |
25 | ///
26 | /// This is called if the Permissions that a user needs calculating.
27 | /// It looks at what permissions the user has, and then filters out any permissions
28 | /// they aren't allowed because they haven't get access to the module that permission is linked to.
29 | ///
30 | ///
31 | /// a string containing the packed permissions
32 | public async Task CalcPermissionsForUserAsync(string userId)
33 | {
34 | //This gets all the permissions, with a distinct to remove duplicates
35 | var permissionsForUser = (await _context.UserToRoles.Where(x => x.UserId == userId)
36 | .Select(x => x.Role.PermissionsInRole)
37 | .ToListAsync())
38 | //Because the permissions are packed we have to put these parts of the query after the ToListAsync()
39 | .SelectMany(x => x).Distinct();
40 |
41 | //we get the modules this user is allowed to see
42 | var userModules = _context.ModulesForUsers.Find(userId)
43 | ?.AllowedPaidForModules ?? PaidForModules.None;
44 | //Now we remove permissions that are linked to modules that the user has no access to
45 | var filteredPermissions =
46 | from permission in permissionsForUser
47 | let moduleAttr = typeof(Permissions).GetMember(permission.ToString())[0]
48 | .GetCustomAttribute()
49 | where moduleAttr == null || userModules.HasFlag(moduleAttr.PaidForModule)
50 | select permission;
51 |
52 | return filteredPermissions.PackPermissionsIntoString();
53 | }
54 |
55 | }
56 | }
--------------------------------------------------------------------------------
/FeatureAuthorize/FeatureAuthorize.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/FeatureAuthorize/PermissionConstants.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 | namespace FeatureAuthorize
4 | {
5 | public static class PermissionConstants
6 | {
7 | public const string PackedPermissionClaimType = "Permissions";
8 | public const string LastPermissionsUpdatedClaimType = "PermissionUpdated";
9 | }
10 | }
--------------------------------------------------------------------------------
/FeatureAuthorize/PermissionExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Security.Claims;
7 | using PermissionParts;
8 |
9 | namespace FeatureAuthorize
10 | {
11 | public static class PermissionExtensions
12 | {
13 | ///
14 | /// This returns true if the current user has the permission
15 | ///
16 | ///
17 | ///
18 | ///
19 | public static bool UserHasThisPermission(this ClaimsPrincipal user, Permissions permission)
20 | {
21 | var permissionClaim =
22 | user?.Claims.SingleOrDefault(x => x.Type == PermissionConstants.PackedPermissionClaimType);
23 | return permissionClaim?.Value.UnpackPermissionsFromString().ToArray().UserHasThisPermission(permission) == true;
24 | }
25 |
26 | public static string GetUserIdFromClaims(this IEnumerable claims)
27 | {
28 | return claims?.SingleOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value;
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/FeatureAuthorize/PolicyCode/AuthorizationPolicyProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Authorization;
6 | using Microsoft.Extensions.Options;
7 |
8 | namespace FeatureAuthorize.PolicyCode
9 | {
10 | //thanks to https://www.jerriepelser.com/blog/creating-dynamic-authorization-policies-aspnet-core/
11 | //And to GholamReza Rabbal see https://github.com/JonPSmith/PermissionAccessControl/issues/3
12 |
13 | public class AuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
14 | {
15 | private readonly AuthorizationOptions _options;
16 |
17 | public AuthorizationPolicyProvider(IOptions options) : base(options)
18 | {
19 | _options = options.Value;
20 | }
21 |
22 | public override async Task GetPolicyAsync(string policyName)
23 | {
24 | //Unit tested shows this is quicker (and safer - see link to issue above) than the original version
25 | return await base.GetPolicyAsync(policyName)
26 | ?? new AuthorizationPolicyBuilder()
27 | .AddRequirements(new PermissionRequirement(policyName))
28 | .Build();
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/FeatureAuthorize/PolicyCode/HasPermissionAttribute.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Authorization;
6 | using PermissionParts;
7 |
8 | namespace FeatureAuthorize.PolicyCode
9 | {
10 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
11 | public class HasPermissionAttribute : AuthorizeAttribute
12 | {
13 | public HasPermissionAttribute(Permissions permission) : base(permission.ToString())
14 | { }
15 | }
16 | }
--------------------------------------------------------------------------------
/FeatureAuthorize/PolicyCode/PermissionHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore.Authorization;
7 | using PermissionParts;
8 |
9 | namespace FeatureAuthorize.PolicyCode
10 | {
11 | //thanks to https://www.jerriepelser.com/blog/creating-dynamic-authorization-policies-aspnet-core/
12 |
13 | public class PermissionHandler : AuthorizationHandler
14 | {
15 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
16 | {
17 | var permissionsClaim =
18 | context.User.Claims.SingleOrDefault(c => c.Type == PermissionConstants.PackedPermissionClaimType);
19 | // If user does not have the scope claim, get out of here
20 | if (permissionsClaim == null)
21 | return Task.CompletedTask;
22 |
23 | if (permissionsClaim.Value.ThisPermissionIsAllowed(requirement.PermissionName))
24 | context.Succeed(requirement);
25 |
26 | return Task.CompletedTask;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/FeatureAuthorize/PolicyCode/PermissionRequirement.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using Microsoft.AspNetCore.Authorization;
6 |
7 | namespace FeatureAuthorize.PolicyCode
8 | {
9 | public class PermissionRequirement : IAuthorizationRequirement
10 | {
11 | public PermissionRequirement(string permissionName)
12 | {
13 | PermissionName = permissionName ?? throw new ArgumentNullException(nameof(permissionName));
14 | }
15 |
16 | public string PermissionName { get; }
17 | }
18 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Jon P Smith - Selective Analytics Ltd.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PermissionAccessControl2.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | Update headers
--------------------------------------------------------------------------------
/PermissionAccessControl2/AddDatabases.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using DataLayer.EfCode;
6 | using Microsoft.Data.Sqlite;
7 | using Microsoft.EntityFrameworkCore;
8 | using Microsoft.Extensions.Configuration;
9 | using Microsoft.Extensions.DependencyInjection;
10 | using PermissionAccessControl2.Data;
11 |
12 | namespace PermissionAccessControl2
13 | {
14 | ///
15 | /// NOTE: This class is in the ASP.NET application to get a reference to the ApplicationDbContext class
16 | ///
17 | public static class AddDatabases
18 | {
19 | public static void RegisterDatabases(this IServiceCollection services, IConfiguration configuration)
20 | {
21 | var type = configuration["DemoSetup:DatabaseSetup"];
22 | if (type == "Permanent")
23 | {
24 | //we are dealing with real databases
25 |
26 | services.AddDbContext(options =>
27 | options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
28 |
29 | services.AddDbContext(options =>
30 | options.UseSqlServer(configuration.GetConnectionString("DemoDatabaseConnection")));
31 | services.AddDbContext(options =>
32 | options.UseSqlServer(configuration.GetConnectionString("DemoDatabaseConnection")));
33 | services.AddDbContext(options =>
34 | options.UseSqlServer(configuration.GetConnectionString("DemoDatabaseConnection")));
35 | }
36 | else if (type == "InMemory")
37 | {
38 | //we are dealing with in-memory databases
39 |
40 | var aspNetAuthConnection = SetupSqliteInMemoryConnection();
41 | services.AddDbContext(options => options.UseSqlite(aspNetAuthConnection));
42 | var appExtraConnection = SetupSqliteInMemoryConnection();
43 | services.AddDbContext(options => options.UseSqlite(appExtraConnection));
44 | services.AddDbContext(options => options.UseSqlite(appExtraConnection));
45 | services.AddDbContext(options => options.UseSqlite(appExtraConnection));
46 | }
47 | else
48 | {
49 | throw new ApplicationException("This needs a section called 'DemoSetup', with property 'DatabaseSetup' in it");
50 | }
51 | }
52 |
53 | private static SqliteConnection SetupSqliteInMemoryConnection()
54 | {
55 | var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
56 | var connectionString = connectionStringBuilder.ToString();
57 | var connection = new SqliteConnection(connectionString);
58 | connection.Open(); //see https://github.com/aspnet/EntityFramework/issues/6968
59 | return connection;
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/Areas/Identity/Pages/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "/Views/Shared/_Layout.cshtml";
3 | }
4 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Controllers/CacheController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.Extensions.Caching.Distributed;
3 | using ServiceLayer.UserServices;
4 |
5 | namespace PermissionAccessControl2.Controllers
6 | {
7 | public class CacheController : Controller
8 | {
9 | private readonly ICacheRoleService _cacheRoleService;
10 |
11 | public CacheController(ICacheRoleService cacheRoleService)
12 | {
13 | _cacheRoleService = cacheRoleService;
14 | }
15 |
16 | public IActionResult Index()
17 | {
18 | return View(_cacheRoleService.ShowExistingCachePermissions(HttpContext.User.Claims));
19 | }
20 |
21 | public IActionResult Toggle()
22 | {
23 | _cacheRoleService.ToggleCacheRole();
24 | return RedirectToAction("Index");
25 | }
26 |
27 | public IActionResult ShowUpdateTime()
28 | {
29 | return View(_cacheRoleService.GetFeatureLastUpdated(HttpContext.User.Claims));
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/Controllers/CompanyController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Mvc;
6 | using ServiceLayer.CompanyServices;
7 | using ServiceLayer.CompanyServices.Concrete;
8 |
9 | namespace PermissionAccessControl2.Controllers
10 | {
11 | public class CompanyController : Controller
12 | {
13 | public IActionResult Index([FromServices] IListCompaniesService service)
14 | {
15 | return View(service.BuildViewOfHierarchy());
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/Controllers/FrontEndController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using FeatureAuthorize;
4 | using Microsoft.AspNetCore.Mvc;
5 | using PermissionParts;
6 |
7 | namespace PermissionAccessControl2.Controllers
8 | {
9 | [Route("api/[controller]")]
10 | [ApiController]
11 | public class FrontEndController : ControllerBase
12 | {
13 | ///
14 | /// This returns null (HTTP 204 - no content) if no logged in user, otherwise a array of the logged in user's permissions
15 | ///
16 | ///
17 | [HttpGet]
18 | public IEnumerable Get()
19 | {
20 | var packedPermissions = HttpContext.User?.Claims
21 | .SingleOrDefault(x => x.Type == PermissionConstants.PackedPermissionClaimType);
22 | return packedPermissions?.Value.UnpackPermissionsFromString().Select(x => x.ToString());
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Controllers/HomeController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using DataKeyParts;
7 | using Microsoft.AspNetCore.Mvc;
8 | using Microsoft.Extensions.Options;
9 | using PermissionAccessControl2.Models;
10 | using ServiceLayer.UserServices;
11 | using ServiceLayer.UserServices.Concrete;
12 |
13 | namespace PermissionAccessControl2.Controllers
14 | {
15 | public class HomeController : Controller
16 | {
17 | public IActionResult Index([FromServices] IOptions demoSetup)
18 | {
19 | return View(demoSetup.Value);
20 | }
21 |
22 | public IActionResult Privacy()
23 | {
24 | return View();
25 | }
26 |
27 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
28 | public IActionResult Error()
29 | {
30 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Controllers/ImpersonateController.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using FeatureAuthorize.PolicyCode;
6 | using Microsoft.AspNetCore.Authorization;
7 | using Microsoft.AspNetCore.Mvc;
8 | using PermissionParts;
9 | using ServiceLayer.UserServices;
10 | using UserImpersonation;
11 |
12 | namespace PermissionAccessControl2.Controllers
13 | {
14 | public class ImpersonateController : Controller
15 | {
16 | //You would normally protect this with [HasPermission(Permissions.Impersonate)], but I left that off on
17 | public IActionResult Index([FromServices] IListUsersService service)
18 | {
19 | if (User.InImpersonationMode())
20 | return RedirectToAction(nameof(Message),
21 | new { errorMessage = "You are already in impersonation mode."});
22 | return View(service.ListUserWithRolesAndDataTenant());
23 | }
24 |
25 | [HttpPost]
26 | [ValidateAntiForgeryToken]
27 | [HasPermission(Permissions.Impersonate)]
28 | public IActionResult StartNormal(string userId, string userName, [FromServices] IImpersonationService service)
29 | {
30 | var errorMessage = service.StartImpersonation(userId, userName, false);
31 | return RedirectToAction(nameof(Message),
32 | new {errorMessage, successMessage =$"You are now impersonating user {userName} with their permissions." });
33 | }
34 |
35 | [HttpPost]
36 | [ValidateAntiForgeryToken]
37 | [HasPermission(Permissions.ImpersonateKeepOwnPermissions)]
38 | public IActionResult StartEnhanced(string userId, string userName, [FromServices] IImpersonationService service)
39 | {
40 | var errorMessage = service.StartImpersonation(userId, userName, true);
41 | return RedirectToAction(nameof(Message),
42 | new { errorMessage, successMessage = $"You are now impersonating user {userName} with your own permissions." });
43 | }
44 |
45 | [Authorize] //you must be logged in
46 | //Note: anyone call call Stop, as when impersonating someone you don't know what permissions (if any) that they have
47 | public IActionResult Stop([FromServices] IImpersonationService service)
48 | {
49 | var errorMessage = service.StopImpersonation();
50 | return RedirectToAction(nameof(Message),
51 | new { errorMessage, successMessage = $"You have stopped impersonating another user." });
52 | }
53 |
54 | public IActionResult Message(string errorMessage, string successMessage)
55 | {
56 | return View(new Tuple(errorMessage, successMessage));
57 | }
58 |
59 |
60 | }
61 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/Controllers/ShopController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using DataLayer.EfCode;
5 | using FeatureAuthorize.PolicyCode;
6 | using GenericServices;
7 | using GenericServices.AspNetCore;
8 | using Microsoft.AspNetCore.Mvc;
9 | using PermissionParts;
10 | using ServiceLayer.Shop;
11 |
12 | namespace PermissionAccessControl2.Controllers
13 | {
14 | public class ShopController : Controller
15 | {
16 | [HttpGet]
17 | [HasPermission(Permissions.SalesSell)]
18 | public IActionResult Till([FromServices] ICrudServices service)
19 | {
20 | var dto = new SellItemDto();
21 | dto.SetResetDto(service.ReadManyNoTracked().ToList());
22 | return View(dto);
23 | }
24 |
25 | [HttpPost]
26 | [ValidateAntiForgeryToken]
27 | [HasPermission(Permissions.SalesSell)]
28 | public IActionResult Till([FromServices] ICrudServices service, SellItemDto dto)
29 | {
30 | if (!ModelState.IsValid)
31 | {
32 | dto.SetResetDto(service.ReadManyNoTracked().ToList());
33 | return View(dto);
34 | }
35 |
36 | var result = service.CreateAndSave(dto);
37 | if (service.IsValid)
38 | return RedirectToAction("BuySuccess", new { message = service.Message, result.ShopSaleId });
39 |
40 | //Error state
41 | service.CopyErrorsToModelState(ModelState, dto);
42 | dto.SetResetDto(service.ReadManyNoTracked().ToList());
43 | return View(dto);
44 | }
45 |
46 | public IActionResult BuySuccess([FromServices] ICrudServices service, string message, int shopSaleId)
47 | {
48 | var saleInfo = service.ReadSingle(shopSaleId);
49 | return View(new Tuple(saleInfo, message));
50 | }
51 |
52 | [HasPermission(Permissions.StockRead)]
53 | public IActionResult Stock([FromServices] ICrudServices service)
54 | {
55 | var allStock = service.ReadManyNoTracked().ToList();
56 | var allTheSameShop = allStock.Any() && allStock.All(x => x.ShopName == allStock.First().ShopName);
57 | return View(new Tuple, bool>(allStock, allTheSameShop));
58 | }
59 |
60 | [HasPermission(Permissions.SalesRead)]
61 | public IActionResult Sales([FromServices] ICrudServices service)
62 | {
63 | var allSales = service.ReadManyNoTracked().ToList();
64 | var allTheSameShop = allSales.Any() && allSales.All(x => x.StockItemShopName == allSales.First().StockItemShopName);
65 | return View(new Tuple, bool>(allSales, allTheSameShop));
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/Controllers/UsersController.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using System.Security.Claims;
6 | using DataLayer.EfCode;
7 | using DataLayer.ExtraAuthClasses;
8 | using FeatureAuthorize;
9 | using GenericServices;
10 | using Microsoft.AspNetCore.Mvc;
11 | using PermissionParts;
12 | using ServiceLayer.UserServices;
13 |
14 | namespace PermissionAccessControl2.Controllers
15 | {
16 | public class UsersController : Controller
17 | {
18 | // GET
19 | public IActionResult Index()
20 | {
21 | return View(HttpContext.User);
22 | }
23 |
24 | public IActionResult Users([FromServices] IListUsersService service)
25 | {
26 | return View(service.ListUserWithRolesAndDataTenant());
27 | }
28 |
29 | public IActionResult AllRoles([FromServices] ICrudServices services)
30 | {
31 | return View(services.ReadManyNoTracked().ToList());
32 | }
33 |
34 | public IActionResult UserPermissions()
35 | {
36 | return View(HttpContext.User.Claims.PermissionsFromClaims());
37 | }
38 |
39 | }
40 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/Data/ApplicationDbContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
5 | using Microsoft.EntityFrameworkCore;
6 |
7 | namespace PermissionAccessControl2.Data
8 | {
9 | public class ApplicationDbContext : IdentityDbContext
10 | {
11 | public ApplicationDbContext(DbContextOptions options)
12 | : base(options)
13 | {
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Models/ErrorViewModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace PermissionAccessControl2.Models
4 | {
5 | public class ErrorViewModel
6 | {
7 | public string RequestId { get; set; }
8 |
9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
10 | }
11 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/PermissionAccessControl2.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 | aspnet-PermissionAccessControl2-85B8F4FB-8C2D-4BB7-82AE-49E851E29A20
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | true
36 | $(NoWarn);1591
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using DataKeyParts;
3 | using DataLayer.EfCode;
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using Microsoft.Extensions.Hosting;
7 | using Microsoft.Extensions.Options;
8 | using PermissionAccessControl2.Data;
9 | using PermissionAccessControl2.SeedDemo;
10 |
11 | namespace PermissionAccessControl2
12 | {
13 | public class Program
14 | {
15 | public static async Task Main(string[] args)
16 | {
17 | var host = CreateHostBuilder(args).Build();
18 | //This migrates the database and adds any seed data as required
19 | await SetupDatabasesAndSeedAsync(host);
20 | await host.RunAsync();
21 | }
22 |
23 | public static IHostBuilder CreateHostBuilder(string[] args) =>
24 | Host.CreateDefaultBuilder(args)
25 | .ConfigureWebHostDefaults(webBuilder =>
26 | {
27 | webBuilder.UseStartup();
28 | });
29 |
30 |
31 | private static async Task SetupDatabasesAndSeedAsync(IHost webHost)
32 | {
33 | using (var scope = webHost.Services.CreateScope())
34 | {
35 | var services = scope.ServiceProvider;
36 | var demoSetupOptions = services.GetRequiredService>();
37 | if (demoSetupOptions.Value.CreateAndSeed)
38 | {
39 | //it only creates the databases and seeds them if the DemoSetup:CreateAndSeed property is true
40 |
41 | using (var context = services.GetRequiredService())
42 | {
43 | context.Database.EnsureCreated();
44 | }
45 | //This creates a database which is a combination of both the ExtraAuthorizeDbContext and CompanyDbContext
46 | using (var context = services.GetRequiredService())
47 | {
48 | context.Database.EnsureCreated();
49 | }
50 |
51 | await webHost.Services.CheckSeedDataAndUserAsync();
52 | await webHost.Services.CheckAddSuperAdminAsync();
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:65480",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "PermissionAccessControl2": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | },
24 | "applicationUrl": "https://localhost:5001;http://localhost:5000"
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/SeedDemo/Internal/AspNetUserExtension.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Linq;
6 | using System.Runtime.CompilerServices;
7 | using System.Threading.Tasks;
8 | using Microsoft.AspNetCore.Identity;
9 |
10 | [assembly: InternalsVisibleTo("Test")]
11 |
12 | namespace PermissionAccessControl2.SeedDemo.Internal
13 | {
14 | internal static class AspNetUserExtension
15 | {
16 | ///
17 | /// This will add a user with the given email if they don't all ready exist
18 | ///
19 | ///
20 | ///
21 | ///
22 | ///
23 | public static async Task CheckAddNewUserAsync(this UserManager userManager, string email, string password)
24 | {
25 | var user = await userManager.FindByEmailAsync(email);
26 | if (user != null)
27 | return user;
28 | user = new IdentityUser { UserName = email, Email = email };
29 | var result = await userManager.CreateAsync(user, password);
30 | if (!result.Succeeded)
31 | {
32 | var errorDescriptions = string.Join("\n", result.Errors.Select(x => x.Description));
33 | throw new InvalidOperationException(
34 | $"Tried to add user {email}, but failed. Errors:\n {errorDescriptions}");
35 | }
36 |
37 | return user;
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/SeedDemo/Internal/DemoUsersSetup.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Runtime.CompilerServices;
8 | using System.Threading.Tasks;
9 | using DataLayer.EfCode;
10 | using DataLayer.MultiTenantClasses;
11 | using Microsoft.AspNetCore.Identity;
12 | using Microsoft.EntityFrameworkCore;
13 | using Microsoft.Extensions.DependencyInjection;
14 | using Newtonsoft.Json;
15 | using ServiceLayer.UserServices;
16 |
17 | [assembly: InternalsVisibleTo("Test")]
18 |
19 | namespace PermissionAccessControl2.SeedDemo.Internal
20 | {
21 | ///
22 | /// This is the code that sets up the demo user, with their roles and data keys.
23 | /// It uses the data from in the wwwroot/SeedData/Users.json file
24 | /// ONLY USED FOR DEMO
25 | ///
26 | internal class DemoUsersSetup
27 | {
28 | private readonly UserManager _userManager;
29 | private readonly ExtraAuthorizeDbContext _extraContext;
30 | private readonly ExtraAuthUsersSetup _extraService;
31 |
32 | public DemoUsersSetup(IServiceProvider services)
33 | {
34 | _userManager = services.GetRequiredService>();
35 | _extraContext = services.GetRequiredService();
36 | _extraService = new ExtraAuthUsersSetup(_extraContext);
37 | }
38 |
39 | public async Task CheckAddDemoUsersAsync(string usersJson)
40 | {
41 | var allOutlets = _extraContext.Tenants.IgnoreQueryFilters().OfType().ToList();
42 | foreach (var userSpec in JsonConvert.DeserializeObject>(usersJson))
43 | {
44 | if (userSpec.LinkedTenant.StartsWith("*"))
45 | {
46 | //We need to form names for outlets
47 | foreach (var retailOutlet in allOutlets.Where(x => x.Name.EndsWith(userSpec.LinkedTenant.Substring(1))))
48 | {
49 | var email = retailOutlet.Name.Replace(' ', '-') + userSpec.Email.Substring(1);
50 | await CheckAddUser(email, userSpec.RolesCommaDelimited, retailOutlet);
51 | }
52 | }
53 | else
54 | {
55 | var foundTenant = _extraContext.Tenants.IgnoreQueryFilters()
56 | .SingleOrDefault(x => x.Name == userSpec.LinkedTenant);
57 | if (foundTenant == null)
58 | throw new ApplicationException($"Could not find a tenant named {userSpec.LinkedTenant}.");
59 | await CheckAddUser(userSpec.Email, userSpec.RolesCommaDelimited, foundTenant);
60 | }
61 | }
62 |
63 | _extraContext.SaveChanges();
64 | }
65 |
66 | private async Task CheckAddUser(string email, string rolesCommaDelimited, TenantBase linkedTenant)
67 | {
68 | var user = await _userManager.CheckAddNewUserAsync(email, email); //password is their email
69 | foreach (var roleName in rolesCommaDelimited.Split(',').Select(x => x.Trim()))
70 | {
71 | _extraService.CheckAddRoleToUser(user.Id, roleName);
72 | }
73 | _extraService.AddUpdateDataAccessHierarchical(user.Id, linkedTenant);
74 | _extraService.CheckAddModules(user.Id, linkedTenant);
75 | }
76 |
77 |
78 | private class UserJson
79 | {
80 | public string Email { get; set; }
81 | public string RolesCommaDelimited { get; set; }
82 | public string LinkedTenant { get; set; }
83 | }
84 |
85 | }
86 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/SeedDemo/Internal/HierarchicalSeeder.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Runtime.CompilerServices;
7 | using DataLayer.EfCode;
8 | using DataLayer.MultiTenantClasses;
9 | using PermissionParts;
10 |
11 | [assembly: InternalsVisibleTo("Test")]
12 |
13 | namespace PermissionAccessControl2.SeedDemo.Internal
14 | {
15 | ///
16 | /// This is a extension method that converts a given format of text data (see default companyDefinitions)
17 | /// in to a set of tenants with hierarchical links between them. Just used to demo tenant data for display or unit testing
18 | /// ONLY USED FOR DEMO and UNIT TESTING
19 | ///
20 | public static class HierarchicalSeeder
21 | {
22 | public static List AddCompanyAndChildrenInDatabase(this CompanyDbContext context, params string[] companyDefinitions)
23 | {
24 | if (!companyDefinitions.Any())
25 | companyDefinitions = new[]
26 | {
27 | "4U Inc.|West Coast|San Fran|SF Dress4U, SF Tie4U, SF Shirt4U",
28 | "4U Inc.|West Coast|LA|LA Dress4U, LA Tie4U, LA Shirt4U"
29 | };
30 |
31 | var companyDict = new Dictionary();
32 | var subGroupsDict = new Dictionary>();
33 | foreach (var companyDefinition in companyDefinitions)
34 | {
35 | var hierarchyNames = companyDefinition.Split('|');
36 | if (!companyDict.ContainsKey(hierarchyNames[0]))
37 | {
38 | companyDict[hierarchyNames[0]] = Company.AddTenantToDatabaseWithSaveChanges(
39 | hierarchyNames[0], PaidForModules.None, context);
40 | subGroupsDict.Clear();
41 | }
42 |
43 | TenantBase parent = companyDict[hierarchyNames[0]];
44 |
45 | for (int i = 1; i < hierarchyNames.Length; i++)
46 | {
47 | if (!subGroupsDict.ContainsKey(i))
48 | {
49 | subGroupsDict[i] = new List();
50 | }
51 | if (i + 1 == hierarchyNames.Length)
52 | {
53 | //End, which are shops
54 | var shopNames = hierarchyNames[i].Split(',').Select(x => x.Trim());
55 | foreach (var shopName in shopNames)
56 | {
57 | RetailOutlet.AddTenantToDatabaseWithSaveChanges(shopName, parent, context);
58 | }
59 | }
60 | else
61 | {
62 | //Groups
63 | SubGroup subGroup = null;
64 | if (subGroupsDict[i].Any(x => x.Name == hierarchyNames[i]))
65 | {
66 | subGroup = subGroupsDict[i].Single(x => x.Name == hierarchyNames[i]);
67 | }
68 | else
69 | {
70 | subGroup = SubGroup.AddTenantToDatabaseWithSaveChanges(hierarchyNames[i], parent, context);
71 | subGroupsDict[i].Add(subGroup);
72 | }
73 | parent = subGroup;
74 | }
75 | }
76 | }
77 |
78 | return new List(companyDict.Values);
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/SeedDemo/Internal/SetupShopStock.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Linq;
6 | using DataLayer.EfCode;
7 | using DataLayer.MultiTenantClasses;
8 | using Microsoft.EntityFrameworkCore;
9 |
10 | namespace PermissionAccessControl2.SeedDemo.Internal
11 | {
12 | ///
13 | /// This adds stock to the shops using the data given by the wwwroot/SeedData/ShopStock.txt file
14 | /// ONLY USED FOR DEMO
15 | ///
16 | public static class SetupShopStock
17 | {
18 | public static void AddStockToShops(this CompanyDbContext context, string [] lines)
19 | {
20 | foreach (var line in lines)
21 | {
22 | var colonIndex = line.IndexOf(':');
23 | var shopName = line.Substring(0, colonIndex);
24 | var shop = context.Tenants.IgnoreQueryFilters().OfType()
25 | .SingleOrDefault(x => x.Name == shopName);
26 | if (shop == null)
27 | throw new ApplicationException($"Could not find a shop of name '{shopName}'");
28 |
29 | var eachStock = from stockAndPrice in line.Substring(colonIndex + 1).Split(',')
30 | let parts = stockAndPrice.Split('|').Select(x => x.Trim()).ToArray()
31 | select new {Name = parts[0], Price = decimal.Parse(parts[1])};
32 | foreach (var stock in eachStock)
33 | {
34 | var newStock = new ShopStock {Name = stock.Name, NumInStock = 5, RetailPrice = stock.Price, Shop = shop};
35 | newStock.SetShopLevelDataKey(shop.DataKey);
36 | context.Add(newStock);
37 | }
38 | }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/SeedDemo/SuperAdminSetup.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 | using DataLayer.EfCode;
9 | using Microsoft.AspNetCore.Identity;
10 | using Microsoft.Extensions.Configuration;
11 | using Microsoft.Extensions.DependencyInjection;
12 | using PermissionAccessControl2.SeedDemo.Internal;
13 | using PermissionParts;
14 | using ServiceLayer.UserServices;
15 |
16 | namespace PermissionAccessControl2.SeedDemo
17 | {
18 | public static class SuperAdminSetup
19 | {
20 | private const string SuperAdminRoleName = "SuperAdmin";
21 |
22 | ///
23 | /// This ensures there is a SuperAdmin user in the system.
24 | /// It gets the SuperAdmin's email and password from the "SuperAdmin" section of the appsettings.json file
25 | /// NOTE: for security reasons I only allows one user with the RoleName of
26 | ///
27 | ///
28 | ///
29 | public static async Task CheckAddSuperAdminAsync(this IServiceProvider serviceProvider)
30 | {
31 | using (var scope = serviceProvider.CreateScope())
32 | {
33 | var services = scope.ServiceProvider;
34 | var extraContext = services.GetRequiredService();
35 | if (extraContext.UserToRoles.Any(x => x.RoleName == SuperAdminRoleName))
36 | //For security reasons there can only be one user with the SuperAdminRoleName
37 | return;
38 |
39 | var userManager = services.GetRequiredService>();
40 |
41 | var config = services.GetRequiredService();
42 | var superSection = config.GetSection("SuperAdmin");
43 | if (superSection == null)
44 | return;
45 |
46 | var userEmail = superSection["Email"];
47 | var userPassword = superSection["Password"];
48 |
49 | var superUser = await userManager.CheckAddNewUserAsync(userEmail, userPassword);
50 |
51 | using (var context = services.GetRequiredService())
52 | {
53 | var extraService = new ExtraAuthUsersSetup(context);
54 | extraService.AddUpdateRoleToPermissions(SuperAdminRoleName, "SuperAdmin Role", new List{ Permissions.AccessAll});
55 | extraService.CheckAddRoleToUser(superUser.Id, SuperAdminRoleName);
56 | context.SaveChanges();
57 | }
58 | }
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Cache/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model IEnumerable
2 | @{
3 | ViewData["Title"] = "Index";
4 | }
5 |
6 | Dynamic toggling of user's claims
7 |
8 | @if (@Model == null)
9 | {
10 | Either no one is logged in or UpdateCookieOnChange is set to false in appsettings.json.
11 | }
12 | else
13 | {
14 | You currently have these cache permissions in your claims
15 |
16 | @foreach (var claim in @Model)
17 | {
18 | @claim.ToString()
19 | }
20 |
21 | Toggle Cache
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Cache/ShowUpdateTime.cshtml:
--------------------------------------------------------------------------------
1 | @model IEnumerable
2 | @{
3 | ViewData["Title"] = "Index";
4 | }
5 |
6 | Show Updated Time
7 |
8 |
9 |
10 |
11 | @foreach (var message in @Model)
12 | {
13 | @message
14 | }
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Company/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model List
2 | @{
3 | ViewData["Title"] = "Index";
4 | }
5 |
6 | Show tenant hierarchy
7 |
8 | @foreach (var html in @Model)
9 | {
10 |
11 | @html
12 |
13 | }
14 |
15 |
16 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Home/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model DataKeyParts.DemoSetupOptions
2 | @{
3 | ViewData["Title"] = "Home Page";
4 | }
5 |
6 |
7 |
Welcome to example authorization app, version 2
8 |
9 |
10 | This is a demo of a web application using my approach to feature and data authorization in an ASP.NET Core web application.
11 | It is demo of a Multi-tenant service for retail outlets which holds the stock and sales for each shop.
12 | It also allows managers to see what is happening in every shop that they are managing.
13 |
14 | This list of articles below describe how it works.
15 |
21 |
22 |
23 |
The setup from the appsetting.json DemoSetup section is:
24 |
25 | @nameof(DataKeyParts.DemoSetupOptions.DatabaseSetup) : @Model.DatabaseSetup
26 | @nameof(DataKeyParts.DemoSetupOptions.CreateAndSeed) : @Model.CreateAndSeed
27 | @nameof(DataKeyParts.DemoSetupOptions.AuthVersion) : @Model.AuthVersion
28 |
29 |
30 |
31 | How to use use this application
32 |
33 |
34 |
35 | Log in using one of the demo users found here .
36 |
37 | Pick a user that has a SaleManager (good choice!) or SalesAssistant role if you want to sell something.
38 | Pick a user that has a AreaManger if you want to see data for all shops.
39 |
40 |
41 |
42 | Once you have logged in then a menu should have a "Shop" dropdown - pick one of the following.
43 |
44 | Till to sell something (not present if AreaManager).
45 | Stock to see what stock the shop has.
46 | Sales to see what sales the shop has done (not present if SalesAssistant).
47 |
48 |
49 | When log in you can see your claims, permissions and other user/role ifo via the "Users" menu button.
50 | Also look at Company for a list of the hierarchical data.
51 | To test refresh of users permissions go to Toggle Role to see this in action. NOTE: UpdateCookieOnChange must be set to true in appsettings.json
52 | To test user impersonation feature log in as the SuperAdmin user go to Impersonation->Pick User to see this in action. NOTE: UpdateCookieOnChange must be set to true in appsettings.json
53 |
54 |
55 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Home/Privacy.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Privacy Policy";
3 | }
4 | @ViewData["Title"]
5 |
6 | This is a demo system and doesn't save your data (unless you set it to do that!). It uses a cookie when you log in, but no tracking.
7 | This code is under the MIT license.
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Impersonate/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model List
2 | @{
3 | ViewData["Title"] = "Users";
4 | }
5 |
6 |
7 |
8 | User Name/Email
9 | User roles
10 | Company
11 | Linked tenant
12 |
13 |
14 | @foreach (var user in @Model)
15 | {
16 |
17 | @user.UserName
18 | @user.RoleNames
19 | @user.CompanyName
20 | @user.LinkedTenantName
21 |
22 |
29 |
36 |
37 |
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Impersonate/Message.cshtml:
--------------------------------------------------------------------------------
1 | @model System.Tuple
2 |
3 | @{
4 | ViewBag.Title = "Message";
5 | }
6 |
7 | Message
8 | @if (Model.Item1 != null)
9 | {
10 | Error: @Model.Item1
11 | }
12 | else
13 | {
14 | @Model.Item2
15 | }
16 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @model ErrorViewModel
2 | @{
3 | ViewData["Title"] = "Error";
4 | }
5 |
6 | Error.
7 | An error occurred while processing your request.
8 |
9 | @if (Model.ShowRequestId)
10 | {
11 |
12 | Request ID: @Model.RequestId
13 |
14 | }
15 |
16 | Development Mode
17 |
18 | Swapping to Development environment will display more detailed information about the error that occurred.
19 |
20 |
21 | The Development environment shouldn't be enabled for deployed applications.
22 | It can result in displaying sensitive information from exceptions to end users.
23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
24 | and restarting the app.
25 |
26 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Shared/_CookieConsentPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Http.Features
2 |
3 | @{
4 | var consentFeature = Context.Features.Get();
5 | var showBanner = !consentFeature?.CanTrack ?? false;
6 | var cookieString = consentFeature?.CreateConsentCookie();
7 | }
8 |
9 | @if (showBanner)
10 | {
11 |
12 | Use this space to summarize your privacy and cookie use policy.
Learn More .
13 |
14 | Accept
15 |
16 |
17 |
25 | }
26 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Shared/_LoginPartial.cshtml:
--------------------------------------------------------------------------------
1 | @using Microsoft.AspNetCore.Identity
2 | @using UserImpersonation
3 | @inject SignInManager SignInManager
4 | @inject UserManager UserManager
5 |
6 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Shared/_ValidationScriptsPartial.cshtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
18 |
19 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Shop/BuySuccess.cshtml:
--------------------------------------------------------------------------------
1 | @model System.Tuple
2 |
3 | @{
4 | ViewBag.Title = "title";
5 | }
6 |
7 | @Model.Item2
8 |
9 | Invoice number:
10 |
11 | @Html.DisplayFor(e => e.Item1.ShopSaleId)
12 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Shop/Sales.cshtml:
--------------------------------------------------------------------------------
1 | @model Tuple, bool>
2 | @{
3 | ViewData["Title"] = "Shop Stock";
4 | }
5 |
6 | @if (!Model.Item1.Any())
7 | {
8 | No sales found
9 | }
10 | else
11 | {
12 | var displayThis = Model.Item2 ? "for shop" + Model.Item1.First().StockItemShopName : "(by shop)";
13 |
14 | List Stock @displayThis
15 |
16 |
17 | @if (!Model.Item2)
18 | {
19 | Shop
20 | }
21 | Stock name
22 | Num sold/returned
23 | Total gain/loss
24 |
25 | @foreach (var stock in @Model.Item1)
26 | {
27 |
28 | @if (!Model.Item2)
29 | {
30 | @stock.StockItemShopName
31 | }
32 | @stock.StockItemName
33 | @stock.NumSoldReturned
34 | @(stock.StockItemRetailPrice * stock.NumSoldReturned)
35 |
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Shop/Stock.cshtml:
--------------------------------------------------------------------------------
1 | @model Tuple, bool>
2 | @{
3 | ViewData["Title"] = "Shop Stock";
4 | }
5 |
6 | @if (!Model.Item1.Any())
7 | {
8 | No stock found
9 | }
10 | else
11 | {
12 | var displayThis = Model.Item2 ? "for shop" + Model.Item1.First().ShopName : "(by shop)";
13 |
14 | List Stock @displayThis
15 |
16 |
17 | @if (!Model.Item2)
18 | {
19 | Shop
20 | }
21 | Stock name
22 | #In stock
23 | Retail price
24 |
25 | @foreach (var stock in @Model.Item1)
26 | {
27 |
28 | @if (!Model.Item2)
29 | {
30 | @stock.ShopName
31 | }
32 | @stock.Name
33 | @stock.NumInStock
34 | @stock.RetailPrice
35 |
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Shop/Till.cshtml:
--------------------------------------------------------------------------------
1 | @model ServiceLayer.Shop.SellItemDto
2 |
3 | @{
4 | ViewBag.Title = "Till";
5 | }
6 |
7 | Shop Sale
8 |
9 |
37 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Users/AllRoles.cshtml:
--------------------------------------------------------------------------------
1 | @model System.Collections.Generic.List
2 |
3 | @{
4 | ViewBag.Title = "title";
5 | }
6 |
7 | All Roles
8 |
9 |
10 |
11 |
12 | Role Name
13 |
14 |
15 | Permissions
16 |
17 | @* *@
18 |
19 |
20 |
21 | @foreach (var item in Model) {
22 |
23 |
24 | @Html.DisplayFor(modelItem => item.RoleName)
25 |
26 |
27 | @string.Join(", ", item.PermissionsInRole.Select(x => x.ToString()))
28 |
29 | @*
30 | @Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
31 | @Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
32 | @Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
33 | *@
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Users/Index.cshtml:
--------------------------------------------------------------------------------
1 | @model System.Security.Claims.ClaimsPrincipal
2 | @{
3 | ViewData["Title"] = "User";
4 | }
5 |
6 | Current logged in user claims.
7 |
8 | @if (Model?.Identity.IsAuthenticated == true)
9 | {
10 | User '@User.Identity.Name'
11 |
12 |
13 | @foreach (var claim in @Model.Claims)
14 | {
15 | @claim.ToString()
16 | }
17 |
18 | }
19 | else
20 | {
21 | No user is logged in.
22 | }
23 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Users/UserPermissions.cshtml:
--------------------------------------------------------------------------------
1 | @model IEnumerable
2 | @{
3 | ViewData["Title"] = "User";
4 | }
5 |
6 | Current logged in user Permissions.
7 |
8 | @if (User?.Identity.IsAuthenticated == true)
9 | {
10 | User '@User.Identity.Name'
11 | if (@Model == null)
12 | {
13 | User has no permissions.
14 | }
15 | else
16 | {
17 |
18 | @foreach (var claim in @Model)
19 | {
20 | @claim.ToString()
21 | }
22 |
23 | }
24 | }
25 | else
26 | {
27 | No user is logged in.
28 | }
29 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/Users/Users.cshtml:
--------------------------------------------------------------------------------
1 | @model List
2 | @{
3 | ViewData["Title"] = "Users";
4 | }
5 |
6 | List of demo users so you can log in to try the app
7 | NOTE: The Email address is also the Password!
8 |
9 |
10 | User Name/Email
11 | User roles
12 | Company
13 | Linked tenant
14 |
15 | @foreach (var user in @Model)
16 | {
17 |
18 | @user.UserName
19 | @user.RoleNames
20 | @user.CompanyName
21 | @user.LinkedTenantName
22 |
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/_ViewImports.cshtml:
--------------------------------------------------------------------------------
1 | @using PermissionAccessControl2
2 | @using PermissionAccessControl2.Models
3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
4 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/Views/_ViewStart.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | Layout = "_Layout";
3 | }
4 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/appsettings.Development.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Debug",
5 | "System": "Information",
6 | "Microsoft": "Information"
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=PermissionAccessControl2-AspNetCoreIdentity;Trusted_Connection=True;MultipleActiveResultSets=true",
4 | "DemoDatabaseConnection": "Server=(localdb)\\mssqllocaldb;Database=PermissionAccessControl2-DemoDatabase;Trusted_Connection=True;MultipleActiveResultSets=true"
5 | },
6 | "Logging": {
7 | "LogLevel": {
8 | "Default": "Warning"
9 | }
10 | },
11 | "AllowedHosts": "*",
12 | "SuperAdmin": //This holds the information on the superuser. You must have one SuperUser setup otherwise you can't manage users
13 | {
14 | "Email": "Super@g1.com",
15 | "Password": "Super@g1.com"
16 | },
17 | "DemoSetup": {
18 | "DatabaseSetup": "InMemory", //This can be "InMemory" or "Permanent" (a real database) database.
19 | "CreateAndSeed": true, //If this is true then it will create the dbs and ensure the data is seeded
20 | "AuthVersion": "Everything" //The options are Off, LoginPermissions, LoginPermissionsDataKey, PermissionsOnly, PermissionsDataKey, Impersonation, RefreshClaims, Everything
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/SeedData/Companies.txt:
--------------------------------------------------------------------------------
1 | 4U Inc.|West Coast|San Fran|SF Dress4U, SF Tie4U, SF Shirt4U
2 | 4U Inc.|West Coast|LA|LA Dress4U, LA Tie4U, LA Shirt4U
3 | 4U Inc.|East Coast|NY Dress4U, Boston Shirt4U
4 | Pets2 Ltd.|London|Cats Place, Kitten Place
5 | Pets2 Ltd.|Bristol|Dogs Place, Puppy Place
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/SeedData/Roles.txt:
--------------------------------------------------------------------------------
1 | Director: EmployeeRead
2 | AreaManager: StockRead, SalesRead
3 | SalesAssistant: StockRead, SalesRead
4 | StoreManager: StockRead, StockAddNew, StockRemove, SalesRead, SalesSell, SalesReturn
5 | UserAdmin: UserRead, UserChange, RoleRead
6 | CacheRole: Cache1, Cache2
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/SeedData/ShopStock.txt:
--------------------------------------------------------------------------------
1 | SF Dress4U: Flower dress|50, Tiny dress|22
2 | SF Tie4U: Blue tie|15, Red tie|20, Green tie|10
3 | SF Shirt4U: White shirt|40, Blue shirt|30
4 | LA Dress4U: Smart dress|90, Expensive dress|210
5 | LA Tie4U: Blue tie|15, Red tie|20, Green tie|10
6 | LA Shirt4U: White shirt|40, Blue shirt|30
7 | NY Dress4U: Modern dress|65, Nice dress|30
8 | Boston Shirt4U: White shirt|40, Blue shirt|30
9 | Cats Place: Cat food (large)|40, Cat food (small)|10
10 | Kitten Place: Scratch pole|60, Play mouse|5, Cat food (small)|12
11 | Dogs Place: Dog food (large)|25, Chew bone|13
12 | Puppy Place: Dog lead|7, Dog food (small)|9
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/SeedData/Users.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Email": "dir@4U.com",
4 | "RolesCommaDelimited": "Director,CacheRole",
5 | "LinkedTenant": "4U Inc."
6 | },
7 | {
8 | "Email": "westCoast@4U.com",
9 | "RolesCommaDelimited": "AreaManager,CacheRole",
10 | "LinkedTenant": "West Coast"
11 | },
12 | {
13 | "Email": "eastCoast@4U.com",
14 | "RolesCommaDelimited": "AreaManager,CacheRole",
15 | "LinkedTenant": "East Coast"
16 | },
17 | {
18 | "Email": "*Boss@4U.com",
19 | "RolesCommaDelimited": "StoreManager,CacheRole",
20 | "LinkedTenant": "*4U"
21 | },
22 | {
23 | "Email": "*Sales@4U.com",
24 | "RolesCommaDelimited": "SalesAssistant,CacheRole",
25 | "LinkedTenant": "*4U"
26 | },
27 | //Pets2
28 | {
29 | "Email": "dir@Pets2.com",
30 | "RolesCommaDelimited": "Director,CacheRole",
31 | "LinkedTenant": "Pets2 Ltd."
32 | },
33 | {
34 | "Email": "london@Pets2.com",
35 | "RolesCommaDelimited": "AreaManager,CacheRole",
36 | "LinkedTenant": "London"
37 | },
38 | {
39 | "Email": "bristol@Pets2.com",
40 | "RolesCommaDelimited": "AreaManager,CacheRole",
41 | "LinkedTenant": "Bristol"
42 | },
43 | {
44 | "Email": "*Sales@Pets2.com",
45 | "RolesCommaDelimited": "SalesAssistant,CacheRole",
46 | "LinkedTenant": "*Place"
47 | }
48 | ]
49 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/css/site.css:
--------------------------------------------------------------------------------
1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | for details on configuring this project to bundle and minify static web assets. */
3 |
4 | a.navbar-brand {
5 | white-space: normal;
6 | text-align: center;
7 | word-break: break-all;
8 | }
9 |
10 | /* Sticky footer styles
11 | -------------------------------------------------- */
12 | html {
13 | font-size: 14px;
14 | }
15 | @media (min-width: 768px) {
16 | html {
17 | font-size: 16px;
18 | }
19 | }
20 |
21 | .border-top {
22 | border-top: 1px solid #e5e5e5;
23 | }
24 | .border-bottom {
25 | border-bottom: 1px solid #e5e5e5;
26 | }
27 |
28 | .box-shadow {
29 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
30 | }
31 |
32 | button.accept-policy {
33 | font-size: 1rem;
34 | line-height: inherit;
35 | }
36 |
37 | /* Sticky footer styles
38 | -------------------------------------------------- */
39 | html {
40 | position: relative;
41 | min-height: 100%;
42 | }
43 |
44 | body {
45 | /* Margin bottom by footer height */
46 | margin-bottom: 60px;
47 | }
48 | .footer {
49 | position: absolute;
50 | bottom: 0;
51 | width: 100%;
52 | white-space: nowrap;
53 | /* Set the fixed height of the footer here */
54 | height: 60px;
55 | line-height: 60px; /* Vertically center the text there */
56 | }
57 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JonPSmith/PermissionAccessControl2/c858a932c6c08e94ad36667bca1262ec889c241e/PermissionAccessControl2/wwwroot/favicon.ico
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/js/site.js:
--------------------------------------------------------------------------------
1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
2 | // for details on configuring this project to bundle and minify static web assets.
3 |
4 | // Write your JavaScript code.
5 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/lib/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2018 Twitter, Inc.
4 | Copyright (c) 2011-2018 The Bootstrap Authors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) .NET Foundation. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use
4 | these files except in compliance with the License. You may obtain a copy of the
5 | License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software distributed
10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the
12 | specific language governing permissions and limitations under the License.
13 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/lib/jquery-validation/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | =====================
3 |
4 | Copyright Jörn Zaefferer
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/PermissionAccessControl2/wwwroot/lib/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright JS Foundation and other contributors, https://js.foundation/
2 |
3 | This software consists of voluntary contributions made by many
4 | individuals. For exact contribution history, see the revision history
5 | available at https://github.com/jquery/jquery
6 |
7 | The following license applies to all parts of this software except as
8 | documented below:
9 |
10 | ====
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining
13 | a copy of this software and associated documentation files (the
14 | "Software"), to deal in the Software without restriction, including
15 | without limitation the rights to use, copy, modify, merge, publish,
16 | distribute, sublicense, and/or sell copies of the Software, and to
17 | permit persons to whom the Software is furnished to do so, subject to
18 | the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be
21 | included in all copies or substantial portions of the Software.
22 |
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 |
31 | ====
32 |
33 | All files located in the node_modules and external directories are
34 | externally maintained libraries used by this software which have their
35 | own licenses; we recommend you read them, as their terms may differ from
36 | the terms above.
37 |
--------------------------------------------------------------------------------
/PermissionParts/LinkedToModuleAttribute.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 |
6 | namespace PermissionParts
7 | {
8 | [AttributeUsage(AttributeTargets.Field)]
9 | public class LinkedToModuleAttribute : Attribute
10 | {
11 | public PaidForModules PaidForModule { get; private set; }
12 |
13 | public LinkedToModuleAttribute(PaidForModules paidForModule)
14 | {
15 | PaidForModule = paidForModule;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/PermissionParts/PaidForModules.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 |
6 | namespace PermissionParts
7 | {
8 | ///
9 | /// This is an example of how you would manage what optional parts of your system a user can access
10 | /// NOTE: You can add Display attributes (as done on Permissions) to give more information about a module
11 | ///
12 | [Flags]
13 | public enum PaidForModules : long
14 | {
15 | None = 0,
16 | Feature1 = 1,
17 | Feature2 = 2,
18 | Feature3 = 4
19 | }
20 | }
--------------------------------------------------------------------------------
/PermissionParts/PermissionChecker.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.ComponentModel;
7 | using System.Linq;
8 |
9 | namespace PermissionParts
10 | {
11 | public static class PermissionChecker
12 | {
13 | ///
14 | /// This is used by the policy provider to check the permission name string
15 | ///
16 | ///
17 | ///
18 | ///
19 | public static bool ThisPermissionIsAllowed(this string packedPermissions, string permissionName)
20 | {
21 | var usersPermissions = packedPermissions.UnpackPermissionsFromString().ToArray();
22 |
23 | if (!Enum.TryParse(permissionName, true, out Permissions permissionToCheck))
24 | throw new InvalidEnumArgumentException($"{permissionName} could not be converted to a {nameof(Permissions)}.");
25 |
26 | return usersPermissions.UserHasThisPermission(permissionToCheck);
27 | }
28 |
29 | ///
30 | /// This is the main checker of whether a user permissions allows them to access something with the given permission
31 | ///
32 | ///
33 | ///
34 | ///
35 | public static bool UserHasThisPermission(this Permissions[] usersPermissions, Permissions permissionToCheck)
36 | {
37 | return usersPermissions.Contains(permissionToCheck) || usersPermissions.Contains(Permissions.AccessAll);
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/PermissionParts/PermissionDisplay.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.ComponentModel.DataAnnotations;
7 | using System.Reflection;
8 |
9 | namespace PermissionParts
10 | {
11 | public class PermissionDisplay
12 | {
13 | public PermissionDisplay(string groupName, string name, string description, Permissions permission,
14 | string moduleName)
15 | {
16 | Permission = permission;
17 | GroupName = groupName;
18 | ShortName = name ?? throw new ArgumentNullException(nameof(name));
19 | Description = description ?? throw new ArgumentNullException(nameof(description));
20 | ModuleName = moduleName;
21 | }
22 |
23 | ///
24 | /// GroupName, which groups permissions working in the same area
25 | ///
26 | public string GroupName { get; private set; }
27 | ///
28 | /// ShortName of the permission - often says what it does, e.g. Read
29 | ///
30 | public string ShortName { get; private set; }
31 | ///
32 | /// Long description of what action this permission allows
33 | ///
34 | public string Description { get; private set; }
35 | ///
36 | /// Gives the actual permission
37 | ///
38 | public Permissions Permission { get; private set; }
39 | ///
40 | /// Contains an optional paidForModule that this feature is linked to
41 | ///
42 | public string ModuleName { get; private set; }
43 |
44 |
45 | ///
46 | /// This returns
47 | ///
48 | ///
49 | public static List GetPermissionsToDisplay(Type enumType)
50 | {
51 | var result = new List();
52 | foreach (var permissionName in Enum.GetNames(enumType))
53 | {
54 | var member = enumType.GetMember(permissionName);
55 | //This allows you to obsolete a permission and it won't be shown as a possible option, but is still there so you won't reuse the number
56 | var obsoleteAttribute = member[0].GetCustomAttribute();
57 | if (obsoleteAttribute != null)
58 | continue;
59 | //If there is no DisplayAttribute then the Enum is not used
60 | var displayAttribute = member[0].GetCustomAttribute();
61 | if (displayAttribute == null)
62 | continue;
63 |
64 | //Gets the optional PaidForModule that a permission can be linked to
65 | var moduleAttribute = member[0].GetCustomAttribute();
66 |
67 | var permission = (Permissions)Enum.Parse(enumType, permissionName, false);
68 |
69 | result.Add(new PermissionDisplay(displayAttribute.GroupName, displayAttribute.Name,
70 | displayAttribute.Description, permission, moduleAttribute?.PaidForModule.ToString()));
71 | }
72 |
73 | return result;
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/PermissionParts/PermissionPackers.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Globalization;
7 | using System.Linq;
8 |
9 | namespace PermissionParts
10 | {
11 | public static class PermissionPackers
12 | {
13 | public static string PackPermissionsIntoString(this IEnumerable permissions)
14 | {
15 | return permissions.Aggregate("", (s, permission) => s + (char)permission);
16 | }
17 |
18 | public static IEnumerable UnpackPermissionsFromString(this string packedPermissions)
19 | {
20 | if (packedPermissions == null)
21 | throw new ArgumentNullException(nameof(packedPermissions));
22 | foreach (var character in packedPermissions)
23 | {
24 | yield return ((Permissions) character);
25 | }
26 | }
27 |
28 | public static Permissions? FindPermissionViaName(this string permissionName)
29 | {
30 | return Enum.TryParse(permissionName, out Permissions permission)
31 | ? (Permissions?) permission
32 | : null;
33 | }
34 |
35 | }
36 | }
--------------------------------------------------------------------------------
/PermissionParts/PermissionParts.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/PermissionParts/Permissions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.ComponentModel.DataAnnotations;
6 |
7 | namespace PermissionParts
8 | {
9 | public enum Permissions : short
10 | {
11 | NotSet = 0, //error condition
12 |
13 | //Here is an example of very detailed control over something
14 | [Display(GroupName = "Stock", Name = "Read", Description = "Can read stock")]
15 | StockRead = 10,
16 | [Display(GroupName = "Stock", Name = "Add new", Description = "Can add a new stock item")]
17 | StockAddNew = 13,
18 | [Display(GroupName = "Stock", Name = "Remove", Description = "Can read sales data")]
19 | StockRemove = 14,
20 |
21 | [Display(GroupName = "Sales", Name = "Read", Description = "Can delete a stock item")]
22 | SalesRead = 20,
23 | [Display(GroupName = "Sales", Name = "Sell", Description = "Can sell items from stock")]
24 | SalesSell = 21,
25 | [Display(GroupName = "Sales", Name = "Return", Description = "Can return an item to stock")]
26 | SalesReturn = 22,
27 |
28 | [Display(GroupName = "Employees", Name = "Read", Description = "Can read company employees")]
29 | EmployeeRead = 30,
30 |
31 | [Display(GroupName = "UserAdmin", Name = "Read users", Description = "Can list User")]
32 | UserRead = 40,
33 | //This is an example of grouping multiple actions under one permission
34 | [Display(GroupName = "UserAdmin", Name = "Alter user", Description = "Can do anything to the User")]
35 | UserChange = 41,
36 |
37 | [Display(GroupName = "UserAdmin", Name = "Read Roles", Description = "Can list Role")]
38 | RoleRead = 50,
39 | [Display(GroupName = "UserAdmin", Name = "Change Role", Description = "Can create, update or delete a Role")]
40 | RoleChange = 51,
41 |
42 | [Display(GroupName = "CacheTest", Name = "Cache1", Description = "Base permission to update permission test")]
43 | Cache1 = 60,
44 | [Display(GroupName = "CacheTest", Name = "Cache2", Description = "Permission to toggle for update permission test")]
45 | Cache2 = 61,
46 |
47 | [Display(GroupName = "Impersonation", Name = "Impersonate - straight", Description = "Impersonate user using their permissions")]
48 | Impersonate = 70,
49 | [Display(GroupName = "Impersonation", Name = "Impersonate - enhanced", Description = "Impersonate user using current permissions")]
50 | ImpersonateKeepOwnPermissions = 71,
51 |
52 | //This is an example of what to do with permission you don't used anymore.
53 | //You don't want its number to be reused as it could cause problems
54 | //Just mark it as obsolete and the PermissionDisplay code won't show it
55 | [Obsolete]
56 | [Display(GroupName = "Old", Name = "Not used", Description = "example of old permission")]
57 | OldPermissionNotUsed = 100,
58 |
59 | //This is an example of a permission linked to a optional (paid for?) feature
60 | //The code that turns roles to permissions can
61 | //remove this permission if the user isn't allowed to access this feature
62 | [LinkedToModule(PaidForModules.Feature1)]
63 | [Display(GroupName = "Features", Name = "Feature1", Description = "Can access feature1")]
64 | Feature1Access = 1000,
65 | [LinkedToModule(PaidForModules.Feature2)]
66 | [Display(GroupName = "Features", Name = "Feature2", Description = "Can access feature2")]
67 | Feature2Access = 1001,
68 |
69 | [Display(GroupName = "SuperAdmin", Name = "AccessAll", Description = "This allows the user to access every feature")]
70 | AccessAll = Int16.MaxValue,
71 | }
72 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JonPSmith/PermissionAccessControl2/c858a932c6c08e94ad36667bca1262ec889c241e/README.md
--------------------------------------------------------------------------------
/RefreshClaimsParts/AuthChanges.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Runtime.CompilerServices;
6 |
7 | [assembly: InternalsVisibleTo("Test")]
8 |
9 | namespace RefreshClaimsParts
10 | {
11 | public class AuthChanges : IAuthChanges
12 | {
13 | ///
14 | /// This returns true if ticksToCompareString is null, or if its value is lower than the value in the TimeStore
15 | ///
16 | ///
17 | ///
18 | ///
19 | ///
20 | public bool IsOutOfDateOrMissing(string cacheKey, string ticksToCompareString, ITimeStore timeStore)
21 | {
22 | if (ticksToCompareString == null)
23 | //if there is no time claim then you do need to reset the claims
24 | return true;
25 |
26 | var ticksToCompare = long.Parse(ticksToCompareString);
27 | return IsOutOfDate(cacheKey, ticksToCompare, timeStore);
28 | }
29 |
30 | private bool IsOutOfDate(string cacheKey, long ticksToCompare, ITimeStore timeStore)
31 | {
32 | var cachedTicks = timeStore.GetValueFromStore(cacheKey);
33 | if (cachedTicks == null)
34 | throw new ApplicationException(
35 | $"You must seed the database with a cache value for the key {cacheKey}.");
36 |
37 | return ticksToCompare < cachedTicks;
38 | }
39 |
40 | public void AddOrUpdate(ITimeStore timeStore)
41 | {
42 | timeStore.AddUpdateValue(AuthChangesConsts.FeatureCacheKey, DateTime.UtcNow.Ticks);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/RefreshClaimsParts/AuthChangesConsts.cs:
--------------------------------------------------------------------------------
1 | namespace RefreshClaimsParts
2 | {
3 | public static class AuthChangesConsts
4 | {
5 | public const int CacheKeyMaxSize = 36;
6 |
7 | public const string FeatureCacheKey = "8FA8E7A8-0ADD-433C-B063-3BD725254C9B";
8 | }
9 | }
--------------------------------------------------------------------------------
/RefreshClaimsParts/IAuthChanges.cs:
--------------------------------------------------------------------------------
1 | namespace RefreshClaimsParts
2 | {
3 | public interface IAuthChanges
4 | {
5 | ///
6 | /// This returns true if there is no ticksToCompare or the ticksToCompare is earlier than the AuthLastUpdated time
7 | ///
8 | ///
9 | ///
10 | /// Link to the DbContext that managers the cache store
11 | ///
12 | bool IsOutOfDateOrMissing(string cacheKey, string ticksToCompareString, ITimeStore timeStore);
13 |
14 | ///
15 | /// This adds or updates the TimeStore entry with the cacheKey with the cachedValue (datetime as ticks)
16 | ///
17 | ///
18 | void AddOrUpdate(ITimeStore timeStore);
19 | }
20 | }
--------------------------------------------------------------------------------
/RefreshClaimsParts/ITimeStore.cs:
--------------------------------------------------------------------------------
1 | namespace RefreshClaimsParts
2 | {
3 | ///
4 | /// Access to the TimeStore part of the ExtraAuthorizeDbContext
5 | ///
6 | public interface ITimeStore
7 | {
8 | ///
9 | /// This reads the TimeStore entry with the given key.
10 | ///
11 | /// the cache key
12 | /// DateTime ticks value, or null if not set.
13 | long? GetValueFromStore(string key);
14 |
15 | ///
16 | /// This adds or updates the TimeStore entry defined by the key
17 | ///
18 | /// the cache key
19 | /// the new DateTime ticks value
20 | void AddUpdateValue(string key, long ticks);
21 | }
22 | }
--------------------------------------------------------------------------------
/RefreshClaimsParts/RefreshClaimsParts.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ServiceLayer/AppStart/StartupExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Reflection;
5 | using Microsoft.Extensions.DependencyInjection;
6 | using NetCore.AutoRegisterDi;
7 |
8 | namespace ServiceLayer.AppStart
9 | {
10 | public static class StartupExtensions
11 | {
12 | public static void ServiceLayerRegister(this IServiceCollection services)
13 | {
14 | //This registers the classes in the current assembly that end in "Service" and have a public interface
15 | services.RegisterAssemblyPublicNonGenericClasses(Assembly.GetExecutingAssembly())
16 | .Where(c => c.Name.EndsWith("Service"))
17 | .AsPublicImplementedInterfaces();
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/ServiceLayer/CompanyServices/Concrete/ListCompaniesService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using DataLayer.EfCode;
8 | using DataLayer.MultiTenantClasses;
9 | using Microsoft.AspNetCore.Html;
10 | using Microsoft.EntityFrameworkCore;
11 |
12 | namespace ServiceLayer.CompanyServices.Concrete
13 | {
14 | ///
15 | /// This displays a list of the Company hierarchies in a simple way
16 | /// ONLY USED FOR DEMO
17 | ///
18 | public class ListCompaniesService : IListCompaniesService
19 | {
20 | private readonly CompanyDbContext _context;
21 |
22 | public ListCompaniesService(CompanyDbContext context)
23 | {
24 | _context = context;
25 | }
26 |
27 | public List BuildViewOfHierarchy()
28 | {
29 | var result = new List();
30 | foreach (var company in _context.Tenants.IgnoreQueryFilters().OfType()
31 | .Include(x => x.Children)
32 | .ThenInclude(x => x.Children)
33 | .ThenInclude(x => x.Children)
34 | .ThenInclude(x => x.Children)
35 | .ThenInclude(x => x.Children))
36 | {
37 | var sb = new StringBuilder("");
38 | sb.Append(HtmlDisplayTenant(company, 0));
39 | ShowChildren(company.Children, sb, 1);
40 | sb.Append("
");
41 | result.Add(new HtmlString(sb.ToString()));
42 | }
43 |
44 | return result;
45 | }
46 |
47 | private void ShowChildren(IEnumerable children, StringBuilder sb, int indent)
48 | {
49 | foreach (var tenant in children)
50 | {
51 | sb.Append(HtmlDisplayTenant(tenant, indent));
52 | if (tenant.Children.Any())
53 | ShowChildren(tenant.Children, sb, indent+1);
54 | }
55 | }
56 |
57 | private string HtmlDisplayTenant(TenantBase tenant, int indent)
58 | {
59 | var result = "";
60 | for (int i = 0; i < indent; i++)
61 | {
62 | result += " ";
63 | }
64 | result += $"{tenant.GetType().Name} " +
65 | $"{tenant.Name} " +
66 | $"DataKey = {tenant.DataKey} " +
67 | " ";
68 |
69 | return result;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/ServiceLayer/CompanyServices/IListCompaniesService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using Microsoft.AspNetCore.Html;
6 |
7 | namespace ServiceLayer.CompanyServices
8 | {
9 | public interface IListCompaniesService
10 | {
11 | List BuildViewOfHierarchy();
12 | }
13 | }
--------------------------------------------------------------------------------
/ServiceLayer/ServiceLayer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/ServiceLayer/Shop/ListSalesDto.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using DataLayer.MultiTenantClasses;
5 | using GenericServices;
6 |
7 | namespace ServiceLayer.Shop
8 | {
9 | public class ListSalesDto : ILinkToEntity
10 | {
11 | public int ShopSaleId { get; set; }
12 |
13 | public string StockItemName { get; set; }
14 |
15 | public decimal StockItemRetailPrice { get; set; }
16 |
17 | ///
18 | /// positive number for sale, negative number for return
19 | ///
20 | public int NumSoldReturned { get; set; }
21 |
22 | ///
23 | /// Will be null if sale
24 | ///
25 | public string ReturnReason { get; set; }
26 |
27 | public string StockItemShopName { get; set; }
28 | }
29 | }
--------------------------------------------------------------------------------
/ServiceLayer/Shop/ListStockDto.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using DataLayer.MultiTenantClasses;
5 | using GenericServices;
6 |
7 | namespace ServiceLayer.Shop
8 | {
9 | public class ListStockDto : ILinkToEntity
10 | {
11 | public int ShopStockId { get; set; }
12 | public string Name { get; set; }
13 | public decimal RetailPrice { get; set; }
14 | public int NumInStock { get; set; }
15 |
16 | public string ShopName { get; set; }
17 | }
18 | }
--------------------------------------------------------------------------------
/ServiceLayer/Shop/SellItemDto.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using DataLayer.MultiTenantClasses;
7 | using GenericServices;
8 |
9 | namespace ServiceLayer.Shop
10 | {
11 | public class SellItemDto : ILinkToEntity
12 | {
13 | public List DropdownData { get; set; }
14 |
15 | public int ShopStockId { get; set; }
16 |
17 | public int NumBought { get; set; } = 1;
18 |
19 | public int TenantItemId { get; set; }
20 |
21 | ///
22 | /// This holds the PK of the created ShopSale
23 | ///
24 | public int ShopSaleId { get; set; }
25 |
26 | public void SetResetDto(List stockList)
27 | {
28 | DropdownData = stockList;
29 | TenantItemId = stockList.FirstOrDefault()?.TenantItemId ?? 0;
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/ServiceLayer/Shop/StockSelectDto.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using DataLayer.MultiTenantClasses;
5 | using GenericServices;
6 |
7 | namespace ServiceLayer.Shop
8 | {
9 | public class StockSelectDto : ILinkToEntity
10 | {
11 | public int ShopStockId { get; set; }
12 | public string Name { get; set; }
13 | public decimal RetailPrice { get; set; }
14 | public int NumInStock { get; set; }
15 |
16 | public int TenantItemId { get; set; }
17 |
18 | public string DisplayText => $"{Name}, {RetailPrice:C} ({NumInStock} left)";
19 | }
20 | }
--------------------------------------------------------------------------------
/ServiceLayer/UserServices/Concrete/CacheRoleService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Security.Claims;
5 | using DataLayer.EfCode;
6 | using DataLayer.ExtraAuthClasses;
7 | using FeatureAuthorize;
8 | using PermissionParts;
9 | using RefreshClaimsParts;
10 |
11 | namespace ServiceLayer.UserServices.Concrete
12 | {
13 | ///
14 | /// This is used to test that the dynamic update of a logged-in user happens if the UpdateCookieOnChange is true.
15 | /// It does this by changing the permissions in the Role called ,
16 | /// which always has the permission , and can have permission
17 | ///
18 | public class CacheRoleService : ICacheRoleService
19 | {
20 | //NOTE: If you change this you need to change the Roles.txt file in wwwroot/SeedData
21 | public const string CacheRoleName = "CacheRole";
22 |
23 | private readonly ExtraAuthorizeDbContext _context;
24 |
25 | public CacheRoleService(ExtraAuthorizeDbContext context)
26 | {
27 | _context = context;
28 | }
29 |
30 | ///
31 | /// This just shows the user's permissions which start with the string "Cache"
32 | ///
33 | ///
34 | ///
35 | public IEnumerable ShowExistingCachePermissions(IEnumerable usersClaims)
36 | {
37 | return usersClaims.PermissionsFromClaims()?.Where(x => x.ToString().StartsWith("Cache"));
38 | }
39 |
40 | ///
41 | /// This toggles whether the permission is in the .
42 | /// This causes the to update the TimeStore with the time this change happens.
43 | /// Then the will compare the users lastUpdated time which will
44 | /// cause a recalc of the logged-in user's permission claim.
45 | ///
46 | public void ToggleCacheRole()
47 | {
48 | var hasCache2Permission = _context.Find(CacheRoleName)
49 | .PermissionsInRole.Any(x => x == Permissions.Cache2);
50 | var updatedPermissions = new List {Permissions.Cache1};
51 | if (!hasCache2Permission)
52 | updatedPermissions.Add(Permissions.Cache2);
53 |
54 | var authUserHelper = new ExtraAuthUsersSetup(_context);
55 | authUserHelper.UpdateRole(CacheRoleName, $"Has {updatedPermissions.Count} permissions.", updatedPermissions);
56 | _context.SaveChanges();
57 | }
58 |
59 | ///
60 | /// This allows us to see the time the last times
61 | /// 1. The last time the Role/Permissions were changes (database value)
62 | /// 2. The last time the current user's permissions were calculated (user claim)
63 | ///
64 | ///
65 | ///
66 | public IEnumerable GetFeatureLastUpdated(IEnumerable usersClaims)
67 | {
68 | var databaseValue = _context.Find(AuthChangesConsts.FeatureCacheKey)?.LastUpdatedTicks;
69 | yield return databaseValue == null
70 | ? "No database value present"
71 | : $"Database: {new DateTime((long)databaseValue):F}";
72 |
73 | var claimsValue = usersClaims
74 | .SingleOrDefault(x => x.Type == PermissionConstants.LastPermissionsUpdatedClaimType)?.Value;
75 | yield return claimsValue == null
76 | ? "No claim value present"
77 | : $"User Claim: {new DateTime(long.Parse(claimsValue)):F}";
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/ServiceLayer/UserServices/Concrete/ListUsersService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using DataLayer.EfCode;
7 | using DataLayer.ExtraAuthClasses;
8 | using DataLayer.MultiTenantClasses;
9 | using Microsoft.AspNetCore.Identity;
10 |
11 | namespace ServiceLayer.UserServices.Concrete
12 | {
13 | public class ListUsersService : IListUsersService
14 | {
15 | private readonly UserManager _userManager;
16 | private readonly ExtraAuthorizeDbContext _extraContext;
17 |
18 | public ListUsersService(UserManager userManager, ExtraAuthorizeDbContext extraContext)
19 | {
20 | _userManager = userManager;
21 | _extraContext = extraContext;
22 | }
23 |
24 | public List ListUserWithRolesAndDataTenant()
25 | {
26 | var result = new List();
27 | foreach (var user in _userManager.Users)
28 | {
29 | var userRoleNames = _extraContext.UserToRoles.Where(x => x.UserId == user.Id).Select(x => x.RoleName);
30 | var dataEntry = _extraContext.Find(user.Id);
31 | string tenantName = "no linked tenant";
32 | string companyName = null;
33 | if (dataEntry != null)
34 | {
35 | var linkedTenant = _extraContext.Find(dataEntry.LinkedTenantId);
36 | tenantName = linkedTenant?.Name ?? "tenant not found";
37 | if (linkedTenant != null)
38 | companyName = _extraContext.Find(linkedTenant.ExtractCompanyId())?.Name;
39 | }
40 |
41 | result.Add(new ListUsersDto(user.Id, user.UserName, string.Join(", ", userRoleNames), companyName, tenantName));
42 | }
43 |
44 | return result;
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/ServiceLayer/UserServices/ICacheRoleService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Security.Claims;
3 | using PermissionParts;
4 |
5 | namespace ServiceLayer.UserServices
6 | {
7 | public interface ICacheRoleService
8 | {
9 | IEnumerable ShowExistingCachePermissions(IEnumerable usersClaims);
10 | void ToggleCacheRole();
11 | IEnumerable GetFeatureLastUpdated(IEnumerable usersClaims);
12 | }
13 | }
--------------------------------------------------------------------------------
/ServiceLayer/UserServices/IListUsersService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 |
6 | namespace ServiceLayer.UserServices
7 | {
8 | public interface IListUsersService
9 | {
10 | List ListUserWithRolesAndDataTenant();
11 | }
12 | }
--------------------------------------------------------------------------------
/ServiceLayer/UserServices/ListUsersDto.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 |
6 | namespace ServiceLayer.UserServices
7 | {
8 | public class ListUsersDto
9 | {
10 | public ListUsersDto(string userId, string userName, string roleNames, string companyName, string linkedTenantName)
11 | {
12 | UserId = userId;
13 | UserName = userName ?? throw new ArgumentNullException(nameof(userName));
14 | RoleNames = roleNames ?? throw new ArgumentNullException(nameof(roleNames));
15 | CompanyName = companyName;
16 | LinkedTenantName = linkedTenantName ?? throw new ArgumentNullException(nameof(linkedTenantName));
17 | }
18 |
19 | public string UserId { get; }
20 | public string UserName { get; }
21 | public string RoleNames { get; }
22 | public string CompanyName { get; }
23 | public string LinkedTenantName { get; }
24 | }
25 | }
--------------------------------------------------------------------------------
/ServiceLayer/UserServices/UserExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Security.Claims;
4 | using FeatureAuthorize;
5 | using PermissionParts;
6 |
7 | namespace ServiceLayer.UserServices
8 | {
9 | public static class UserExtensions
10 | {
11 | ///
12 | /// This gets the permissions for the currently logged in user (or null if no claim)
13 | ///
14 | ///
15 | ///
16 | public static IEnumerable PermissionsFromClaims(this IEnumerable usersClaims)
17 | {
18 | var permissionsClaim =
19 | usersClaims?.SingleOrDefault(c => c.Type == PermissionConstants.PackedPermissionClaimType);
20 | return permissionsClaim?.Value.UnpackPermissionsFromString();
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/Test/DiConfigHelpers/MockHostingEnvironment.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2021 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.Extensions.FileProviders;
6 |
7 | namespace Test.DiConfigHelpers
8 | {
9 | internal class MockHostingEnvironment : IWebHostEnvironment
10 | {
11 | public string EnvironmentName { get; set; } = "Development";
12 |
13 | public string ApplicationName { get; set; }
14 |
15 | public string WebRootPath { get; set; }
16 |
17 | public IFileProvider WebRootFileProvider { get; set; }
18 |
19 | public string ContentRootPath { get; set; }
20 |
21 | public IFileProvider ContentRootFileProvider { get; set; }
22 | }
23 | }
--------------------------------------------------------------------------------
/Test/EfHelpers/AuthRoleServiceHelpers.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Collections.Generic;
6 | using DataLayer.EfCode;
7 | using DataLayer.ExtraAuthClasses;
8 | using PermissionParts;
9 | using ServiceLayer.UserServices.Concrete;
10 | using Xunit.Extensions.AssertExtensions;
11 |
12 | namespace Test.EfHelpers
13 | {
14 | public static class AuthRoleServiceHelpers
15 | {
16 | public static void SeedUserWithDefaultPermissions(this ExtraAuthorizeDbContext context,
17 | PaidForModules modules = PaidForModules.None, string userId = "userId")
18 | {
19 | var defaultPermissions = new List {Permissions.StockRead, Permissions.Feature1Access};
20 |
21 | var roleStatus = RoleToPermissions.CreateRoleWithPermissions(
22 | "TestRole1", "TestRole1", defaultPermissions, context);
23 | roleStatus.IsValid.ShouldBeTrue(roleStatus.GetAllErrors());
24 | context.Add(roleStatus.Result);
25 |
26 | var moduleUser = new ModulesForUser(userId, modules);
27 | context.Add(moduleUser);
28 |
29 | var userStatus = UserToRole.AddRoleToUser(userId, "TestRole1", context);
30 | userStatus.IsValid.ShouldBeTrue(roleStatus.GetAllErrors());
31 | context.Add(userStatus.Result);
32 |
33 | context.SaveChanges();
34 | }
35 |
36 | public static void SeedUserWithTwoRoles(this ExtraAuthorizeDbContext context, string userId = "userId")
37 | {
38 | var userStatus = RoleToPermissions.CreateRoleWithPermissions(
39 | "TestRole1", "TestRole1", new List { Permissions.StockRead}, context);
40 | userStatus.IsValid.ShouldBeTrue(userStatus.GetAllErrors());
41 | context.Add(userStatus.Result);
42 | userStatus = RoleToPermissions.CreateRoleWithPermissions(
43 | "TestRole2", "TestRole1", new List { Permissions.SalesSell}, context);
44 | userStatus.IsValid.ShouldBeTrue(userStatus.GetAllErrors());
45 | context.Add(userStatus.Result);
46 |
47 | var roleStatus = UserToRole.AddRoleToUser(userId, "TestRole1", context);
48 | roleStatus.IsValid.ShouldBeTrue(userStatus.GetAllErrors());
49 | context.Add(roleStatus.Result);
50 | roleStatus = UserToRole.AddRoleToUser(userId, "TestRole2", context);
51 | roleStatus.IsValid.ShouldBeTrue(userStatus.GetAllErrors());
52 | context.Add(roleStatus.Result);
53 |
54 | context.SaveChanges();
55 | }
56 |
57 | public static void SeedCacheRole(this ExtraAuthorizeDbContext context, bool hasCache2, string userId = "userId")
58 | {
59 | var permissions = new List { Permissions.Cache1 };
60 | if (hasCache2)
61 | permissions.Add(Permissions.Cache2);
62 | var userStatus = RoleToPermissions.CreateRoleWithPermissions(
63 | CacheRoleService.CacheRoleName, CacheRoleService.CacheRoleName, permissions, context);
64 | userStatus.IsValid.ShouldBeTrue(userStatus.GetAllErrors());
65 | context.Add(userStatus.Result);
66 |
67 | context.SaveChanges();
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/Test/EfHelpers/CheckEntitiesHelpers.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Reflection;
6 | using DataKeyParts;
7 | using Microsoft.EntityFrameworkCore;
8 | using Microsoft.EntityFrameworkCore.Metadata;
9 |
10 | namespace Test.EfHelpers
11 | {
12 | public static class CheckEntitiesHelpers
13 | {
14 | public static IEnumerable CheckEntitiesHasAQueryFilter(this IEnumerable entityTypes)
15 | {
16 | foreach (var entityType in entityTypes)
17 | {
18 | if (entityType.GetQueryFilter() == null
19 | && entityType.BaseType == null //not a TPH subclass
20 | && entityType.ClrType.GetCustomAttribute() == null //not an owned type
21 | && entityType.ClrType.GetCustomAttribute() == null) //Not marked as global
22 | yield return $"The entity class {entityType.Name} does not have a query filter";
23 | }
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/Test/FakesAndMocks/CookieRequestHelpers.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2021 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using Microsoft.AspNetCore.DataProtection;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.AspNetCore.Http.Features;
7 | using Microsoft.Extensions.Primitives;
8 | using Microsoft.Net.Http.Headers;
9 |
10 | namespace Test.FakesAndMocks
11 | {
12 | public static class CookieRequestHelpers
13 | {
14 |
15 | public static void AddRequestCookie(this HttpContext httpContext, string key, string value)
16 | {
17 | httpContext.Request.Cookies = MockRequestCookieCollection(key, value);
18 | }
19 |
20 | //see https://stackoverflow.com/a/63132794/1434764
21 | public static IRequestCookieCollection MockRequestCookieCollection(string key, string value)
22 | {
23 | var requestFeature = new HttpRequestFeature();
24 | var featureCollection = new FeatureCollection();
25 |
26 | requestFeature.Headers = new HeaderDictionary();
27 | requestFeature.Headers.Add(HeaderNames.Cookie, new StringValues(key + "=" + value));
28 |
29 | featureCollection.Set(requestFeature);
30 |
31 | var cookiesFeature = new RequestCookiesFeature(featureCollection);
32 |
33 | return cookiesFeature.Cookies;
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/Test/FakesAndMocks/FakeAuthChanges.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using RefreshClaimsParts;
6 |
7 | namespace Test.FakesAndMocks
8 | {
9 | public class FakeAuthChanges : IAuthChanges
10 | {
11 | public long CachedValue { get; private set; } = -1;
12 | public bool CacheValueSet { get; private set; }
13 |
14 | public bool IsOutOfDateOrMissing(string cacheKey, string ticksToCompareString, ITimeStore timeStore)
15 | {
16 | throw new System.NotImplementedException();
17 | }
18 |
19 | public bool IsLowerThan(string cacheKey, long ticksToCompare)
20 | {
21 | throw new System.NotImplementedException();
22 | }
23 |
24 | public void AddOrUpdate(ITimeStore timeStore)
25 | {
26 | CacheValueSet = true;
27 | }
28 |
29 | public void Clear()
30 | {
31 | CachedValue = -1;
32 | CacheValueSet = false;
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/Test/FakesAndMocks/FakeGetClaimsProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using DataKeyParts;
5 |
6 | namespace Test.FakesAndMocks
7 | {
8 | public class FakeGetClaimsProvider : IGetClaimsProvider
9 | {
10 | public FakeGetClaimsProvider(string dataKey)
11 | {
12 | DataKey = dataKey;
13 | }
14 |
15 | public string DataKey { get; }
16 | }
17 | }
--------------------------------------------------------------------------------
/Test/FakesAndMocks/FakeTimeStore.cs:
--------------------------------------------------------------------------------
1 | using RefreshClaimsParts;
2 |
3 | namespace Test.FakesAndMocks
4 | {
5 | public class FakeTimeStore : ITimeStore
6 | {
7 | public FakeTimeStore(string key, long? value)
8 | {
9 | Key = key;
10 | Value = value;
11 | }
12 |
13 | public string Key { get; private set; }
14 | public long? Value { get; private set; }
15 |
16 | public long? GetValueFromStore(string key)
17 | {
18 | Key = key;
19 | return Value;
20 | }
21 |
22 | public void AddUpdateValue(string key, long value)
23 | {
24 | Key = key;
25 | Value = value;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Test/Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | PreserveNewest
19 |
20 |
21 | PreserveNewest
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 | PreserveNewest
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | all
44 | runtime; build; native; contentfiles; analyzers
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Test/TestData/SeedData/Companies.txt:
--------------------------------------------------------------------------------
1 | 4U Inc.|West Coast|LA Tie4U
2 | 4U Inc.|East Coast|NY Dress4U
3 | Pets2 Ltd.|Cats Place, Kitten Place
--------------------------------------------------------------------------------
/Test/TestData/SeedData/Roles.txt:
--------------------------------------------------------------------------------
1 | Director: EmployeeRead
2 | AreaManager: StockRead, SalesRead
3 | SalesAssistant: StockRead, SalesSell
4 | StoreManager: StockRead, StockAddNew, StockRemove, SalesRead, SalesSell, SalesReturn
5 | UserAdmin: UserRead, UserChange, RoleRead
--------------------------------------------------------------------------------
/Test/TestData/SeedData/ShopStock.txt:
--------------------------------------------------------------------------------
1 | LA Tie4U: Blue tie|15, Red tie|20, Green tie|10
2 | NY Dress4U: Modern dress|65, Nice dress|30
3 | Cats Place: Cat food (large)|40, Cat food (small)|10
4 | Kitten Place: Scratch pole|60, Play mouse|5, Cat food (small)|12
--------------------------------------------------------------------------------
/Test/TestData/SeedData/Users.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Email": "dir@4U.com",
4 | "RolesCommaDelimited": "Director",
5 | "LinkedTenant": "4U Inc."
6 | },
7 | {
8 | "Email": "westCoast@4U.com",
9 | "RolesCommaDelimited": "AreaManager",
10 | "LinkedTenant": "West Coast"
11 | },
12 | {
13 | "Email": "eastCoast@4U.com",
14 | "RolesCommaDelimited": "AreaManager",
15 | "LinkedTenant": "East Coast"
16 | },
17 | {
18 | "Email": "*Boss@4U.com",
19 | "RolesCommaDelimited": "StoreManager",
20 | "LinkedTenant": "*4U"
21 | },
22 | {
23 | "Email": "*Sales@4U.com",
24 | "RolesCommaDelimited": "SalesAssistant",
25 | "LinkedTenant": "*4U"
26 | },
27 | //Pets2
28 | {
29 | "Email": "dir@Pets2.com",
30 | "RolesCommaDelimited": "Director",
31 | "LinkedTenant": "Pets2 Ltd."
32 | },
33 | {
34 | "Email": "*Sales@Pets2.com",
35 | "RolesCommaDelimited": "SalesAssistant",
36 | "LinkedTenant": "*Place"
37 | }
38 | ]
39 |
--------------------------------------------------------------------------------
/Test/UnitCommands/DeleteAllUnitTestDatabases.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using TestSupport.Attributes;
5 | using TestSupport.EfHelpers;
6 | using Xunit.Abstractions;
7 |
8 | namespace Test.UnitCommands
9 | {
10 | public class DeleteAllUnitTestDatabases
11 | {
12 | private readonly ITestOutputHelper _output;
13 |
14 | public DeleteAllUnitTestDatabases(ITestOutputHelper output)
15 | {
16 | _output = output;
17 | }
18 |
19 | //Run this method to wipe ALL the test database for the current branch
20 | //You need to run it in debug mode - that stops it being run when you "run all" unit tests
21 | [RunnableInDebugOnly]
22 | public void DeleteAllTestDatabasesOk()
23 | {
24 | var numDeleted = DatabaseTidyHelper.DeleteAllUnitTestDatabases();
25 | _output.WriteLine( "This deleted {0} databases.", numDeleted);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Test/UnitTests/DataAuthorizeTests/TestHierarchicalFiltering.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using DataLayer.EfCode;
6 | using PermissionAccessControl2.SeedDemo.Internal;
7 | using Test.FakesAndMocks;
8 | using TestSupport.EfHelpers;
9 | using Xunit;
10 | using Xunit.Abstractions;
11 | using Xunit.Extensions.AssertExtensions;
12 |
13 | namespace Test.UnitTests.DataAuthorizeTests
14 | {
15 | public class TestHierarchicalFiltering
16 | {
17 | private readonly ITestOutputHelper _output;
18 |
19 | [Fact]
20 | public void TestCreateValidDatabaseOk()
21 | {
22 | //SETUP
23 | var options = SqliteInMemory.CreateOptions();
24 | using (var context = new CompanyDbContext(options, new FakeGetClaimsProvider("accessKey")))
25 | {
26 | //ATTEMPT
27 | context.Database.EnsureCreated();
28 |
29 | //VERIFY
30 | }
31 | }
32 |
33 | public TestHierarchicalFiltering(ITestOutputHelper output)
34 | {
35 | _output = output;
36 | }
37 |
38 | [Theory]
39 | [InlineData("1|", 10)]
40 | [InlineData("1|2|", 9)]
41 | [InlineData("1|2|3|", 4)]
42 | [InlineData("1|2|3|6*", 1)]
43 | public void TestFilterTenantsOk(string dataKey, int expectedCount)
44 | {
45 | //SETUP
46 | var options = SqliteInMemory.CreateOptions();
47 | using (var context = new CompanyDbContext(options, new FakeGetClaimsProvider(dataKey)))
48 | {
49 | context.Database.EnsureCreated();
50 | context.AddCompanyAndChildrenInDatabase();
51 |
52 | //ATTEMPT
53 | var tenants = context.Tenants.ToList();
54 |
55 | //VERIFY
56 | //foreach (var line in tenants)
57 | //{
58 | // _output.WriteLine($"\"{line}\",");
59 | //}
60 | tenants.Count.ShouldEqual(expectedCount);
61 | }
62 | }
63 |
64 |
65 | }
66 | }
--------------------------------------------------------------------------------
/Test/UnitTests/FeatureAuthorizeTests/TestAuthChanges.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using RefreshClaimsParts;
6 | using Test.FakesAndMocks;
7 | using Xunit;
8 | using Xunit.Extensions.AssertExtensions;
9 |
10 | namespace Test.UnitTests.FeatureAuthorizeTests
11 | {
12 | public class TestAuthChanges
13 | {
14 |
15 | [Theory]
16 | [InlineData("test", "123", true)]
17 | [InlineData("test", "1234", false)]
18 | [InlineData("test", null, true)]
19 | public void TestIsOutOfDateOrMissing(string key, string ticksToTry, bool expectedResult )
20 | {
21 | //SETUP
22 | var fakeTimeStore = new FakeTimeStore("test", 200);
23 | var authChange = new AuthChanges();
24 |
25 | //ATTEMPT
26 | var isOutOfDate = authChange.IsOutOfDateOrMissing(key, ticksToTry, fakeTimeStore);
27 |
28 | //VERIFY
29 | isOutOfDate.ShouldEqual(expectedResult);
30 | }
31 |
32 | [Fact]
33 | public void TestIsOutOfDateOrMissingNoOriginalValue()
34 | {
35 | //SETUP
36 | var fakeTimeStore = new FakeTimeStore("test", null);
37 | var authChange = new AuthChanges();
38 |
39 | //ATTEMPT
40 | var ex = Assert.Throws(() => authChange.IsOutOfDateOrMissing("test", "100", fakeTimeStore));
41 |
42 | //VERIFY
43 | ex.Message.ShouldStartWith("You must seed the database with a cache value for the key ");
44 | }
45 |
46 |
47 | [Fact]
48 | public void TestAddOrUpdateDatabaseUpdate()
49 | {
50 | //SETUP
51 | var fakeTimeStore = new FakeTimeStore("test", 200);
52 | var authChange = new AuthChanges();
53 |
54 | //ATTEMPT
55 | authChange.AddOrUpdate(fakeTimeStore);
56 |
57 | //VERIFY
58 | fakeTimeStore.Key.ShouldEqual(AuthChangesConsts.FeatureCacheKey);
59 | fakeTimeStore.Value.ShouldNotEqual((long)200);
60 | }
61 |
62 | [Fact]
63 | public void TestAddOrUpdateDatabaseAdd()
64 | {
65 | //SETUP
66 | var fakeTimeStore = new FakeTimeStore(AuthChangesConsts.FeatureCacheKey, 200);
67 | var authChange = new AuthChanges();
68 |
69 | //ATTEMPT
70 | authChange.AddOrUpdate(fakeTimeStore);
71 |
72 | //VERIFY
73 | fakeTimeStore.Key.ShouldEqual(AuthChangesConsts.FeatureCacheKey);
74 | fakeTimeStore.Value.ShouldNotEqual((long)200);
75 | }
76 |
77 | }
78 | }
--------------------------------------------------------------------------------
/Test/UnitTests/FeatureAuthorizeTests/TestCacheRoleService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Security.Claims;
8 | using DataLayer.EfCode;
9 | using DataLayer.ExtraAuthClasses;
10 | using FeatureAuthorize;
11 | using PermissionParts;
12 | using ServiceLayer.UserServices.Concrete;
13 | using Test.EfHelpers;
14 | using Test.FakesAndMocks;
15 | using TestSupport.EfHelpers;
16 | using Xunit;
17 | using Xunit.Extensions.AssertExtensions;
18 |
19 | namespace Test.UnitTests.FeatureAuthorizeTests
20 | {
21 | public class TestCacheRoleService
22 | {
23 | [Theory]
24 | [InlineData(true)]
25 | [InlineData(false)]
26 | public void TestSeedCacheRole(bool hasCache2)
27 | {
28 | //SETUP
29 | var fakeAuthChanges = new FakeAuthChanges();
30 | var options = SqliteInMemory.CreateOptions();
31 | using (var context = new ExtraAuthorizeDbContext(options, fakeAuthChanges))
32 | {
33 | context.Database.EnsureCreated();
34 |
35 | //ATTEMPT
36 | context.SeedCacheRole(hasCache2);
37 | }
38 | using (var context = new ExtraAuthorizeDbContext(options, fakeAuthChanges))
39 | {
40 | //VERIFY
41 | var role = context.RolesToPermissions.Single();
42 | role.RoleName.ShouldEqual(CacheRoleService.CacheRoleName);
43 | role.PermissionsInRole.Count().ShouldEqual(hasCache2 ? 2 : 1);
44 | }
45 | }
46 |
47 | [Fact]
48 | public void TestToggleCacheRole()
49 | {
50 | //SETUP
51 | var fakeAuthChanges = new FakeAuthChanges();
52 | var options = SqliteInMemory.CreateOptions();
53 | using (var context = new ExtraAuthorizeDbContext(options, fakeAuthChanges))
54 | {
55 | context.Database.EnsureCreated();
56 | context.SeedCacheRole(true);
57 |
58 | var cacheRoleService = new CacheRoleService(context);
59 | var claims = new List
60 | {
61 | new Claim(PermissionConstants.PackedPermissionClaimType,
62 | new List {Permissions.Cache1, Permissions.Cache2}.PackPermissionsIntoString())
63 | };
64 |
65 | //ATTEMPT
66 | cacheRoleService.ToggleCacheRole();
67 |
68 | //VERIFY
69 | var role = context.RolesToPermissions.Single();
70 | role.RoleName.ShouldEqual(CacheRoleService.CacheRoleName);
71 | role.PermissionsInRole.Count().ShouldEqual(1);
72 | fakeAuthChanges.CacheValueSet.ShouldBeTrue();
73 | }
74 | }
75 |
76 | }
77 | }
--------------------------------------------------------------------------------
/Test/UnitTests/FeatureAuthorizeTests/TestCalcAllowedPermissions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks;
6 | using DataLayer.EfCode;
7 | using FeatureAuthorize;
8 | using PermissionParts;
9 | using Test.EfHelpers;
10 | using Test.FakesAndMocks;
11 | using TestSupport.EfHelpers;
12 | using Xunit;
13 | using Xunit.Extensions.AssertExtensions;
14 |
15 | namespace Test.UnitTests.FeatureAuthorizeTests
16 | {
17 | public class TestCalcAllowedPermissions
18 | {
19 | [Fact]
20 | public async Task TestCalcPermissionsForUserAsync()
21 | {
22 | //SETUP
23 | var fakeAuthChanges = new FakeAuthChanges();
24 | var options = SqliteInMemory.CreateOptions();
25 | using (var context = new ExtraAuthorizeDbContext(options, fakeAuthChanges))
26 | {
27 | context.Database.EnsureCreated();
28 | context.SeedUserWithDefaultPermissions();
29 |
30 | var calc = new CalcAllowedPermissions(context);
31 | //ATTEMPT
32 | var packedP = await calc.CalcPermissionsForUserAsync("userId");
33 |
34 | //VERIFY
35 | packedP.UnpackPermissionsFromString().ShouldEqual(new List { Permissions.StockRead});
36 | }
37 | }
38 |
39 | [Fact]
40 | public async Task TestCalcPermissionsForUserAsyncWithModule()
41 | {
42 | //SETUP
43 | var fakeAuthChanges = new FakeAuthChanges();
44 | var options = SqliteInMemory.CreateOptions();
45 | using (var context = new ExtraAuthorizeDbContext(options, fakeAuthChanges))
46 | {
47 | context.Database.EnsureCreated();
48 | context.SeedUserWithDefaultPermissions(PaidForModules.Feature1);
49 |
50 | var calc = new CalcAllowedPermissions(context);
51 | //ATTEMPT
52 | var packedP = await calc.CalcPermissionsForUserAsync("userId");
53 |
54 | //VERIFY
55 | packedP.UnpackPermissionsFromString().ShouldEqual(new List { Permissions.StockRead, Permissions.Feature1Access });
56 | }
57 | }
58 |
59 | }
60 | }
--------------------------------------------------------------------------------
/Test/UnitTests/FeatureAuthorizeTests/TestPermissionsChangedDatabase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using DataLayer.EfCode;
8 | using DataLayer.ExtraAuthClasses;
9 | using Microsoft.EntityFrameworkCore;
10 | using PermissionParts;
11 | using RefreshClaimsParts;
12 | using Test.FakesAndMocks;
13 | using TestSupport.EfHelpers;
14 | using Xunit;
15 | using Xunit.Extensions.AssertExtensions;
16 |
17 | namespace Test.UnitTests.FeatureAuthorizeTests
18 | {
19 | public class TestPermissionsChangedDatabase
20 | {
21 | [Fact]
22 | public void TestAddRoleNotTrigger()
23 | {
24 | //SETUP
25 | var options = SqliteInMemory.CreateOptions();
26 | using (var context = new ExtraAuthorizeDbContext(options, new AuthChanges()))
27 | {
28 | context.Database.EnsureCreated();
29 |
30 | //ATTEMPT
31 | var rolToPer = RoleToPermissions.CreateRoleWithPermissions
32 | ("test", "test", new List { Permissions.AccessAll }, context).Result;
33 | context.Add(rolToPer);
34 | context.SaveChanges();
35 |
36 | //VERIFY
37 | context.TimeStores.Count().ShouldEqual(0);
38 | context.RolesToPermissions.Count().ShouldEqual(1);
39 | }
40 | }
41 |
42 | [Fact]
43 | public void TestUpdateRoleTrigger()
44 | {
45 | //SETUP
46 | var options = SqliteInMemory.CreateOptions();
47 | using (var context = new ExtraAuthorizeDbContext(options, new AuthChanges()))
48 | {
49 | context.Database.EnsureCreated();
50 | var rolToPer = RoleToPermissions.CreateRoleWithPermissions
51 | ("test", "test", new List { Permissions.AccessAll }, context).Result;
52 | context.Add(rolToPer);
53 | context.SaveChanges();
54 |
55 | //ATTEMPT
56 | rolToPer.Update("test", new List { Permissions.EmployeeRead });
57 | context.SaveChanges();
58 |
59 | //VERIFY
60 | context.TimeStores.Count().ShouldEqual(1);
61 | context.RolesToPermissions.Count().ShouldEqual(1);
62 | }
63 | }
64 |
65 | [Fact]
66 | public async Task TestAddRoleToUseTrigger()
67 | {
68 | //SETUP
69 | var options = SqliteInMemory.CreateOptions();
70 | using (var context = new ExtraAuthorizeDbContext(options, new AuthChanges()))
71 | {
72 | context.Database.EnsureCreated();
73 | var rolToPer = RoleToPermissions.CreateRoleWithPermissions
74 | ("test", "test", new List { Permissions.AccessAll }, context).Result;
75 | context.Add(rolToPer);
76 | context.SaveChanges();
77 |
78 | //ATTEMPT
79 | var userToRole = new UserToRole("test", rolToPer);
80 | context.Add(userToRole);
81 | await context.SaveChangesAsync();
82 |
83 | //VERIFY
84 | context.TimeStores.Count().ShouldEqual(1);
85 | context.UserToRoles.Count().ShouldEqual(1);
86 | }
87 | }
88 |
89 | }
90 | }
--------------------------------------------------------------------------------
/Test/UnitTests/FeatureAuthorizeTests/TestPermissionsChangedFakeDatabase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using DataLayer.EfCode;
8 | using DataLayer.ExtraAuthClasses;
9 | using PermissionParts;
10 | using Test.EfHelpers;
11 | using Test.FakesAndMocks;
12 | using TestSupport.EfHelpers;
13 | using Xunit;
14 | using Xunit.Extensions.AssertExtensions;
15 |
16 | namespace Test.UnitTests.FeatureAuthorizeTests
17 | {
18 | public class TestPermissionsChangedFakeDatabase
19 | {
20 | [Fact]
21 | public void TestAddRoleNotTrigger()
22 | {
23 | //SETUP
24 | var fakeAuthChanges = new FakeAuthChanges();
25 | var options = SqliteInMemory.CreateOptions();
26 | using (var context = new ExtraAuthorizeDbContext(options, fakeAuthChanges))
27 | {
28 | context.Database.EnsureCreated();
29 |
30 | //ATTEMPT
31 | var rolToPer = RoleToPermissions.CreateRoleWithPermissions
32 | ("test", "test", new List { Permissions.AccessAll }, context).Result;
33 | context.Add(rolToPer);
34 | context.SaveChanges();
35 |
36 | //VERIFY
37 | fakeAuthChanges.CacheValueSet.ShouldBeFalse();
38 | context.RolesToPermissions.Count().ShouldEqual(1);
39 | }
40 | }
41 |
42 | [Fact]
43 | public void TestUpdateRoleTrigger()
44 | {
45 | //SETUP
46 | var fakeAuthChanges = new FakeAuthChanges();
47 | var options = SqliteInMemory.CreateOptions();
48 | using (var context = new ExtraAuthorizeDbContext(options, fakeAuthChanges))
49 | {
50 | context.Database.EnsureCreated();
51 | var rolToPer = RoleToPermissions.CreateRoleWithPermissions
52 | ("test", "test", new List { Permissions.AccessAll }, context).Result;
53 | context.Add(rolToPer);
54 | context.SaveChanges();
55 |
56 | //ATTEMPT
57 | rolToPer.Update("updated", new List { Permissions.EmployeeRead });
58 | context.SaveChanges();
59 |
60 | //VERIFY
61 | fakeAuthChanges.CacheValueSet.ShouldBeTrue();
62 | context.RolesToPermissions.Count().ShouldEqual(1);
63 | }
64 | }
65 |
66 | [Fact]
67 | public async Task TestAddRoleToUseTrigger()
68 | {
69 | //SETUP
70 | var fakeAuthChanges = new FakeAuthChanges();
71 | var options = SqliteInMemory.CreateOptions();
72 | using (var context = new ExtraAuthorizeDbContext(options, fakeAuthChanges))
73 | {
74 | context.Database.EnsureCreated();
75 | var rolToPer = RoleToPermissions.CreateRoleWithPermissions
76 | ("test", "test", new List { Permissions.AccessAll }, context).Result;
77 | context.Add(rolToPer);
78 | context.SaveChanges();
79 | fakeAuthChanges.Clear();
80 |
81 | //ATTEMPT
82 | var userToRole = new UserToRole("test", rolToPer);
83 | context.Add(userToRole);
84 | await context.SaveChangesAsync();
85 |
86 | //VERIFY
87 | fakeAuthChanges.CacheValueSet.ShouldBeTrue();
88 | context.UserToRoles.Count().ShouldEqual(1);
89 | }
90 | }
91 |
92 |
93 |
94 | }
95 | }
--------------------------------------------------------------------------------
/Test/UnitTests/SecurityChecks/CheckEntitiesAreSecure.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using DataLayer.EfCode;
6 | using Test.EfHelpers;
7 | using Test.FakesAndMocks;
8 | using TestSupport.EfHelpers;
9 | using Xunit;
10 | using Xunit.Extensions.AssertExtensions;
11 |
12 | namespace Test.UnitTests.SecurityChecks
13 | {
14 | public class CheckEntitiesAreSecure
15 | {
16 | [Fact]
17 | public void CheckQueryFiltersAreAppliedToEntityClassesOk()
18 | {
19 | //SETUP
20 | var options = SqliteInMemory.CreateOptions();
21 | using (var context = new CompanyDbContext(options, new FakeGetClaimsProvider("accessKey")))
22 | {
23 | var entities = context.Model.GetEntityTypes().ToList();
24 |
25 | //ATTEMPT
26 | var queryFilterErrs = entities.CheckEntitiesHasAQueryFilter().ToList();
27 |
28 | //VERIFY
29 | queryFilterErrs.Any().ShouldBeFalse(string.Join('\n', queryFilterErrs));
30 | }
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/Test/UnitTests/SecurityChecks/CheckPermissions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Linq;
6 | using PermissionParts;
7 | using Xunit;
8 | using Xunit.Extensions.AssertExtensions;
9 |
10 | namespace Test.UnitTests.SecurityChecks
11 | {
12 | public class CheckPermissions
13 | {
14 | //Its VERY important that you don't have duplicate Permission numbers as it could cause a security breach
15 | [Fact]
16 | public void CheckPermissionsHaveUniqueNumberOk()
17 | {
18 | //SETUP
19 |
20 | //ATTEMPT
21 | var nums = Enum.GetValues(typeof(Permissions)).Cast().Select(x => (int)x).ToList();
22 |
23 | //VERIFY
24 | nums.Count.ShouldEqual(nums.Distinct().Count());
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/Test/UnitTests/ShopServices/TestShopServices.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using DataLayer.EfCode;
6 | using DataLayer.MultiTenantClasses;
7 | using GenericServices.PublicButHidden;
8 | using GenericServices.Setup;
9 | using Microsoft.EntityFrameworkCore;
10 | using PermissionParts;
11 | using ServiceLayer.Shop;
12 | using Test.FakesAndMocks;
13 | using TestSupport.EfHelpers;
14 | using Xunit;
15 | using Xunit.Extensions.AssertExtensions;
16 |
17 | namespace Test.UnitTests.ShopServices
18 | {
19 | public class TestShopServices
20 | {
21 |
22 |
23 | [Fact]
24 | public void TestQueryFilterWorksOnShopStock()
25 | {
26 | //SETUP
27 | var options = SqliteInMemory.CreateOptions();
28 | using (var context = new CompanyDbContext(options, new FakeGetClaimsProvider("accessKey*")))
29 | {
30 | context.Database.EnsureCreated();
31 | var company = Company.AddTenantToDatabaseWithSaveChanges("TestCompany", PaidForModules.None, context);
32 | var shop = RetailOutlet.AddTenantToDatabaseWithSaveChanges("TestShop", company, context);
33 | var stock = new ShopStock {Name = "dress", NumInStock = 5, Shop = shop};
34 | context.Add(stock);
35 | context.SaveChanges();
36 |
37 | var utData = context.SetupSingleDtoAndEntities();
38 | var service = new CrudServices(context, utData.ConfigAndMapper);
39 |
40 | //ATTEMPT
41 | var dto = new SellItemDto
42 | {
43 | TenantItemId = shop.TenantItemId,
44 | ShopStockId = stock.ShopStockId,
45 | NumBought = 1
46 | };
47 | var shopSale = service.CreateAndSave(dto);
48 |
49 | //VERIFY
50 | service.IsValid.ShouldBeTrue(service.GetAllErrors());
51 | context.ShopSales.Count().ShouldEqual(1);
52 | context.ShopStocks.Single().NumInStock.ShouldEqual(4);
53 | }
54 | }
55 |
56 |
57 | }
58 | }
--------------------------------------------------------------------------------
/Test/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "ConnectionStrings": {
3 | "UnitTestConnection": "Server=(localdb)\\mssqllocaldb;Database=PermissionAccessControl2-Test;Trusted_Connection=True;MultipleActiveResultSets=true"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/Test/demosettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "SuperAdmin": //This holds the information on the superuser. You must have one SuperUser setup otherwise you can't manage users
3 | {
4 | "Email": "Super@g1.com",
5 | "Password": "Super@g1.com"
6 | }
7 | }
--------------------------------------------------------------------------------
/UserImpersonation/AppStart/StartupExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using Microsoft.Extensions.DependencyInjection;
5 | using NetCore.AutoRegisterDi;
6 |
7 | namespace UserImpersonation.AppStart
8 | {
9 | public static class StartupExtensions
10 | {
11 | public static void UserImpersonationRegister(this IServiceCollection services)
12 | {
13 | //This registers the classes in the current assembly that end in "Service" and have a public interface
14 | services.RegisterAssemblyPublicNonGenericClasses()
15 | .Where(c => c.Name.EndsWith("Service"))
16 | .AsPublicImplementedInterfaces();
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/UserImpersonation/Concrete/AuthCookieSignout.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Threading.Tasks;
5 | using Microsoft.AspNetCore.Authentication.Cookies;
6 |
7 | namespace UserImpersonation.Concrete
8 | {
9 | public class AuthCookieSigningOut
10 | {
11 | ///
12 | /// This will ensure any impersonation cookie is deleted when a user signs out
13 | ///
14 | ///
15 | ///
16 | public Task SigningOutAsync(CookieSigningOutContext context)
17 | {
18 | var cookie = new ImpersonationCookie(context.HttpContext, null);
19 | cookie.Delete();
20 |
21 | return Task.CompletedTask;
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/UserImpersonation/Concrete/ImpersonationCookie.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 | using System.Runtime.CompilerServices;
6 | using Microsoft.AspNetCore.DataProtection;
7 | using Microsoft.AspNetCore.Http;
8 |
9 | [assembly: InternalsVisibleTo("Test")]
10 |
11 | namespace UserImpersonation.Concrete
12 | {
13 | public class ImpersonationCookie
14 | {
15 | private const string CookieName = "UserImpersonation";
16 |
17 | private readonly HttpContext _httpContext;
18 | private readonly IDataProtectionProvider _protectionProvider;
19 | private readonly CookieOptions _options;
20 |
21 | public string EncryptPurpose { get; private set; }
22 |
23 | public ImpersonationCookie(HttpContext httpContext, IDataProtectionProvider protectionProvider)
24 | {
25 | _httpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
26 | _protectionProvider = protectionProvider; //Can be null
27 | EncryptPurpose = "hffhegse432!&2!jbK!K3wqqqagg3bbassdewdsgfedgbfdewe13c";
28 | _options = new CookieOptions
29 | {
30 | Secure = false, //In real life you would want this to be true, but for this demo I allow http
31 | HttpOnly = true, //Not used by JavaScript
32 | IsEssential = true,
33 | //These two make it a session cookie, i.e. it disappears when the browser is closed
34 | Expires = null,
35 | MaxAge = null
36 | };
37 | }
38 |
39 | public void AddUpdateCookie(string data)
40 | {
41 | if (_protectionProvider == null)
42 | throw new NullReferenceException(
43 | $"The {nameof(IDataProtectionProvider)} was null, which means impersonation is turned off.");
44 |
45 | var protector = _protectionProvider.CreateProtector(EncryptPurpose);
46 | var encryptedString = protector.Protect(data);
47 | _httpContext.Response.Cookies.Append(CookieName, encryptedString, _options);
48 | }
49 |
50 | public bool Exists(IRequestCookieCollection cookiesIn)
51 | {
52 | return cookiesIn[CookieName] != null;
53 | }
54 |
55 | public string GetCookieInValue()
56 | {
57 | if (_protectionProvider == null)
58 | throw new NullReferenceException(
59 | $"The {nameof(IDataProtectionProvider)} was null, which means impersonation is turned off.");
60 |
61 | var cookieData = _httpContext.Request.Cookies[CookieName];
62 | if (string.IsNullOrEmpty(cookieData))
63 | return null;
64 |
65 | var protector = _protectionProvider.CreateProtector(EncryptPurpose);
66 | string decrypt = null;
67 | try
68 | {
69 | decrypt = protector.Unprotect(cookieData);
70 | }
71 | catch (Exception e)
72 | {
73 | //_logger.LogError(e, "Error decoding a cookie. Have deleted cookie to stop any further problems.");
74 | Delete();
75 | throw;
76 | }
77 |
78 | return decrypt;
79 | }
80 |
81 | public void Delete()
82 | {
83 | _httpContext.Response.Cookies.Delete(CookieName, _options);
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/UserImpersonation/Concrete/ImpersonationData.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System;
5 |
6 | namespace UserImpersonation.Concrete
7 | {
8 | ///
9 | /// This holds the data that will be put into the ImpersonationCookie
10 | ///
11 | public class ImpersonationData
12 | {
13 | ///
14 | /// UserId of the user you are impersonating
15 | ///
16 | public string UserId { get; }
17 |
18 | ///
19 | /// UserName of the impersonated user - used to show who you are impersonating
20 | ///
21 | public string UserName { get; }
22 |
23 | ///
24 | /// If true then when impersonating another user you will keep your original permissions
25 | ///
26 | public bool KeepOwnPermissions { get; }
27 |
28 | public ImpersonationData(string userId, string userName, bool keepOwnPermissions)
29 | {
30 | UserId = userId ?? throw new ArgumentNullException(nameof(userId));
31 | UserName = userName ?? throw new ArgumentNullException(nameof(userName));
32 | KeepOwnPermissions = keepOwnPermissions;
33 | }
34 |
35 | public ImpersonationData(string packedString)
36 | {
37 | var split = packedString.Split(',');
38 | if (split.Length != 3)
39 | throw new ArgumentException("The string didn't unpack to three items");
40 |
41 | UserId = split[0];
42 | KeepOwnPermissions = bool.Parse(split[1]);
43 | UserName = split[2];
44 | }
45 |
46 | public string GetPackImpersonationData()
47 | {
48 | return $"{UserId},{KeepOwnPermissions},{UserName}";
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/UserImpersonation/Concrete/ImpersonationService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using FeatureAuthorize;
5 | using Microsoft.AspNetCore.DataProtection;
6 | using Microsoft.AspNetCore.Http;
7 |
8 | namespace UserImpersonation.Concrete
9 | {
10 | public class ImpersonationService : IImpersonationService
11 | {
12 | private readonly HttpContext _httpContext;
13 | private readonly ImpersonationCookie _cookie;
14 |
15 | public ImpersonationService(IHttpContextAccessor httpContextAccessor, IDataProtectionProvider protectionProvider)
16 | {
17 | _httpContext = httpContextAccessor.HttpContext;
18 | _cookie = protectionProvider != null //If protectionProvider is null then impersonation is turned off
19 | ? new ImpersonationCookie(_httpContext, protectionProvider)
20 | : null;
21 | }
22 |
23 | ///
24 | /// This creates an user impersonation cookie, which starts the user impersonation via the AuthCookie ValidateAsync event
25 | ///
26 | /// This must be the userId of the user you want to impersonate
27 | ///
28 | ///
29 | /// Error message, or null if OK.
30 | public string StartImpersonation(string userId, string userName, bool keepOwnPermissions)
31 | {
32 | if (_cookie == null)
33 | return "Impersonation is turned off in this application.";
34 | if (!_httpContext.User.Identity.IsAuthenticated)
35 | return "You must be logged in to impersonate a user.";
36 | if (_httpContext.User.Claims.GetUserIdFromClaims() == userId)
37 | return "You cannot impersonate yourself.";
38 | if (_httpContext.User.InImpersonationMode())
39 | return "You are already in impersonation mode.";
40 | if (userId == null)
41 | return "You must provide a userId string";
42 | if (userName == null)
43 | return "You must provide a username string";
44 |
45 | _cookie.AddUpdateCookie(new ImpersonationData(userId, userName, keepOwnPermissions).GetPackImpersonationData());
46 | return null;
47 | }
48 |
49 | ///
50 | /// This will delete the user impersonation cookie, which causes the AuthCookie ValidateAsync event to revert to the original user
51 | ///
52 | /// error message, or null if OK
53 | public string StopImpersonation()
54 | {
55 | if (!_httpContext.User.InImpersonationMode())
56 | return "You aren't in impersonation mode.";
57 |
58 | _cookie.Delete();
59 | return null;
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/UserImpersonation/IImpersonationService.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 | namespace UserImpersonation
4 | {
5 | public interface IImpersonationService
6 | {
7 | ///
8 | /// This creates an user impersonation cookie, which starts the user impersonation via the AuthCookie ValidateAsync event
9 | ///
10 | /// This must be the userId of the user you want to impersonate
11 | /// the name to show as being impersonated
12 | /// true if the original user's permissions should be used
13 | /// error message, or null if OK
14 | string StartImpersonation(string userId, string userName, bool keepOwnPermissions);
15 |
16 | ///
17 | /// This will delete the user impersonation cookie, which causes the AuthCookie ValidateAsync event to revert to the original user
18 | ///
19 | /// error message, or null if OK
20 | string StopImpersonation();
21 | }
22 | }
--------------------------------------------------------------------------------
/UserImpersonation/ImpersonateExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2019 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
2 | // Licensed under MIT license. See License.txt in the project root for license information.
3 |
4 | using System.Linq;
5 | using System.Security.Claims;
6 | using Microsoft.AspNetCore.Html;
7 | using UserImpersonation.Concrete;
8 |
9 | namespace UserImpersonation
10 | {
11 | public static class ImpersonateExtensions
12 | {
13 | public static bool InImpersonationMode(this ClaimsPrincipal claimsPrincipal)
14 | {
15 | return claimsPrincipal.Claims.Any(x => x.Type == ImpersonationHandler.ImpersonationClaimType);
16 | }
17 |
18 | public static string GetImpersonatedUserNameMode(this ClaimsPrincipal claimsPrincipal)
19 | {
20 | return claimsPrincipal.Claims.SingleOrDefault(x => x.Type == ImpersonationHandler.ImpersonationClaimType)?.Value;
21 | }
22 |
23 | public static HtmlString GetCurrentUserNameAsHtml(this ClaimsPrincipal claimsPrincipal)
24 | {
25 | var impersonalisedName = claimsPrincipal.GetImpersonatedUserNameMode();
26 | var nameToShow = impersonalisedName ??
27 | claimsPrincipal.Claims.SingleOrDefault(x => x.Type == ClaimTypes.Name)?.Value ??
28 | "not logged in";
29 |
30 | return new HtmlString(
31 | "Impersonating " : ">Hello ")
32 | + $"{nameToShow} ");
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/UserImpersonation/UserImpersonation.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------