Microsoft Identity Claims: Building the UI, Part 3

In our final post of the series, we build our views using all of the techniques from previous posts and build on the Permission Manager.

Written by Jonathan "JD" Danylko • Last Updated: • Develop •

Man sketching out a design for a screen using a pen and paper

Microsoft Identity: Granular Authorizations Series

  1. Microsoft Identity Claims: Granular Authorizations, Part 1
  2. Microsoft Identity Claims: Isolating Enumerations, Part 2
  3. Microsoft Identity Claims: Building the UI, Part 3 (this post)

During this series, we've examined and demonstrated how to store entity permissions in the UserClaims table of Microsoft Identity. Once we created the foundation of the permissions, we explained a need to isolate enumerations on a permission entity. Even though it was a small post, we'll show how important it is for the UI.

In this final post, we'll tie part one and two together to create a user interface (UI) for ease of use for administrators.

Overview

In Part 1, we introduced a PermissionManager to determine whether a user is authorized to perform a certain action in the application with the IsAllowed() method.

To finalize our PermissionManager, we'll need a way to load and save the claims. Luckily, we have the Microsoft Identity to assist with this task.

We'll start with loading the permissions.

Loading Permissions

To manage the permissions, we need a model to hold the user's settings.

In the UI, we can envision it in a group/entity display.

Product Create
View
Update
Delete
Attachments Upload

The approach of the model will mimic the UI so let's focus on the PermissionModel first.

public class PermissionModel
{
    public IDictionary<string, IDictionary<string, bool>> EntityPermission { get; set; } =
        new Dictionary<string, IDictionary<string, bool>>();
}

Think of this model like the UI above. The first string in the dictionary is the entity name where the second type is a dictionary comprised of the PermissionEnum name and whether it's true or false (boolean).

Next, we need a ViewModel for our permissions. The ViewModel requires the user we're editing and the actual permissions for that user.

public class PermissionViewModel
{
    public ApplicationUser EditedUser { get; set; }
    public PermissionModel ClaimModels { get; set; }
}

Now that we have our ViewModel defined, we can use the PermissionManager to send and receive our ClaimModels.

public PermissionModel GetClaimModels()
{
    var result = new PermissionModel();
    var mhePermissions = Enumeration.GetAll<Permission>().ToList();
    foreach (var entity in mhePermissions)
    {
        var permission = _user.Claims.FirstOrDefault(t => t.ClaimType == entity.DisplayName);
        result.EntityPermission.Add(entity.DisplayName, GetEntityPermissions(permission, entity));
    }

   return result;
}

private
 IDictionary<string, bool> GetEntityPermissions(ApplicationUserClaim permissionClaim,
    Permission permission)
{
    var allPermissions = GetPermissionFlags();

   var includedPermissions = GetValidPermissionsFlags(permission);

   return allPermissions
        .Where(enumPermission =>
            Array.Exists(includedPermissions, t => t.Equals(enumPermission.Value.ToEnum<T>())))
        .ToDictionary(enumPermission => enumPermission.Key,
            enumPermission => IsAllowed(permission, enumPermission.Value.ToEnum<T>()));
}

public
 static IDictionary<string, int> GetPermissionFlags()
{
    var enumType = typeof(T);
    if (enumType.BaseType != typeof(Enum))
        throw new ArgumentException("T is not System.Enum");

   var allPermissionEnums = Enum.GetValues(typeof(T));

   IDictionary<string, int> list = new Dictionary<string, int>();
    foreach (var e in allPermissionEnums)
    {
        var fi = e.GetType().GetField(e.ToString());
        var attributes = (DescriptionAttribute[])
            fi.GetCustomAttributes(typeof(DescriptionAttribute), false);

       list.Add(new KeyValuePair<string, int>(attributes.Length > 0
            ? attributes[0].Description
            : e.ToString(), (int)e));
    }

   return list;
}

public
 static T[] GetValidPermissionsFlags(Permission permission)
{
    var included = GetIncludedPermissions(permission).ToList();

   var result = Enum.GetValues(typeof(T));
    if (included.Any())
    {
        result = included.ToArray();
    }

   return (T[]) result;
}

private
 static IEnumerable<PermissionEnum> GetIncludedPermissions(Permission permission)
{
    var field = permission.GetType().GetField(permission.DisplayName);
    if (field == null)
        return new PermissionEnum[0];

   var attribute = (IncludePermissionAttribute)field
        .GetCustomAttribute(typeof(IncludePermissionAttribute));

   return attribute != null
        ? attribute.IncludedPermissions
        : new PermissionEnum[0];
}

Let's break down each of the methods.

GetClaimModels() is the building of the ViewModel from the UserClaims table for the UI. 

We create a new PermissionModel and gather all of the permission entities. In this case, it includes the Product and Attachment permission entities (in the Permission class of part 1). This gives us a list of two entity permissions.

In the loop, we grab each user claim based on the entity's permission name (Claim.ClaimType == "Product"). Even if we don't find it, we still add it to a collection with a call to GetEntityPermissions(). This gives us our loading capabilities.

Let's look at GetEntityPermissions(). In this method, we need an entire list of every permission and the permissions assigned only to this entity to generate a simple enum list with booleans.

The GetPermissionFlags() loops through the enumerated <T> defined in the main class looking for the description and value.

GetValidPermissionFlags() takes a permission entity and returns all of the permissions allowed on that entity based on the result from GetIncludedPermissions(). This is where we utilize our attributes from Part 2.

NOTICE: The ToEnum<T> extension method is found here.

This gives us a collection of entities with a list of their permissions in one tidy package.

Controller Setup

The controller is probably the easiest to setup. If you have a UserManager from Microsoft Identity, this provides the user and the claims you're editing on the screen.

With the PermissionManager almost finished, we can test our models against the UI and see if everything works.

var user = userManager.FindById(userIdToLoad);
viewModel.EditedUser = user;

var manager = new PermissionManager<PermissionEnum>(user);
viewModel.ClaimModels = manager.GetClaimModels();

Our controller returns the model (viewModel) to the View.

Building the View

The View is also easy. We use the PermissionViewModel from our controller, use a bootstrap card component for our UI, and display our layout permissions.

<div class="card bg-light">

   @using (Html.BeginForm("Permissions", "Profile", FormMethod.Post, new { @class = "form-horizontal" }))
    {
        <div class="card-body">

           <a class="btn btn-primary" href="@Url.ProfileUrl()">&laquo; Back to Profile</a>

           <h3>User Permissions <span class="small">for @Model.EditedUser.GetFullName()</span></h3>
            <hr />

           @Html.HiddenFor(e=> e.EditedUser.Id)

           @foreach (var permission in Model.ClaimModels.EntityPermission)
            {
                <div class="form-group">
                    <label class="h4 col-md-2 control-label mt-0">@permission.Key</label>
                    <div class="col-md-3">
                        @foreach (var permissionType in permission.Value)
                        {
                            <div class="checkbox">
                                <label>
                                    @Html.CheckBoxFor(e => e.ClaimModels.EntityPermission[permission.Key][permissionType.Key])
                                    @permissionType.Key
                                </label>
                            </div>
                        }
                    </div>
                </div>
            }

           <hr />
            <button type="submit" class="btn btn-primary margin-r-10" id="submitPermissions" name="submitPermissions">Save Permissions</button>
            <a href="@Url.ProfileUrl()" class="btn btn-default" id="cancelButton">Cancel</a>
        </div>

   }

</
div>

This gives us our formatted display.

screenshot of permission screen

Saving the Permissions

Did you notice the BeginForm at the beginning of the code? We're calling the Permissions method in the Profile controller passing in the PermissionViewModel on POST.

So it seems we need a POST for our Controller. ??

The best part about the View is everything is placed back into the ViewModel on postback to the Controller.

[HttpPost]
public ActionResult Permissions(PermissionViewModel model)
{
    var context = HttpContext;

   var userManager = context.GetOwinContext().Get<ApplicationUserManager>();
    var user = userManager.FindById(model.EditedUser.Id);

   var manager = new PermissionManager<PermissionEnum>(user);
    var newUser = manager.UpdateClaims(model.ClaimModels.EntityPermission);
    userManager.Update(newUser);

   return Redirect(Url.ProfileUrl());
}

In the Controller POST, we load the user from the ApplicationUserManager and pass the user into the PermissionManager.

If you notice, we are forgetting something.

Our PermissionManager requires an UpdateClaims() method.

Updating the User Claims

Our UpdateClaims() method is simple since we already have an ApplicationUser and UserClaims available to us through the PermissionManager.

public ApplicationUser UpdateClaims(IDictionary<string, IDictionary<string, bool>> entity)
{
    foreach (var permission in entity)
    {
        var permissionSum = GetPermissionSum(permission.Value);

       var userPermission = _user.Claims.FirstOrDefault(e => e.ClaimType == permission.Key);
        if (userPermission == null)
        {
            _user.Claims.Add(new ApplicationUserClaim
            {
                UserId = _user.Id,
                ClaimType = permission.Key,
                ClaimValue = permissionSum.ToString()
            });
        }
        else
        {
            userPermission.ClaimValue = permissionSum.ToString();
        }
    }

   return _user;
}

private
 int GetPermissionSum(IDictionary<string, bool> permissions)
{
    var result = permissions.Where(e => e.Value)
        .Select(permission => permission.Key.ToEnum<T>())
        .ToArray();
    return PermissionSum(result);
}

public static int PermissionSum(params T[] list)
{
    return list.Cast<int>().Sum();
}

On updating, we calculate the PermissionSum by taking the list of permissions, filter out the true (checked) values, convert each Key to the respected Enum and return all values in an array.

At this point, we pass the PermissionEnums array into the PermissionSum method and calculate a total. This gives us our value for the ClaimValue.

Next, we check to see if the UserClaim exists. If it does not exist, we create a new ApplicationUserClaim and assign the PermissionSum to the ClaimValue and entity name to the ClaimType. If it does exist, we just replace the ClaimValue with the PermissionSum.

In the Controller, we utilize the UserManager's Update method to save the user's claims.

Conclusion

In this post, we've added onto the PermissionManager making it easier to authorize access to the application (using IsAllowed), save, and load authorizations from the Identity UserClaims table.

This approach is extremely versatile for authorizations and can be expanded for any type of entity you add to the system.

Do you have your own security authorizations? What do you use for authorizations? Post your comments below and let's discuss.

Did you like this content? Show your support by buying me a coffee.

Buy me a coffee  Buy me a coffee
Picture of Jonathan "JD" Danylko

Jonathan Danylko is a web architect and entrepreneur who's been programming for over 25 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Principal Software Engineer Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus