Skip to content

Commit

Permalink
Major refactor before starting part2
Browse files Browse the repository at this point in the history
  • Loading branch information
JonPSmith committed Dec 17, 2018
1 parent 07176ac commit 682d845
Show file tree
Hide file tree
Showing 25 changed files with 330 additions and 168 deletions.
8 changes: 4 additions & 4 deletions DataLayer/EfClasses/ModulesForUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
namespace DataLayer.EfClasses
{
/// <summary>
/// This holds what modules a user can access, using the user's email as the key
/// This holds what modules a user can access, using the user's identity key
/// </summary>
public class ModulesForUser
{
public ModulesForUser(string userEmail, PaidForModules allowedPaidForModules)
public ModulesForUser(string userId, PaidForModules allowedPaidForModules)
{
UserEmail = userEmail ?? throw new ArgumentNullException(nameof(userEmail));
UserId = userId ?? throw new ArgumentNullException(nameof(userId));
AllowedPaidForModules = allowedPaidForModules;
}

[Key]
public string UserEmail { get; set; }
public string UserId { get; set; }
public PaidForModules AllowedPaidForModules { get; set; }
}
}
12 changes: 12 additions & 0 deletions PermissionAccessControl.sln
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApp", "TestWebApp\Te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataLayer", "DataLayer\DataLayer.csproj", "{DBA80257-4697-480E-85C6-87C23047E415}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RolesToPermission", "RolesToPermission\RolesToPermission.csproj", "{D75D71AF-EFEE-4C37-BD28-9BC11B2ADACA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StartupCode", "StartupCode\StartupCode.csproj", "{7CE53160-EA4F-4395-90FC-C1A10290BD62}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -34,6 +38,14 @@ Global
{DBA80257-4697-480E-85C6-87C23047E415}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBA80257-4697-480E-85C6-87C23047E415}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBA80257-4697-480E-85C6-87C23047E415}.Release|Any CPU.Build.0 = Release|Any CPU
{D75D71AF-EFEE-4C37-BD28-9BC11B2ADACA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D75D71AF-EFEE-4C37-BD28-9BC11B2ADACA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D75D71AF-EFEE-4C37-BD28-9BC11B2ADACA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D75D71AF-EFEE-4C37-BD28-9BC11B2ADACA}.Release|Any CPU.Build.0 = Release|Any CPU
{7CE53160-EA4F-4395-90FC-C1A10290BD62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CE53160-EA4F-4395-90FC-C1A10290BD62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CE53160-EA4F-4395-90FC-C1A10290BD62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CE53160-EA4F-4395-90FC-C1A10290BD62}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;

namespace TestWebApp.RolesToPermissions
namespace RolesToPermission
{
//thanks to https://www.jerriepelser.com/blog/creating-dynamic-authorization-policies-aspnet-core/

Expand Down
62 changes: 62 additions & 0 deletions RolesToPermission/CalcAllowedPermissions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT license. See License.txt in the project root for license information.

using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Threading.Tasks;
using DataLayer.EfCode;
using Microsoft.EntityFrameworkCore;
using PermissionParts;

namespace RolesToPermission
{
public class CalcAllowedPermissions
{
/// <summary>
/// NOTE: This class is used in OnValidatePrincipal so it can't use DI, so I can't inject the DbContext here because that is dynamic.
/// Therefore I can pass in the database options because that is a singleton
/// From that the method can create a valid dbContext to access the database
/// </summary>
private readonly DbContextOptions<ExtraAuthorizeDbContext> _extraAuthDbContextOptions;


public CalcAllowedPermissions(DbContextOptions<ExtraAuthorizeDbContext> extraAuthDbContextOptions)
{
_extraAuthDbContextOptions = extraAuthDbContextOptions;
}

/// <summary>
/// This is called if the Permissions that a user needs calculating.
/// </summary>
/// <param name="user"></param>
/// <returns></returns>
public async Task<string> CalcPermissionsForUser(ClaimsPrincipal user)
{
var usersRoles = user.Claims.Where(x => x.Type == ClaimTypes.Role).Select(x => x.Value)
.ToList();

using (var dbContext = new ExtraAuthorizeDbContext(_extraAuthDbContextOptions))
{
//This gets all the permissions, with a distinct to remove duplicates
var permissionsForUser = await dbContext.RolesToPermissions.Where(x => usersRoles.Contains(x.RoleName))
.SelectMany(x => x.PermissionsInRole)
.Distinct()
.ToListAsync();
//we get the modules this user is allows to see
var userModules =
dbContext.ModulesForUsers.Find(user.Claims.SingleOrDefault(x => x.Type == ClaimTypes.NameIdentifier).Value)
?.AllowedPaidForModules ?? PaidForModules.None;
//Now we remove permissions that are linked to modules that the user has no access to
var filteredPermissions =
from permission in permissionsForUser
let moduleAttr = typeof(Permissions).GetMember(permission.ToString())[0]
.GetCustomAttribute<LinkedToModuleAttribute>()
where moduleAttr == null || userModules.HasFlag(moduleAttr.PaidForModule)
select permission;

return filteredPermissions.PackPermissionsIntoString();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Microsoft.AspNetCore.Authorization;
using PermissionParts;

namespace TestWebApp.RolesToPermissions
namespace RolesToPermission
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public class HasPermissionAttribute : AuthorizeAttribute
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT license. See License.txt in the project root for license information.

namespace TestWebApp.RolesToPermissions
namespace RolesToPermission
{
public static class PermissionConstants
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using System.Security.Claims;
using PermissionParts;

namespace TestWebApp.RolesToPermissions
namespace RolesToPermission
{
public static class PermissionExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using Microsoft.AspNetCore.Authorization;
using PermissionParts;

namespace TestWebApp.RolesToPermissions
namespace RolesToPermission
{
//thanks to https://www.jerriepelser.com/blog/creating-dynamic-authorization-policies-aspnet-core/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System;
using Microsoft.AspNetCore.Authorization;

namespace TestWebApp.RolesToPermissions
namespace RolesToPermission
{
public class PermissionRequirement : IAuthorizationRequirement
{
Expand Down
17 changes: 17 additions & 0 deletions RolesToPermission/RolesToPermission.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
<ProjectReference Include="..\PermissionParts\PermissionParts.csproj" />
</ItemGroup>

</Project>
19 changes: 19 additions & 0 deletions StartupCode/StartupCode.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.6" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.1.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
</ItemGroup>

</Project>
97 changes: 97 additions & 0 deletions StartupCode/StartupExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DataLayer.EfClasses;
using DataLayer.EfCode;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using PermissionParts;

namespace StartupCode
{
public static class StartupExtensions
{
private const string SeedDataDir = "SeedData";
private const string UserInfoJsonFilename = "userinfo.json";
private const string RoleToPermissionsFilename = "rolestopermissions.json";

public static async Task AddUsersAndExtraAuthAsync(this IServiceProvider serviceProvider)
{
using (var scope = serviceProvider.CreateScope())
{

var services = scope.ServiceProvider;
var env = services.GetRequiredService<IHostingEnvironment>();
var pathToUserData = Path.GetFullPath(Path.Combine(env.WebRootPath, SeedDataDir, UserInfoJsonFilename));
var userInfo = JsonConvert.DeserializeObject<List<UserInfoJson>>(File.ReadAllText(pathToUserData));

var userManager = services.GetRequiredService<UserManager<IdentityUser>>();
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();

var users = await Task.WhenAll(userInfo.Select(async x => await AddUserWithRoles(x, userManager, roleManager)));

var pathToRolePermissionData = Path.GetFullPath(Path.Combine(env.WebRootPath, SeedDataDir, RoleToPermissionsFilename));
var rolesToPermissions = JsonConvert.DeserializeObject<List<RoleToPermissions>>(File.ReadAllText(pathToRolePermissionData));

using (var context = services.GetRequiredService<ExtraAuthorizeDbContext>())
{
context.AddRange(rolesToPermissions);
context.AddRange(userInfo.BuildModulesForUsers(users));
context.SaveChanges();
}
}
}

//---------------------------------------------------------------------------
//private methods

private static IEnumerable<ModulesForUser> BuildModulesForUsers(this List<UserInfoJson> userInfo,
IdentityUser[] users)
{
foreach (var userInfoJson in userInfo.Where(x => !string.IsNullOrEmpty(x.ModulesCommaDelimited)))
{
PaidForModules combinedModules = PaidForModules.None;
foreach (var moduleName in userInfoJson.ModulesCommaDelimited.Split(',').Select(x => x.Trim()))
{
if (!Enum.TryParse(typeof(PaidForModules), moduleName, true, out var thisModule))
throw new InvalidOperationException($"The module name {moduleName} in the {UserInfoJsonFilename} isn't a valid module name.");
combinedModules |= (PaidForModules) thisModule;
}
yield return new ModulesForUser(userInfoJson.Email.GetUserIdWithGivenEmail(users), combinedModules);
}
}

private static string GetUserIdWithGivenEmail(this string email, IdentityUser[] users)
{
return users.Single(x => x.Email == email).Id;
}

private static async Task<IdentityUser> AddUserWithRoles(UserInfoJson userInfo, UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager)
{
var user = new IdentityUser {UserName = userInfo.Email, Email = userInfo.Email};
var result = await userManager.CreateAsync(user, user.Email); //email is the password
if (!result.Succeeded)
throw new InvalidOperationException($"Tried to add user {user.UserName}, but failed.");

foreach (var roleName in userInfo.RolesCommaDelimited.Split(',').Select(x => x.Trim()))
{
var roleExist = await roleManager.RoleExistsAsync(roleName);
if (!roleExist)
{
//create the roles and seed them to the database: Question 1
await roleManager.CreateAsync(new IdentityRole(roleName));
}
await userManager.AddToRoleAsync(user, roleName);
}

return user;
}
}
}
27 changes: 27 additions & 0 deletions StartupCode/UserInfoJson.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2018 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/
// Licensed under MIT license. See License.txt in the project root for license information.

using System.Collections.Generic;

namespace StartupCode
{
public class UserInfoJson
{
/// <summary>
/// User's Email, which is also their password
/// </summary>
public string Email { get; set; }
/// <summary>
/// List of RoleNames, comma delimited
/// </summary>
public string RolesCommaDelimited { get; set; }
/// <summary>
/// List of Module Names, comma delimited
/// </summary>
public string ModulesCommaDelimited { get; set; }
/// <summary>
/// Various Data authorization data
/// </summary>
public Dictionary<string,string> DataInfo { get; set; }
}
}
1 change: 1 addition & 0 deletions TestWebApp/Controllers/ColorController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using PermissionParts;
using RolesToPermission;
using TestWebApp.RolesToPermissions;

namespace TestWebApp.Controllers
Expand Down
1 change: 1 addition & 0 deletions TestWebApp/Controllers/Feature1Controller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using Microsoft.AspNetCore.Mvc;
using PermissionParts;
using RolesToPermission;
using TestWebApp.RolesToPermissions;

namespace TestWebApp.Controllers
Expand Down
2 changes: 1 addition & 1 deletion TestWebApp/DisplayCode/ListUsers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public List<UserListDto> ListUserWithRolesAndModules()
var result = new List<UserListDto>();
foreach (var user in _applicationDbContext.Users)
{
var thisUserModules = _extraAuthorizeDbContext.ModulesForUsers.Find(user.Email)?.AllowedPaidForModules ??
var thisUserModules = _extraAuthorizeDbContext.ModulesForUsers.Find(user.Id)?.AllowedPaidForModules ??
PaidForModules.None;
result.Add(new UserListDto(user.UserName,
string.Join(", ", rolesWithUserIds.Where(x => x.UserId == user.Id).Select(x => x.Name)),
Expand Down
21 changes: 20 additions & 1 deletion TestWebApp/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DataLayer.EfCode;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TestWebApp.Data;
using TestWebApp.RolesToPermissions;

namespace TestWebApp
Expand All @@ -24,8 +27,24 @@ private static IWebHost BuildWebHostAsync(string[] args)
.UseStartup<Startup>()
.Build();

webHost.SetupDatabases();
SetupDatabases(webHost);
return webHost;
}

private static void SetupDatabases(IWebHost webHost)
{
using (var scope = webHost.Services.CreateScope())
{
var services = scope.ServiceProvider;
using (var context = services.GetRequiredService<ExtraAuthorizeDbContext>())
{
context.Database.EnsureCreated();
}
using (var context = services.GetRequiredService<ApplicationDbContext>())
{
context.Database.EnsureCreated();
}
}
}
}
}
Loading

0 comments on commit 682d845

Please sign in to comment.