+ All Categories
Home > Documents > RBAC Framework Introduction

RBAC Framework Introduction

Date post: 12-Sep-2015
Category:
Upload: -
View: 45 times
Download: 4 times
Share this document with a friend
Description:
Role Based Access Control Framework
Popular Tags:
32
CUSTOM ROLES BASED ACCESS CONTROL (RBAC) IN ASP.NET MVC APPLICATIONS PART 1 ARTICLE FRAMEWORK INTRODUCTION PROJECT: CUSTOM ROLES BASED ACCESS CONTROL (RBAC) AUTHOR: STEFAN WLOCH REPORT DATE: 13 TH FEBRUARY 2015
Transcript
  • CUSTOM ROLES BASED ACCESS CONTROL (RBAC) IN

    ASP.NET MVC APPLICATIONS

    PART 1

    ARTICLE FRAMEWORK INTRODUCTION

    PROJECT: CUSTOM ROLES BASED ACCESS CONTROL (RBAC) AUTHOR: STEFAN WLOCH REPORT DATE: 13TH FEBRUARY 2015

  • Page 2

    Contents

    Introduction ...................................................................................................................................................................... 3

    Background ....................................................................................................................................................................... 3

    Using default ASP.NET Roles and Membership API .......................................................................................................... 3

    Custom Controller/Action Authorization .......................................................................................................................... 4

    Authorization ................................................................................................................................................................ 4

    Roles Based Access Control (RBAC) .............................................................................................................................. 4

    Preparing the Database .................................................................................................................................................... 5

    Retrieving User Application Permissions from our RBAC Tables .................................................................................. 5

    User Roles/Permissions Class Mapping ............................................................................................................................ 6

    A Basic Overview of MVC Controller Action Methods ...................................................................................................... 7

    Roles and MVC Controller/Action Associations ................................................................................................................ 8

    Action Filters ................................................................................................................................................................... 10

    Custom Action Filters .................................................................................................................................................. 10

    Restricting Access to MVC Controller Action Methods using Action Filters ............................................................... 12

    Performing Conditional Processing using RBAC during Controller Action Execution ................................................. 13

    Using RBAC in a Controllers Action Method .............................................................................................................. 15

    Using RBAC in a Controllers View .............................................................................................................................. 16

    Using RBAC to Dynamically Control Menus ................................................................................................................ 17

    Persisting RBAC Data via ADO.NET Entity Framework (EF)............................................................................................. 18

    Creating an Entity Framework (EF) RBAC Model ............................................................................................................ 19

    Generated Entity Framework (EF) RBAC Model ......................................................................................................... 20

    Extracting Data from our Entity Framework (EF) RBAC Model ................................................................................... 21

    Reading User Roles/Permissions from our Database ................................................................................................. 22

    Code Listing for Entity Framework (EF) RBAC Data Retrieval ................................................................................. 22

    Extending the RBAC Framework ..................................................................................................................................... 23

    Sample Project ................................................................................................................................................................ 24

    Application Permissions .............................................................................................................................................. 24

    Application Roles ........................................................................................................................................................ 25

    Application Users ........................................................................................................................................................ 26

    Adding RBAC to Existing MVC Applications ................................................................................................................ 27

    jQuery User Interface .................................................................................................................................................. 29

    Removing jQuery User Interface References .............................................................................................................. 30

    RBAC Authentication/Authorisation Overview ............................................................................................................... 31

    Conclusion ....................................................................................................................................................................... 32

  • Page 3

    Introduction In this post, I shall cover implementing custom Roles Based Access Control (RBAC) and subsequent roles

    maintenance in the context of an intranet based ASP.NET MVC web application using Windows Authentication.

    ASP.NET Roles and Membership provides almost all features required to perform authentication and authorization but

    adding a new role and assigning it to a particular user seems to have been lost. This solution forms a self-contained

    framework independent of default out of the box providers. The framework allows us to focus on which features/areas in our application are restricted to the user, including menus, and what information to make visible/invisible to the user

    without concerning ourselves with the underlying technicalities. The framework offers RBAC functionality inside the

    controller action and controller view at a granular level whilst using minimum code syntax and the framework can be

    extended to incorporate custom RBAC methods. It is especially suited for corporate intranet applications where there

    is restricted access to the hosting web server once your web application has been deployed or the administration of

    user roles including role assignment cannot be directly undertaken by the applications system administrator or owner.

    Background Developing intranet applications within an organization based on Windows Authentication has been around since the

    dawn of the intranet. In most organizations, its typical that intranet based applications not only require to permit

    access to a subset of users defined in the organizations Active Directory server, but to also define roles which are

    assigned to the applications users thus restricting access to certain features/areas within the application. Again,

    Roles Based Access Control isnt a new concept and there are numerous examples posted that exemplify this concept

    in one form or another. ASP.NET MVC aligns itself well for RBAC and the examples posted on the web in their

    various guises either over engineer the concept or are too simplistic averting extendibility. Its for this reason that I

    wrote this article.

    Using default ASP.NET Roles and Membership API The default ASP.NET Roles and Membership classes come in very handy when we want to provide authentication

    and authorization in our applications. This approach requires you to define roles upfront which are then referenced in

    the MVC application via function attributes; essentially hardcoding values.

    [Authorize(Roles="Administrators")] public class AdminController : Controller { . . . }

    Several problems with this approach become immediately obvious...

    Without recompiling and re-deploying my application, how do I create new custom roles and bind them

    dynamically to controller methods once my application has been deployed?

    How do I dynamically associate users with multiple roles where the highest application permission takes

    access precedence?

    How do I dynamically control menus or controller view rendering based on the requesting users role(s) and

    associated application permissions?

    When developing corporate solutions, we generally find ourselves in situations where the applications users role data

    needs to be stored in the applications own database. If a database server fails due to hardware failure, restoring an

    earlier backed-up copy of the applications database will contain all the role data thus aligning well for database

    replication for the purpose of a hot standby database server.

  • Page 4

    Custom Controller/Action Authorization Choosing to implement our own custom authentication/authorization mechanism will entail abandoning the default out

    of the box ASP.NET Roles and Membership authentication/authorization mechanism. However, in doing so enables

    for finer granularity over user roles and application rights. Generally speaking, an application once deployed should

    be self-maintaining and regulating via the applications system administrator; it becomes a time consuming and costly

    affair when an application developer inherently becomes the users/roles administrator for that application unless the

    intention is to capitalize on the reliance of support teams maintaining backend roles.

    Authorization Role based applications are where users in the system are assigned specific roles. In our system, each role

    determines which areas of the application the role can access via application permissions. Application permissions

    define MVC controller names and controller action names represented as a string concatenation of the two properties

    in the format controller-action (eg admin-index). Application permissions are unique which can be traced back to

    their controller-action references. It's easy to get confused with the difference between user authentication and user

    authorization. In summary, authentication is verifying that users are who they say they are, using some form of login

    mechanism (username/password, Windows Authentication, and so on something that says this is who I am).

    Authorization is verifying that they can perform tasks as part of their job role with respect to your site. This is usually

    achieved using some type of role-based system.

    Roles Based Access Control (RBAC) Roles Based Access Control is an approach to restricting system access to authorised users. This mechanism can be

    used to protect users from accessing parts of the system that they do not need. It also can be used to restrict access

    to data which they do not need to see.

    Roles are created for various job functions and its not uncommon for new roles to be introduced into a role-based

    system long after the application has been deployed. The permissions to perform certain operations are assigned to

    specific roles. Users are assigned particular roles, and through those role assignments acquire application

    permissions to perform particular computer-system functions. Since users are not assigned permissions directly, but

    only acquire them through their role (or roles), management of individual user permissions becomes a matter of simply

    assigning appropriate roles to the user's account. Each user in the system can be assigned zero, one or many roles

    depending on their responsibility within the business processes.

  • Page 5

    Preparing the Database In order to form the basis of our authentication/authorization framework, we will need to add several tables to the

    applications existing database, if one currently exists, or create a new application database that will contain the

    tables. Theses tables are derived from the following Entity-Relationship (ER) diagram.

    RBAC Entity-Relationship Diagram

    USER ROLE PERMISSIONIs Assigned Is Assigned

    Our Entity-Relationship diagram implies that an application user can be assigned zero or many application roles. An

    application role can be assigned zero or many application permissions. Application permission represents controller

    action methods.

    Subsequently, we derive the following database tables from our Entity-Relationship diagram.

    USERS

    User_IdUsername

    LNK_USER_ROLE

    User_IdRole_Id

    ROLES

    Role_IdRoleNameIsSysAdmin

    LNK_ROLE_PERMISSION

    Role_IdPermission_Id

    PERMISSIONS

    Permission_IdPermissionName

    We would clearly use more table properties in our real-world application allowing for flexible customization but the

    illustrated tables provide the minimum properties required to form the basis of an RBAC framework. Integrating our

    custom authentication/authorization mechanism into existing MVC applications should be relatively straight forward

    since no additional databases or Identity Management providers (IdMs) are needed. It would be highly unlikely that

    any MVC application wishing to introduce RBAC would be operating without a backend database in the first place

    therefore we could simply add the above tables to the existing database. However, we do have the option to separate

    our RBAC tables away from the main application database since our RBAC tables are independent and based on a

    loose coupling design. Any MVC application operating without a backend database will generally not need RBAC,

    examples being unit/currency conversion websites.

    Retrieving User Application Permissions from our RBAC Tables The following SQL will retrieve a users application permissions from our database tables and is used here for

    illustrative purposes.

    SELECT Permission_Id, PermissionName FROM PERMISSIONS

    WHERE Permission_Id IN (

    SELECT DISTINCT(Permission_Id)

    FROM LNK_ROLE_PERMISSION

    WHERE Role_Id IN (SELECT DISTINCT(Role_Id)

    FROM LNK_USER_ROLE ur

    JOIN USERS u ON u.User_Id=ur.User_Id

    WHERE u.Username='swloch'))

  • Page 6

    User Roles/Permissions Class Mapping Let us now represent a users associated roles and application permissions using a single class encapsulating the

    necessary worker methods to check the users role(s) and associated permissions. We will take a closer look at the

    GetDatabaseUserRolesPermissions() method at a later stage. This class will be used throughout our application to determine user authorization.

    Note: Code snippets presented in this article are minimal intended for illustration purposes only in order to focus on

    the topic at hand. The sample project available for download expands on the illustrated code.

    public class RBACUser { public int User_Id { get; set; } public bool IsSysAdmin { get; set; } public string Username { get; set; } private List Roles = new List(); public RBACUser(string _username) { this.Username = _username; this.IsSysAdmin = false; GetDatabaseUserRolesPermissions(); } private void GetDatabaseUserRolesPermissions() {

    //Get user roles and permissions from database tables... } public bool HasPermission(string requiredPermission) { bool bFound = false; foreach (UserRole role in this.Roles) { bFound = (role.Permissions.Where(p => p.PermissionDescription == requiredPermission).ToList().Count > 0); if (bFound) break; } return bFound; } public bool HasRole(string role) { return (Roles.Where(p => p.RoleName == role).ToList().Count > 0); } } public class UserRole { public int Role_Id { get; set; } public string RoleName { get; set; } public List Permissions = new List(); } public class RolePermission { public int Permission_Id { get; set; } public string PermissionDescription { get; set; } }

    The RBACUser class encapsulates custom user authentication/authorization functionality and will be executed in an action filter which supports pre-action behaviour to controller action methods. Action Filters are explained in the

    following section.

  • Page 7

    A Basic Overview of MVC Controller Action Methods MVC controllers are responsible for responding to requests made against an ASP.NET MVC website. MVC attempts

    to map each browser request to a particular controller action. If no controller action is specified in the URL, as in the

    example given below, the default controller action index is used. If the underlying controller or controller action does

    not exist, a HTTP 404 error will be returned to the browser. For example, entering the following URL into a web

    browser will cause MVC to attempt to invoke the Index action in the Admin controller.

    http://localhost/Admin

    The Admin verb in the URL signifies the controller name due to its position in the URL path. In this example, the

    AdminController class is invoked; MVCs controller class naming convention is to append the keyword Controller

    to the controllers name. An MVC controller class is responsible for processing and responding to browser requests.

    Every controller class should expose controller action methods that get invoked via URL references or other paths.

    The following URL specifies both a controller name and controller action method.

    http://localhost/Admin/Create

    MVC will attempt to invoke the Create controller action method in the AdminController class. Every controller

    action returns an action result in response to a browser request even if the referenced controller or controller action

    doesnt exist. Before executing an invoked controller action method, pre-processing can be instructed by using an

    Action Filter where logic can be placed to determine if the action method should be executed or directed to another

    part of the system instead. An Action Filter is an ideal candidate for checking a users authorization for the invoked

    functionality.

  • Page 8

    Roles and MVC Controller/Action Associations Now that we have a basic understanding on how controller action methods are invoked and that MVC provides a way

    for us to process logic before an action method is executed in order to evaluate whether the invoked action method

    should be executed, we need to understand the association between controller action methods and our application

    roles. In our system, each role will have a number of application controller-action associations defined; recall that

    application permissions define MVC controller names and controller action names formatted as controller-action.

    Lets consider the following example; the Administrator role defined in our application must have the ability to create

    new users via the Admin controller using the Create action method. Therefore, we require to associate the

    application permission admin-create with the Administrator role. Likewise, the Standard User role defined in our

    application should not have this ability and therefore must not have the application permission association. Our

    example create user action method simply returns a page to the browser containing text fields and a submit button

    that enables the administrator to create a new user in the backend database. Clearly, we dont want this page

    accessible to anybody.

    We need to create the application permission admin-create and assign the permission to our Administrator role

    since the controllers name and action method name combination will be passed to our authorization logic as the

    required application permission to be evaluated against the requesting users allowed application permissions. You

    can change the format of the application permission stored in the database to a format you prefer but it needs to be

    consistent throughout the application.

    For the time being, we shall manually create and assign the application permission in our database to gain an

    understanding on how our RBAC tables are structured but the sample project available for download provides an

    administration menu that enables a system administrator to perform CRUD actions on users/roles/permissions

    dynamically.

    --Create an 'Administrator' Role setting IsSystemRole=1

    INSERT INTO ROLES(RoleName, IsSysAdmin) VALUES('Administrator', 1)

    --Create a 'Standard User' Role setting IsSystemRole=0

    INSERT INTO ROLES(RoleName, IsSysAdmin) VALUES('Standard User', 0)

    --Create an Application Permission for the action method 'Create'

    --defined in the 'Admin' controller (ie 'admin-create')

    INSERT INTO PERMISSIONS(PermissionDescription) VALUES('admin-create')

    --Associate the Application Permission 'admin-create' with the

    --'Administrator' Role

    INSERT INTO LNK_ROLE_PERMISSION VALUES(

    (SELECT Role_Id FROM ROLES WHERE RoleName = 'Administrator'),

    (SELECT Permission_Id FROM PERMISSIONS WHERE PermissionDescription = 'admin-create'))

    The following tables display the content view of the ROLES,

    PERMISSIONS and LNK_ROLE_PERMISSION tables respectively in

    relation to the above SQL INSERT commands.

  • Page 9

    Now that we have defined an application role and assigned one application permission to the new role, we need to

    create an application user who is assigned the Administrator role. To keep things simple, we shall assume that our

    application is running as an intranet based system using Integrated Windows authentication via IIS where the user

    requesting the resource has already be authenticated. We simply use the IPrincipal object to identify the

    requesting users name to which we map their unique username to our USERS table.

    It goes without saying that only users permitted to access the intranet site would be added to the USERS table unless

    you are adopting a user registration mechanism where users would be assigned standard user role. This also

    works perfectly well for web sites not based on Integrated Windows authentication where authentication is achieved

    and tracked using authentication tokens. In either case, the user is identified via the IPrincipal object.

    Lets assume that the user to which we shall assign the application administration role has the Windows username

    swloch and that the IPrincipal.Identity.Name property for this user evaluates to somedomain\swloch.

    We need to add the user to the USERS table using Windows username (without the prepended domain name) as the

    table Username field and assign the Administrator role to the user.

    --Create the user 'swloch'

    INSERT INTO USERS(Username) VALUES('swloch')

    --Associate the 'Administrator' Role with user

    INSERT INTO LNK_USER_ROLE VALUES(

    (SELECT User_Id FROM USERS WHERE Username = 'swloch'),

    (SELECT Role_Id FROM ROLES WHERE RoleName = 'Administrator'))

    The following tables display the content view of the USERS and LNK_USER_ROLE

    tables respectively in relation to the above SQL INSERT commands.

    .

    Before a controllers action is executed, the requesting users role is determined to check whether the role contains

    the requested controller/action combination. If the role does contain the controller/action association then execution of

    the controller action is permitted resulting in the

    action result page from that controller/action being

    returned. If the role does not contain the required

    controller/action association, an invalid authorisation

    page is returned instead of the action result page.

    RBAC DatabaseRBAC Database

    User Request(Invoke

    authorization filter)

    Action Result

    http://localhost/admin/create

    YES

    Admin Page

    NO

    Unauthorized Page Response

    Unauthorized Message Page

    Authorisation CheckDoes users role(s) contains required controller-action

    association?

  • Page 10

    Action Filters In MVC, controllers define action methods that usually have a one-to-one relationship with possible user interactions,

    such as clicking a link or submitting a form. Occasionally, you want to perform logic either before an action method is

    called or after an action method runs. To support this, MVC provides action filters. Action filters are custom attributes

    that provide a declarative means to add pre-action and post-action behaviour to controller action methods.

    ASP.NET MVC provides the following types of action filters:

    Authorization filter, which makes security decisions about whether to execute an action method, such as

    performing authentication or validating properties of the request. The AuthorizeAttribute class is one example of an authorization filter.

    Action filter, which wraps the action method execution. This filter can perform additional processing, such as

    providing extra data to the action method, inspecting the return value, or cancelling execution of the action

    method.

    Result filter, which wraps execution of the ActionResult object. This filter can perform additional processing of the result, such as modifying the HTTP response. The OutputCacheAttribute class is one example of a result filter.

    Exception filter, which executes if there is an unhandled exception thrown somewhere in action method,

    starting with the authorization filters and ending with the execution of the result. Exception filters can be used

    for tasks such as logging or displaying an error page. The HandleErrorAttribute class is one example of an exception filter.

    Custom Action Filters Lets create a custom authorization filter named RBACAttribute inherited from the AuthorizeAttribute class. Our

    custom attribute extracts the requested controller name and controller action name properties from the

    AuthorizationContext object and checks whether the derived application permission exists as a permitted

    permission in any of the requesting users assigned roles using the RBACUser object.

    public class RBACAttribute : AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { //Create permission string based on the requested controller name and action name in the format 'controllername-action' string requiredPermission = String.Format("{0}-{1}", filterContext.ActionDescriptor.ControllerDescriptor.ControllerName, filterContext.ActionDescriptor.ActionName); //Create an instance of our custom user authorization object passing requesting user's 'Windows Username' into constructor RBACUser requestingUser = new RBACUser(filterContext.RequestContext.HttpContext.User.Identity.Name); //Check if the requesting user has the permission to run the controller's action if (!requestingUser.HasPermission(requiredPermission) & !requestingUser.IsSysAdmin) { //User doesn't have the required permission, return our custom 401 Unauthorized access error //Since we are setting filterContext.Result to contain an ActionResult page, the controller's action will not be run. //The custom 401 Unauthorized access error will be returned to the browser in response to the initial request. filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "action", "Index" }, { "controller", "Unauthorised" } }); } //If the user has the permission to run the controller's action, then filterContext.Result will be uninitialized and //executing the controller's action is dependant on whether filterContext.Result is uninitialized. } }

    The RBACUser object exposes the HasPermission method that accepts a permission parameter returning a bool value denoting the existence of that permission in any of the users assigned roles. If you derive a class from the AuthorizeAttribute class, the derived class must be thread safe. Therefore, do not store state information in an instance field in an instance of the class unless that state information is meant to apply to all requests. Instead, store state information per request in the Items property, which is accessible through the context objects passed to AuthorizeAttribute.

  • Page 11

    In the event where the users role(s) do not contain the required application permission, a customized 401

    Unauthorized access error is returned instead of the intended controllers action result view. The customized error

    simply returns a view, as detailed below, via the Index controller action defined in the Unauthorized controller

    invoked by the RedirectToRouteResult.

    The error text is defined in the Index.cshtml file corresponding to the Unauthorized controller and you should modify

    this file to change the pages aesthetics.

    UnauthorizedController.cs public class UnauthorisedController : Controller { // GET: Unauthorised public ActionResult Index() { Session.Abandon(); return View(); } }

    Index.cshtml corresponding to the Unauthorized controller @{ ViewBag.Title = "Unauthorised Request"; } Error 401 : Unauthorised Request You do not have permission to access the requested resource due to security restrictions. In order to gain access, please speak to your system administrator.

  • Page 12

    Restricting Access to MVC Controller Action Methods using Action Filters We can now use our custom authorization filter to restrict access to a controllers action method in our web application

    by decorating the action method with our RBACAttribute attribute; MVC allows you to omit the Attribute verb from the authorization attribute when decorating a controllers action to simply use RBAC for those who prefer this naming

    convention. Our authorization attribute instructs MVC to perform logic before the action method is called where logic

    checks whether the requesting user has been authenticated and has the required application permission to execute

    the controllers action method.

    [RBAC] public ActionResult Create() { return View(); } OR [RBACAttribute] public ActionResult Create() { return View(); }

    A good approach to security is to always place the security check as close as possible to the resource you are securing. You may have additional checks higher up the stack, but ultimately, you need to secure the actual resource. This way, no matter how the user gets to the resource, there will always be a security check in place. In this case, you dont want to rely on routing and URL authorization to secure a controller; you really need to secure the controller itself.

    Our custom RBACAttribute authorization attribute serves this purpose.

    If you dont specify any roles or users, the current user must simply be authenticated in order to call the action method. This is an easy way to block unauthenticated users from a particular controller action.

    If a user attempts to access an action method with this attribute applied and fails the authorization check, the filter causes the server to return a 401 Unauthorized HTTP status code.

  • Page 13

    Performing Conditional Processing using RBAC during Controller Action Execution Lets consider that we have a controller action method that returns a page containing employee data. This time, we

    are not decorating the controllers action with our custom RBACAttribute attribute since all users registered with our application may access this page. However, we do wish to restrict the data returned inside our controllers action

    during execution based on the requesting users role(s).

    For example, a user having the 'Standard' user role will have access to the controllers action (no authorization filter

    applied to the controllers action) but only be allowed to view the Employee's work related contact details whereas a

    user having the 'HumanResourcesManager' role will be allowed to view additional information including the

    employee's salary. Therefore, we require to check the requesting users role both in the controllers action method

    and corresponding view to determine the level of employee data returned back to the browser.

    Since our RBACUser class encapsulates the required functionality to evaluate a users roles/permissions, we need to expose this functionality to the controllers action methods and views. The simplest way is to expose our custom methods to the System.Web.Mvc.ControllerBase abstract class using extension methods. This enables our users roles/permissions functionality to be available to every controller during execution keeping code changes to an absolute minimum, even if you have numerous controllers and corresponding views since there will be no need to change every controller with a new controller class inherited from Controller which exposes our functionality. Let us extend the System.Web.Mvc.ControllerBase class to expose our RBACUser functionality necessary for

    roles/permissions management.

    public static class RBAC_ExtendedMethods { public static bool HasRole(this ControllerBase controller, string role) { bool Found = false; try { //Check if the requesting user has the specified role... Found = new RBACUser(controller.ControllerContext.HttpContext.User.Identity.Name).HasRole(role); } catch { } return Found; } public static bool HasPermission(this ControllerBase controller, string permission) { bool Found = false; try { //Check if the requesting user has the specified application permission... Found = new RBACUser(controller.ControllerContext.HttpContext.User.Identity.Name).HasPermission(permission); } catch { } return Found; } public static bool IsSysAdmin(this ControllerBase controller) { bool IsSysAdmin = false; try { //Check if the requesting user has the System Administrator privilege... IsSysAdmin = new RBACUser(controller.ControllerContext.HttpContext.User.Identity.Name).IsSysAdmin; } catch { } return IsSysAdmin; } }

  • Page 14

    We can now call our exposed functionality in any controller action and/or corresponding view through the controllers

    context object as illustrated below.

    Controller Action (EmployeeController.cs)

    RBACUser functionality exposed via our RBAC_ExtendedMethods class can be used in controller actions.

    Controller Action View (Index.cshtml)

    RBACUser functionality exposed via our RBAC_ExtendedMethods class can be used in views.

  • Page 15

    Using RBAC in a Controllers Action Method The following listing illustrates the use of our custom HasRole and HasPermission methods, exposed in our RBACUser class, in the controllers action through the controllers context object. We have extended these methods to the controllers context object using extension methods defined in our RBAC_ExtendedMethods class. public class EmployeeController : Controller { // GET: Employee public ActionResult Index() { if (this.HasRole("HumanResourcesManager")) { //This code block is permitted as the requesting user has the 'HumanResourcesManager' role assigned. //Perform additional tasks and/or extract additional data from the database into the controller's //view model/viewbag in order to be passed down to the controller's view. } if (this.HasPermission("ViewRestrictedHRData")) { //This code block is permitted as the requesting user has the 'ViewRestrictedHRData' permission assigned. //We can also define role functionality permissions not related to controller-action access. //Extract salary data from database into controller's view model/viewbag... } return View(); } }

    Without having to alter our EmployeeController class, we now have our RBACUser functionality exposed through the controllers context object using extension methods. This is also true for the controllers view.

  • Page 16

    Using RBAC in a Controllers View The following listing illustrates the use of our custom HasRole and HasPermission methods. We have extended these methods to the controllers context object using extension methods defined in our RBAC_ExtendedMethods class. @{ ViewBag.Title = "Employee Page"; } @{ if (ViewContext.Controller.HasRole("HumanResourcesManager")) { Use this area to provide additional information and/or display additional data provided in the model/viewbag by the controller's action as the user has the "HumanResourcesManager" role assigned. } if (ViewContext.Controller.HasPermission("ViewRestrictedHRData")) { Use this area to provide additional information and/or display additional data provided in the model/viewbag by the controller's action as the user has the "ViewRestrictedHRData" permission assigned. } } Use this area to provide standard information.

    Our RBAC_ExtendedMethods class provides us with a flexible framework which enables us to extend our custom RBAC functionality with minimal effort. Our extended RBAC methods are automatically exposed to every controller action and corresponding view in our application through the controllers context object without having to change the controller classes in anyway (except for the use of our newly exposed functionality).

  • Page 17

    Using RBAC to Dynamically Control Menus

    Our custom HasPermission, HasRole and IsSysAdmin methods come in useful when displaying dynamic menu

    items. Recall that each role in our system will have a number of application controller-action associations defined

    each representing a controllers name and controllers action name. Consider the application menu items displayed

    below.

    If we need to dynamically display menu items based on the requesting users role permissions, we can simply refer to the menus target controller name and controller action as the permission in the format controller-action. For example, if we require the Import Data menu to be visible only to users allowed access to the underlying controllers action, we would simply wrap our custom HasPermission method around the menu item definition (generally found in the _Layout.cshtml view) as illustrated below passing the menus target controller name and controller action as the permission to be checked. ... @{ if (ViewContext.Controller.IsSysAdmin()) { System Administration ... } } @{ if (ViewContext.Controller.HasPermission("data-import")) { @Html.ActionLink("Import Data", "Import", "Data") } } @Html.ActionLink("About", "About", "Home") @Html.ActionLink("Contact", "Contact", "Home") @Html.ActionLink("Home", "Index", "Home") ...

    The custom HasPermission method will check the requesting users role(s) for the permission data-import and display accordingly. Additionally, we can also make use of the IsSysAdmin method to check whether the requesting user has a role that has the IsSysAdmin property enabled. Therefore, a user that doesnt have a System Administrator role nor a role defining the data-import permission will see the following menu items displayed (based on the illustrated code snippet) as opposed to the menu items displayed above.

    However, if a user enters a URL directly (eg http:///Data/Import) and does not have the required permission, a customized 401 Unauthorized access error is returned instead of the intended controllers action result view providing we have decorated the controllers action or controller class with our RBACAttribute. Associating the data-import permission to the users role will automatically display the corresponding menu.

  • Page 18

    Persisting RBAC Data via ADO.NET Entity Framework (EF) Because every request to a controller action decorated with our RBACAttribute authorization attribute requires verification of the requesting users permitted application permissions stored in our RBAC database, the users roles/permissions data should be read from the RBAC database once and then stored persistently for maximum performance. Busy websites can be exposed to thousands of requests per minute and reading the requesting users roles/permissions data from the RBAC database for every request would seriously hinder the websites performance. ADO.NET Entity Framework (EF) is a component of the .NET Framework which provides a data persistence layer using a conceptual model, called the Entity Data Model (EDM), which sits on top of the database schema. EF applications can run on any computer on which the .NET Framework (starting with version 3.5 SP1) is installed. EF persists data in-memory using a data model that is mapped to underlying database tables; data changes to the underlying tables are automatically detected and automatically refreshed in the persistent data model. This makes EF an ideal candidate for storing our RBAC data (even if your existing application isnt using EF) since performance will be dramatically increased as the RBAC data isnt read from the underlying database every time we query the data model. Once our roles/permissions are correctly configured, updates will be infrequent.

  • Page 19

    Creating an Entity Framework (EF) RBAC Model Lets create a new EF model to store our RBAC data which will be used by our RBACUser object. Add a new

    ADO.NET Entity Data Model item to your existing project as detailed below (unless you already have a model in

    which case you simply need to add the table associations into your models OnModelCreating method).

    Name your database connection string accordingly. The name

    specified will be stored in the applications Web.Config file as a

    database connection string located in the

    configuration section as detailed below.

  • Page 20

    Generated Entity Framework (EF) RBAC Model The following database context model and corresponding database entities are generated from our RBAC database

    tables; we access the applications RBAC data through the context model.

    namespace RBAC.Models { public partial class RBAC_Model : DbContext { public RBAC_Model(): base("name=RBAC_Model") { } public virtual DbSet Permissions { get; set; } public virtual DbSet Roles { get; set; } public virtual DbSet Users { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity() .HasMany(e => e.Roles) .WithMany(e => e.Permissions) .Map(m => m.ToTable("LNK_ROLE_PERMISSION").MapLeftKey("Permission_Id").MapRightKey("Role_Id")); modelBuilder.Entity() .HasMany(e => e.Users) .WithMany(e => e.Roles) .Map(m => m.ToTable("LNK_USER_ROLE").MapLeftKey("Role_Id").MapRightKey("User_Id")); } } }

    [Table("USERS")] public partial class User { public User() { Roles = new HashSet(); } [Key] public int User_Id { get; set; } public string Username { get; set; } public virtual ICollection Roles { get; set; } } [Table("ROLES")] public partial class Role { public Role() { Permissions = new HashSet(); Users = new HashSet(); } [Key] public int Role_Id { get; set; } public string RoleName { get; set; } public string RoleDescription { get; set; } public bool IsSysAdmin { get; set; } public virtual ICollection Permissions { get; set; } public virtual ICollection Users { get; set; } } [Table("PERMISSIONS")] public partial class Permission { public Permission() { Roles = new HashSet(); } [Key] public int Permission_Id { get; set; } public string PermissionDescription { get; set; } public virtual ICollection Roles { get; set; } }

  • Page 21

    The generated model has identified and defined three entities (User, Role and Permission) from our RBAC database

    and defined the RBAC_Model context model which links the entity relationships together. The OnModelCreating

    method on the RBAC_Model context model class is called during the database context model creation where model

    entity relationships are created which correspond to the underlying database tables including table index keys and

    referential integrity constraints.

    NOTE: If you already have an Entity Data Model (EDM) in your existing application, simply add the generated entity

    classes (Users, Roles and Permissions) to your project and define the corresponding DbSet for these entities in your

    model including the entity relationships defined in the OnModelCreating method. So long as the RBAC tables are

    added to your existing database, there will be no need to add a database connection string to the applications

    Web.Config since your application will already have a connection string defined for your existing EDM.

    Extracting Data from our Entity Framework (EF) RBAC Model

    To create an instance of our RBAC EF context model, we simply create an instance of the RBAC_Model class from

    which we then search against the database entities (ie Users, Roles and Permissions). The following listing illustrates

    the creation of our context model and a search against the Users table for swloch.

    //Create an instance of the RBAC Entity Data Model (EDM)... using (RBAC_Model _data = new RBAC_Model()) { User _user = _data.Users.Where(u => u.Username == swloch).FirstOrDefault(); if (_user != null) { //User found, access roles/permissions via exposed properties... foreach (Role _role in _user.Roles) { foreach (Permission _permission in _role.Permissions) { if (_permission.PermissionDescription == 'admin-create') { } } } } }

    The first time a DbContext is created, is pretty expensive but once the object has been created much of the

    information is cached so that subsequent instantiations are significantly quicker. You are more likely to see

    performance problems from keeping a context object around than you are from instantiating one each time you need

    access to your database. If you keep a context object around it will keep track of all the updates, additions, deletes

    etc and this will slow your application down and may even cause subtle bugs to appear in your application.

    The context object should be created per request. Create the context object, do what you need to do with the object

    and then get rid of it. Do not try and have a global context (this is not how web applications work).

  • Page 22

    Reading User Roles/Permissions from our Database We will now take a closer look at the GetDatabaseUserRolesPermissions() method used in our RBACUser class.

    Code Listing for Entity Framework (EF) RBAC Data Retrieval The following listing details the GetDatabaseUserRolesPermissions() method which extracts the requesting users RBAC data from the underlying database using ADO.NET Entity Framework. When a new instance of the RBAC

    context model is created for the very first time, data will be retrieved from the database and stored in-memory.

    Therefore, subsequent new instances of the RBAC context model will not require data to be loaded directly from the

    underlying database since EF will use the data already stored in-memory. Any underlying changes to the data in the

    database are detected by EF and reloaded when we next access the data thus ensuring data retrieval from our

    persistence data layer is always up-to-date.

    private void GetDatabaseUserRolesPermissions() { using (RBAC_Model _data = new RBAC_Model()) { User _user = _data.Users.Where(u => u.UserName == this.Username).FirstOrDefault(); if (_user != null) { this.User_Id = _user.Id; foreach (Role _role in _user.Roles) { UserRole _userRole = new UserRole { Role_Id = _role.Id, RoleName = _role.RoleName }; foreach (Permission _permission in _role.Permissions) { _userRole.Permissions.Add(new RolePermission { Permission_Id = _permission.Id, PermissionDescription = _permission.PermissionDescription }); } this.Roles.Add(_userRole); if (!this.IsSystemAdmin) this.IsSystemAdmin = _role.IsSysAdmin; } } } }

  • Page 23

    Extending the RBAC Framework If we wish to extend our RBAC framework with customized methods, we simply add new methods to the RBACUser

    class; these methods could be associated with new fields added to the USERS database table or associated tables.

    For example, we could add a new column to the USERS database table called Title which represents the users

    name title (eg Mr, Prof, Dr etc). By adding the new property Title to our RBACUser class as highlighted below, the

    property will be automatically loaded from the database into our RBACUser object by Entity Framework.

    We could then expose a new method in our RBACUser class called IsDoctor() which checks for the value Dr

    returning a bool (true if value equals Dr otherwise false). Although this is an unlikely real world example, it

    demonstrates the concept.

    public class RBACUser { public int User_Id { get; set; } public bool IsSysAdmin { get; set; } public string Username { get; set; } public string Title { get; set; } public bool IsDoctor() { return (this.Title == "Dr"); } ... ... }

    In order to expose our new functionality to the applications controller actions and controller views, we must wrap our

    new functionality in new methods in our RBAC_ExtendedMethods class (ie our extension methods).

    public static class RBAC_ExtendedMethods { public static bool IsDoctor(this ControllerBase controller) { bool IsDoctor = false; try { //Check if the requesting user has the specified role... IsDoctor = new RBACUser(controller.ControllerContext.HttpContext.User.Identity.Name).IsDoctor(); } catch { } return IsDoctor; } ... ... }

    We have now extended our RBAC framework with our customized method IsDoctor(). We can now use the new

    method in our controller action via this.IsDoctor() syntax or in our controller view via

    ViewContext.Controller.IsDoctor() syntax.

    Note: Code snippets used in the above examples are minimal highlighting additional code only in order to focus on

    the topic at hand.

  • Page 24

    Sample Project The sample project available for download implements the AdminController which provides the necessary RBAC

    administration. Simply adding this controller, accompanying views contained in the Admin folder and RBAC model to

    any existing MVC project will provide the necessary RBAC administration functionality. Integrating the RBAC

    administration functionality into an existing ASP.NET MVC project is discussed in the next section.

    The RBAC administration is exposed via the System Administration menu as displayed below and will only be

    visible to a user having a role that has the IsSysAdmin option enabled; the

    menu item definition must be contained within the IsSysAdmin function to

    display dynamically as discussed in the Using RBAC to Dynamically Control

    Menus section. The menu style is driven by CSS and can be easily modified to

    follow any application theme.

    Before we create any application roles, we need to create permissions

    associated with our application. Depending on which areas of your application

    you need to restrict using role based access, there may be a large number of

    controller action methods which translate to application permissions. Entering

    each controller action as a permission into your application can be a dull and time consuming task. To aid in the

    creation of your application permissions, the Permissions screen contains a button labeled Import Permissions.

    Application Permissions

    The import permissions function uses the .NET Frameworks Reflection API which enables the fetching of assembly

    type information at runtime. The function iterates through the assemblys MVC controller methods and saves each

    controller-action to the permissions database table.

  • Page 25

    Application Roles

    Once the applications permissions have been defined, we are in a position to create user roles. User roles are

    typically associated with one or more application permissions. Your application business rules should define which

    roles in your application should access which areas. Clicking on the Roles menu will display your applications roles

    (where defined) and enable CRUD actions on the roles to be undertaken.

    Once an application role has been created, application permissions can then be assigned to the role. Permissions

    can be associated and disassociated with a role at any time. Associating permissions with roles can be a time

    consuming task. To aid in the role-permission association process, the Add All Permissions button associates all

    permissions with the role in a single action; unwanted permissions can then be disassociated using the trash icon.

    Alternatively, individual permissions can be selected from the dropdown and added via the Add Permission button.

  • Page 26

    NOTE: Permanently deleting an application permission via the Permissions screen will automatically remove the

    permission from associated roles.

    Application Users

    Once the applications roles have been defined, Users can be created and assigned roles.

    Roles can be associated and disassociated with a user at any time. Individual roles are assigned to a user by

    selecting the role from the dropdown and pressing the Add Role button; unwanted roles can be unassigned using

    the trash icon.

    NOTE: Permanently deleting an application role via the Roles screen will automatically remove the role from

    associated users.

  • Page 27

    Adding RBAC to Existing MVC Applications

    Adding the RBAC functionality to an existing application will require the following steps to be undertaken:-

    1. Using Solution Explorer in Visual Studio, create a new

    folder in your project called Action Filters and add the

    RBACAttribute.cs file by right-clicking on the newly

    created folder and selecting Add >> Existing Item.

    This file defines the RBACAttribute used to decorate

    controller action methods.

    Add the file RBAC_ExtendedMethods.cs to your Action

    Filters folder in the same manner as above. This file

    defines the class RBAC_ExtendedMethods which

    defines the extension methods HasRole,

    HasPermission and IsSysAdmin

    2. Using Solution Explorer in Visual Studio, create a new

    folder in your project inside the Content folder called

    Images and add the grid icons (bin.png and pen.png)

    unless you prefer to use images already present in your

    application in which case you will need to alter the

    image references to point to your preferred images.

    3. Using Solution Explorer in Visual Studio, create a new

    folder in your project inside the Content folder called

    themes and add the file jquery-ui-1.10.3.min.css

    including the subdirectories ui-lightness and images

    as detailed opposite; ensure you include the

    corresponding files from each folder. The applied

    theme, named ui-lightness, was downloaded from

    http://jqueryui.com but you can easily replace or remove

    this theme.

    4. Using Solution Explorer in Visual Studio, add both the

    AdminController.cs and

    UnauthorisedController.cs files to your

    Controllers folder by right-clicking and selecting Add

    >> Existing Item. These files define the RBAC

    System Administration controller functionality.

    5. Using Solution Explorer in Visual Studio, add the files

    PERMISSIONS.cs, ROLE.cs, USER.cs and

    RBAC_Model.cs to your Models folder. These files

    define the RBAC_Model data model and corresponding

    data model types (User, Role, Permission).

    6. Using Solution Explorer in Visual Studio, create a new

    folder in your applications Views folder called Admin

    and add the view files as detailed in the RBACDemo

    project by right-clicking on the newly created folder and

    selecting Add >> Existing Item. These views are

    associated with the Admin controller actions. If your

    project already contains an Admin controller, you can

    rename the RBAC Admin controller and move the

    associated views to the new corresponding folder.

    Links which reference the Admin controller will need

  • Page 28

    changing to reference the newly named controller. These links are contained in both the controller itself and

    the corresponding views.

    7. Using Solution Explorer in Visual Studio, create a new folder in your applications Views folder called

    Unauthorised and add the view file Index.cs by right-clicking on the newly created folder and selecting

    Add >> Existing Item.

    8. Include the System Administration menu in your _Layout.cshtml file using your existing menu CSS

    styling. If you prefer the illustrated menu styling, copy the necessary CSS from the sample Site.css file to

    your projects Site.css file. This also applies to the views; incorporate your own styling.

    9. The Scripts folder references the jquery-ui-1.10.3.custom.min.js file but you can freely download your own

    custom themed version from http://jqueryui.com and reference accordingly.

    http://jqueryui.com

    If you do download your own custom version of jQuery UI, using Solution Explorer in Visual Studio, add the file

    jquery-ui-1.10.x.custom.min.js to the Scripts folder otherwise the jQuery dialog will not render correctly.

  • Page 29

    jQuery User Interface

    The jQuery User Interface can be freely downloaded from http://jqueryui.com where custom themes can be

    generated. The easiest way is to use the ThemeRoller to build a theme. This application will generate a jquery-ui-

    1.10.x.custom.min.css file, an images directory containing all the necessary background images and icon sprites

    and a jquery-ui-1.10.x.custom.min.js file which can simply be dropped into your project.

    The jquery-ui-1.10.x.custom.min.css and jquery-ui-1.10.x.custom.min.js files require to be referenced in your

    _Layout.cshtml file as follows.

    NOTE: If you download a custom theme based on jQuery UI Version 1.11.x, file names will differ (ie jquery-ui-

    theme.min.css and jquery-ui.min.js)

    _Layout.cshtml RBAC Demo @Scripts.Render("~/bundles/modernizr") ...

    The applied theme, named ui-lightness, was downloaded from http://jqueryui.com.

    The following screenshot illustrates a correctly configured dialog box.

  • Page 30

    Removing the applied theme will prevent dialog boxes (used for deleting grid items) from rendering correctly.

    Although the dialog box will display and still operate, it will be displayed incorrectly. The following screenshot

    illustrates an incorrectly displayed dialog box due to a theme being incorrectly configured or missing.

    However, you may wish to remove the jQuery User Interface completely so that the user is not prompted before deleting the item or you can provide an alternative interface for the dialog box.

    Removing jQuery User Interface References

    If you prefer not to use the jQuery User Interface, simply remove the jquery-ui-xxx.css file reference from your

    _Layout.cshtml file and delete the themes folder located inside the Content folder. Where a jQuery UI dialog box

    was previously invoked, modify the code as illustrated in the following example using the correct controller action

    method and corresponding parameters.

    ... @foreach (var item in Model) { @Html.ActionLink(item.RoleDescription, "RoleDetails", new { id = item.Role_Id }) ... } ...

    The trash icon displayed in the grid will now call the controller action method directly passing the required parameters.

    However, no dialog warning will be displayed to the user thus preventing the user from cancelling the delete operation.

    Alternatively, you could invoke a JavaScript function, utilizing the JavaScript alert keyword, via the onclick event

    which will display a basic dialog warning not reliant on the jQuery User Interface. It really comes down to how

    sophisticated you wish to make your User Interface.

    There are pros and cons to each of these methods, and depending on your product, market, and niche, one may be

    more suitable for you than the others.

  • Page 31

    RBAC Authentication/Authorisation Overview The IIS User Authentication process is undertaken by IIS to provide an additional layer of security. Since the web

    application is intranet based and will run within a Windows domain, IIS will check that a user making an inbound

    request to the web server has been authenticated. If a user is not authenticated, IIS reroutes the inbound request to

    an Authentication Login dialog box requesting alternative user credentials. If a user is authenticated, the inbound

    request is routed to the MVC web application where authorisation checks are undertaken as described in earlier

    sections.

    The following diagram illustrates the RBAC Authentication/Authorisation process.

    DatabaseDatabase

    Filename

    Transfer Date

    Username

    Role_Id

    ROLES

    User_IdRole_Id

    LNK_USER_ROLE

    User_IdIsSysAdminUsername

    USERS

    Role_IdPermission_Id

    LNK_ROLE_PERMISSION

    Permission_Id

    PERMISSIONS

    User Request(Invoke

    authorization filter)

    Action Result

    http://localhost/admin/create

    YES

    Create User PageUnauthorized Page

    Response

    Unauthorized Message Page

    IIS ASP.NET MVC RBAC DATABASEHTTP GET REQUEST

    Create User Page Response

    Unauthorized Page Response

    Error 401: Unauthorized Request

    You do not have permission to access the requested resource due to security restrictions. In order to gain access, please speak with your system administrator.

    Unauthorized Request X

    User Data

    ENTITY FRAMEWORK

    HTT

    P R

    ESP

    ON

    SE

    User NOT Authenticated

    Display User Authentication

    dialog box

    User ISAuthenticated

    Save

    Admin X

    Create User

    Is User Authenticated?

    Authorisation CheckDoes users role(s) contains required controller-action

    association?

    NO

    RBACUser Object

    User Data

    An inbound request to our web application is initially handled by IIS which authenticates the user against the active

    directory group via an Authentication Login dialog box if not authenticated. If the user is authenticated, the request is

    forwarded to the MVC web application which checks the users rights and roles. A users right will decide whether the

    requested controller/action can be processed. A user is stored as an object in the Entity Framework layer which

    populates the users data from the database tables.

  • Page 32

    Conclusion This solution forms an ideal framework for any intranet application that requires dynamic self-contained Roles Based

    Access Control (RBAC) that is specific to the application and independent of ASP.NET Roles and Membership and

    Identity Management providers (IdM) such as Microsoft Identity Integration Server (MIIS). The framework can be

    added to existing projects as well as new developments and once deployed will be self-maintaining and regulating via

    the applications system administrator with little or no reliance on the application developer.

    This solution is particularly suited for corporate intranet applications where limited access to the deployed web server

    is granted or the administration of user roles including role assignment is delegated away from the application system

    administrator or owner.

    In the next post, the framework will be extended to incorporate role based reporting.


Recommended