3

Currently, I'm working on a project management website. There is a SuperAdmin (role) in the system, who can create new projects. The SuperAdmin can assign users to these projects with different roles, like admin or observer. A user can take different roles for different projects or even no role at all.
For example:

  1. User A is Admin for Project A
  2. User A is Observer for Project B (read access only)
  3. User A has no role Project C (can't access the project)
  4. User B is Admin for Project B
  5. User B is Observer for Project C (read access only)

The Identity does not seems like to support this behaviour. The claim-based authorization seems to support static claims, but not dynamic ones (accessing a project with an id). I can create my own UserProjectRoles class which associates the user, projects and the roles and use that in the authorization handler (policy-based authorization), but I was wondering if this is the right solution. How would you solve this task?

Edit:

I ended up doing the following: I created a new UserProjectClaims table, which connencts the Users and a Projects and contains the role type as an enum (I will move the role/claim type to it's own table, this was just for the sake of testing).

public class UserProjectClaim
{
    public int ProjectId { get; set; }
    public Project Project { get; set; }

    public int UserId { get; set; }
    public AppUser User { get; set; }

    public UserProjectClaimEnum ClaimType { get; set; }
}

Then I used this table in the AuthorizationHandler as seen in the Policy-based authorization tutorial:

protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ProjectAdminRequirement requirement)
{
    if (context.Resource is AuthorizationFilterContext authContext)
    {
        var projectId = Int32.Parse(authContext.HttpContext.Request.Query["projectId"]);
        var userid = (await _userManager.GetUserAsync(context.User)).Id;
        var claim = _dbContext.AppUsers.Include(i=>i.UserProjectClaims).Single(u=>u.Id==userid).UserProjectClaims.SingleOrDefault(p => p.ProjectId == projectId);      

        if (claim != null && claim.ClaimType == Data.Enums.UserProjectClaimEnum.ProjectAdmin)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
    }           
}

And it's working as expected.

gyomihaly
  • 55
  • 9

1 Answers1

1

After creating a new Web Application (MVC) with Individual User Accounts (VS2017), you will find tables such as AspNetRoles, AspNetRoleClaims, AspNetUserRoles, AspNetUserClaims as part of an Entity Framework Core IdentityContext.

The AccountController has a UserManager injected but does not have methods for maintenance of Roles & Claims. The Views (Pages), Controllers and Models are not supplied to manage the relationships, out of the box.

The good news is that the UserManager has all sorts of functionality to add claims & roles to users. AddClaimAsync, AddToRoleAsync, GetClaimsAsync, GetRolesAsync, GetUsersForClaimAsync, GetUsersInRoleAsync, RemoveClaimAsync, RemoveFromRoleAsync and versions that process claims/roles as an IEnumerable.

Short term solution: It should not be a difficult task to add methods to the AccountController or ManageController or even create a RolesController to support pages that manage these relationships.

Long term solution: Learn how to add templates to generate these items whenever you create a new MVC Identity project. Then show off by making it public in GitHub :) Or write an article about what you learned for other people in your situation.

Postscript: By creating a little middleware that you put in the request processing path, you can load the roles/claims for the current user that the rest of the request handling can refer to. A join table from Projects to Roles with perhaps read-role and write-role you can compare the roles required to the roles/claims the user has.

Technique: Here is a terrific explanation of custom policies and Authorization. In your case, the policy would test if the user has a role/claim that matches your project. Your roles above really are Project-Read, Project-Write. If neither, no access.

John White
  • 562
  • 2
  • 10
  • Now if you need help with the mechanics of "How do I...?" then please expand upon this start. – John White Jun 26 '18 at 15:18
  • Thanks for the answer! – gyomihaly Jun 26 '18 at 15:43
  • I'm aware of these tables, but I don't think I can implement my functionality based on them (although I can override them, but that's not the point). I don't think the built-in Roles and Claims support this kind of authorization, but I'm not sure. I can solve this problem by writing my own Claim tables and injecting the DbContext to an AuthorizationHandler, but I hoped that there is a more elegant solution. – gyomihaly Jun 26 '18 at 15:50
  • Oh, ok. A little more explanation then. The plumbing is there, but the pretty stuff (UI) isn't. Updating my Answer. – John White Jun 26 '18 at 15:51
  • Just add Read & Write roles/claims for Project A, same for Project B, etc. Claims is probably preferable. – John White Jun 26 '18 at 16:06
  • For a UI take a look at [Identity Management](https://github.com/mguinness/IdentityManager) for ASP.NET Core. – Mark G Jun 27 '18 at 01:01