Skip to main content
A newer version of this page is available. .
.NET Framework 4.5.2+

Implement Custom Security Objects (Users, Roles, Operation Permissions)

  • 14 minutes to read

This example illustrates how to create custom security objects such as permissions, roles, and users. You can implement a permission an administrator uses to allow or deny users to export data in an XAF application. Each role exposes the CanExport property (a custom role object is implemented for this purpose). When the corresponding checkbox is checked, users associated with this role have access to the ExportController.ExportAction Action.

Blazor applications do not support the ExportAction. However, you can implement the approach described in this topic to secure other operations.

Note

This example applies to XPO applications only. You should modify the code snippets in this topic to use it in EF 6 or EF Core applications:

Refer to the following help topics for more information about the EF 6 and EF Core data model design:

Tip

A complete sample project is available in the DevExpress Code Examples database at https://supportcenter.devexpress.com/ticket/details/e3794/xaf-how-to-implement-custom-permission-role-and-user-objects.

Implement Custom Role and User Objects

  • Extend the PermissionPolicyRole class with an additional CanExport property. This indicates that a role has ExportPermission.

    using DevExpress.Persistent.BaseImpl.PermissionPolicy;
    // ...
    [DefaultClassOptions, ImageName("BO_Role")]
    public class ExtendedSecurityRole : PermissionPolicyRole {
        public ExtendedSecurityRole(Session session) : base(session) { }
        public bool CanExport {
            get { return GetPropertyValue<bool>(nameof(CanExport)); }
            set { SetPropertyValue<bool>(nameof(CanExport), value); }
        }
    }
    
  • Place the Can Export item on the ExtendedSecurityRole Detail View layout (see View Items Layout Customization).

    CustomPermission_ModelEditor

  • The code snippet below demonstrates the custom PermissionPolicyUser object’s implementation. You can execute this additional task to make this example more realistic. However, customizing the PermissionPolicyUser‘s implementation to support the ExportPermission is not required.

    using DevExpress.Persistent.BaseImpl.PermissionPolicy;
    // ...
    [DefaultClassOptions, ImageName("BO_Employee")]
    public class Employee : PermissionPolicyUser {
        public Employee(Session session)
            : base(session) { }
        [Association("Employee-Task")]
        public XPCollection<Task> Tasks {
            get { return GetCollection<Task>(nameof(Tasks));  }
        }
    }
    [DefaultClassOptions, ImageName("BO_Task")]
    public class Task : BaseObject {
        public Task(Session session)
            : base(session) { }
        private string subject;
        public string Subject {
            get { return subject; }
            set { SetPropertyValue(nameof(Subject), ref subject, value); }
        }
        private DateTime dueDate;
        public DateTime DueDate {
            get { return dueDate; }
            set { SetPropertyValue(nameof(DueDate), ref dueDate, value); }
        }
        private Employee assignedTo;
        [Association("Employee-Task")]
        public Employee AssignedTo {
            get { return assignedTo; }
            set { SetPropertyValue(nameof(AssignedTo), ref assignedTo, value); }
        }
    }
    
  • Add the Can Export column to the Roles Nested List View displayed on the User Detail View (see Adjust the Administrative UI):

  • To use the custom ExtendedSecurityRole role and custom Employee user instead of the default role and user, modify the SecurityStrategyComplex.RoleType and SecurityStrategy.UserType values, as shown below.

    WinForms
    File: WinApplication.Designer.cs (WinApplication.Designer.vb).

    partial class MainDemoWindowsFormsApplication {
        // ...
        private void InitializeComponent() {
            // ...
            this.securityStrategyComplex1.Authentication = this.authenticationStandard1;
            this.securityStrategyComplex1.RoleType = typeof(ExtendedSecurityRole);
            this.securityStrategyComplex1.UserType = typeof(Employee);
            // ...
        }
    }
    

    ASP.NET Web Forms
    File: WebApplication.cs (WebApplication.vb).

    public class MainDemoWebApplication : WebApplication {
        // ...
        private void InitializeComponent() {
            // ...
            this.securityStrategyComplex1.Authentication = this.authenticationStandard1;
            this.securityStrategyComplex1.RoleType = typeof(ExtendedSecurityRole);
            this.securityStrategyComplex1.UserType = typeof(Employee);
            // ...
        }
    }
    

    Blazor
    File: Startup.cs.

    public class Startup {
        // ...
        public void ConfigureServices(IServiceCollection services) {
            // ...
            services.AddXafSecurity(options => {
                options.RoleType = typeof(ExtendedSecurityRole);
                options.UserType = typeof(Employee);
                // ...
            })
            // ...
        }
    }
    

    In .NET Framework applications, you can alternatively invoke the Application Designer and drag the SecurityStrategyComplex and AuthenticationStandard components from the Toolbox onto the Security pane. Modify the SecurityStrategyComplex.RoleType and SecurityStrategy.UserType values in the Properties window to use the implemented ExtendedSecurityRole and Employee objects instead of the default PermissionPolicyUser and PermissionPolicyRole.

    CustomPermission_AppDesigner

Implement Custom Operation Permission and Permission Request

  • Add a class that supports the IOperationPermission interface to implement a custom Operation Permission.

    using DevExpress.ExpressApp.Security;
    // ...
    public class ExportPermission : IOperationPermission {
        public string Operation { 
            get { return "Export"; }
        }
    }
    
  • The Security System uses Permission Requests to determine whether permission is granted. To add a Permission Request for the ExportPermission, implement the IPermissionRequest interface as follows:

    public class ExportPermissionRequest : IPermissionRequest {
        public object GetHashObject() {
            return this.GetType().FullName;
        }
    }
    

Implement the Permission Request Processor and Register it within the Security Strategy

  • All Permission Requests should have an appropriate Permission Request Processor registered in the Security Strategy. Inherit the PermissionRequestProcessorBase<ProcessorPermissionRequestType> class and pass the Permission Request type as the ancestor class’ generic parameter to implement a class as a processor.

    public class ExportPermissionRequestProcessor : 
        PermissionRequestProcessorBase<ExportPermissionRequest> {
        private IPermissionDictionary permissions;
        public ExportPermissionRequestProcessor(IPermissionDictionary permissions) {
            this.permissions = permissions;
        }
        public override bool IsGranted(ExportPermissionRequest permissionRequest) {
            return (permissions.FindFirst<ExportPermission>() != null);
        }
    }
    
  • Handle the SecurityStrategy.CustomizeRequestProcessors event in the Program.cs (Program.vb) file from the WinForms application project, in the Global.asax.cs (Global.asax.vb) file from the ASP.NET Web Forms, and in the Startup.cs file from the ASP.NET Core Blazor application project to register the ExportPermissionRequestProcessor. Subscribe to this event before the XafApplication.Setup method is called. There is no need to handle this event on the client side when the Middle Tier application server is in use. Instead, subscribe to this event in the server’s Program.cs (Program.vb) file. In the event handler, pass the ExportPermission object to the PermissionDictionary.

    Windows Forms

    ((SecurityStrategy)winApplication.Security).CustomizeRequestProcessors +=
        delegate(object sender, CustomizeRequestProcessorsEventArgs e) {
            List<IOperationPermission> result = new List<IOperationPermission>();
            SecurityStrategyComplex security = sender as SecurityStrategyComplex;
            if (security != null) {
                Employee user = security.User as Employee;
                if (user != null) {
                    foreach (ExtendedSecurityRole role in user.Roles) {
                        if (role.CanExport) {
                            result.Add(new ExportPermission());
                        }
                    }
                }
            }
            IPermissionDictionary permissionDictionary = new PermissionDictionary((IEnumerable<IOperationPermission>)result);
            e.Processors.Add(typeof(ExportPermissionRequest), new ExportPermissionRequestProcessor(permissionDictionary));
        }; 
    winApplication.Setup();
    winApplication.Start();
    

    ASP.NET Web Forms

    ((SecurityStrategy)WebApplication.Instance.Security).CustomizeRequestProcessors +=
        delegate(object s, CustomizeRequestProcessorsEventArgs args) {
            List<IOperationPermission> result = new List<IOperationPermission>();
            SecurityStrategyComplex security = s as SecurityStrategyComplex;
            if (security != null) {
                Employee user = security.User as Employee;
                if (user != null) {
                    foreach (ExtendedSecurityRole role in user.Roles) {
                        if (role.CanExport) {
                            result.Add(new ExportPermission());
                        }
                    }
                }
            }
            IPermissionDictionary permissionDictionary = new PermissionDictionary((IEnumerable<IOperationPermission>)result);
            args.Processors.Add(typeof(ExportPermissionRequest), new ExportPermissionRequestProcessor(permissionDictionary));
        };
    WebApplication.Instance.Setup();
    WebApplication.Instance.Start();
    

    Blazor

    public class Startup {
        // ...
        public void ConfigureServices(IServiceCollection services) {
            // ...
            services.AddXafSecurity(options => {
                // ...
                options.Events.OnSecurityStrategyCreated = securityStrategy => {
                    // ...
                    ((SecurityStrategy)securityStrategy).CustomizeRequestProcessors += delegate (object sender, CustomizeRequestProcessorsEventArgs e) {
                        List<IOperationPermission> result = new List<IOperationPermission>();
                        SecurityStrategyComplex security = sender as SecurityStrategyComplex;
                        if (security != null) {
                            Employee user = security.User as Employee;
                            if (user != null) {
                                foreach (ExtendedSecurityRole role in user.Roles) {
                                    if (role.CanExport) {
                                        result.Add(new ExportPermission());
                                    }
                                }
                            }
                        }
                        IPermissionDictionary permissionDictionary = new PermissionDictionary((IEnumerable<IOperationPermission>)result);
                        e.Processors.Add(typeof(ExportPermissionRequest), new ExportPermissionRequestProcessor(permissionDictionary));
                    };
                };
            })
            // ...
        }
    }
    

    Application Server

    static void Main(string[] args) {
        // ...
        Func<IDataServerSecurity> dataServerSecurityProvider = () => {
            SecurityStrategyComplex security = new SecurityStrategyComplex(
                typeof(Employee), typeof(ExtendedSecurityRole), new AuthenticationStandard());
            security.CustomizeRequestProcessors +=
                delegate(object sender, CustomizeRequestProcessorsEventArgs e) {
                    List<IOperationPermission> result = new List<IOperationPermission>();
                    if (security != null) {
                        Employee user = security.User as Employee;
                        if (user != null) {
                            foreach (ExtendedSecurityRole role in user.Roles) {
                                if (role.CanExport) {
                                    result.Add(new ExportPermission());
                                }
                            }
                        }
                    }
                    IPermissionDictionary permissionDictionary = new PermissionDictionary((IEnumerable<IOperationPermission>)result);
                    e.Processors.Add(typeof(ExportPermissionRequest), new ExportPermissionRequestProcessor(permissionDictionary));
                }; 
            return security;
        };
        // ...
    }
    

    In the Middle Tier Security scenario, you should additionally use the static WcfDataServerHelper.AddKnownType method to register your custom permission request. Call this method in the application server and client application code before the data server and client application are initialized.

    WcfDataServerHelper.AddKnownType(typeof(ExportPermissionRequest));
    

Take the Custom Permission into Account in the ExportController Controller

Add the following View Controller to inform the ExportController controller about the ExportPermission permission:

public class SecuredExportController : ViewController {
    protected override void OnActivated() {
        base.OnActivated();
        ExportController controller = Frame.GetController<ExportController>();
        if (controller != null) {
            controller.ExportAction.Executing += ExportAction_Executing;
            if(SecuritySystem.Instance is IRequestSecurity) {
                controller.Active.SetItemValue("Security", 
                    Application.GetSecurityStrategy().IsGranted(new ExportPermissionRequest()));
            }
        }
    }
    void ExportAction_Executing(object sender, System.ComponentModel.CancelEventArgs e) {
        if (!Application.GetSecurityStrategy().IsGranted(new ExportPermissionRequest())) {
            throw new UserFriendlyException("Export operation is prohibited.");
        }
    }
}
  • The code above deactivates the controller for users who are not allowed to export data. The ActionBase.Executing event handler demonstrates how to throw an exception when a user attempts to perform a prohibited operation.

Add Demo Data

Add the predefined User and Role objects (see Client-Side Security (2-Tier Architecture)) in the ModuleUpdater.UpdateDatabaseAfterUpdateSchema method as shown below.

public class Updater : ModuleUpdater {
    public Updater(IObjectSpace objectSpace, Version currentDBVersion) : 
        base(objectSpace, currentDBVersion) { }
    public override void UpdateDatabaseAfterUpdateSchema() {
        base.UpdateDatabaseAfterUpdateSchema();
        ExtendedSecurityRole defaultRole = CreateUserRole();
        ExtendedSecurityRole administratorRole = CreateAdministratorRole();
        ExtendedSecurityRole exporterRole = CreateExporterRole();
        Employee userAdmin = ObjectSpace.FirstOrDefault<Employee>(employee => employee.UserName == "Admin");
        if (userAdmin == null) {
            userAdmin = ObjectSpace.CreateObject<Employee>();
            userAdmin.UserName = "Admin";
            userAdmin.IsActive = true;
            userAdmin.SetPassword("");
            userAdmin.Roles.Add(administratorRole);
        }
        Employee userSam = ObjectSpace.FirstOrDefault<Employee>(employee => employee.UserName == "Sam");
        if (userSam == null) {
            userSam = ObjectSpace.CreateObject<Employee>();
            userSam.UserName = "Sam";
            userSam.IsActive = true;
            userSam.SetPassword("");
            userSam.Roles.Add(exporterRole);
            userSam.Roles.Add(defaultRole);
        }
        Employee userJohn = ObjectSpace.FirstOrDefault<Employee>(employee => employee.UserName == "John");
        if (userJohn == null) {
            userJohn = ObjectSpace.CreateObject<Employee>();
            userJohn.UserName = "John";
            userJohn.IsActive = true;
            userJohn.Roles.Add(defaultRole);
            for (int i = 1; i <= 10; i++) {
                string subject = string.Format("Task {0}",i);
                Task task = ObjectSpace.FirstOrDefault<Task>(t => t.Subject = subject);
                if (task == null) {
                    task = ObjectSpace.CreateObject<Task>();
                    task.Subject = subject;
                    task.DueDate = DateTime.Today;
                    task.Save();
                    userJohn.Tasks.Add(task);
                }
            }
        }
        ObjectSpace.CommitChanges();
    }
    private ExtendedSecurityRole CreateAdministratorRole() {
        ExtendedSecurityRole administratorRole = ObjectSpace.FirstOrDefault<ExtendedSecurityRole>(role => role.Name == SecurityStrategyComplex.AdministratorRoleName);
        if (administratorRole == null) {
            administratorRole = ObjectSpace.CreateObject<ExtendedSecurityRole>();
            administratorRole.Name = SecurityStrategyComplex.AdministratorRoleName;
            administratorRole.IsAdministrative = true;
        }
        return administratorRole;
    }
    private ExtendedSecurityRole CreateExporterRole() {
        ExtendedSecurityRole exporterRole = ObjectSpace.FirstOrDefault<ExtendedSecurityRole>(role => role.Name == "Exporter");
        if (exporterRole == null) {
            exporterRole = ObjectSpace.CreateObject<ExtendedSecurityRole>();
            exporterRole.Name = "Exporter";
            exporterRole.CanExport = true;
        }
        return exporterRole;
    }
    private ExtendedSecurityRole CreateUserRole() {
        ExtendedSecurityRole userRole = ObjectSpace.FirstOrDefault<ExtendedSecurityRole>(role => role.Name == "Default");
        if (userRole == null) {
            userRole = ObjectSpace.CreateObject<ExtendedSecurityRole>();
            userRole.Name = "Default";
            userRole.SetTypePermission<Task>(SecurityOperations.FullAccess, SecurityPermissionState.Allow);
            userRole.SetTypePermission<Employee>(SecurityOperations.ReadOnlyAccess, SecurityPermissionState.Allow);
            userRole.AddObjectPermissionFromLambda<PermissionPolicyUser>(SecurityOperations.ReadOnlyAccess, u => u.Oid == (Guid)CurrentUserIdOperator.CurrentUserId(), SecurityPermissionState.Allow);
        }
        return userRole;
    }
}

Confirm that the Application UI Changes based on Role

Run the application, and login as “Admin”. Open the Extended Security Role object’s Detail View. The Can Export checkbox is checked for the “Exporter” role.

CustomPermission_CanExport

Login again as “Sam”, who is in the “Exporter” role. Ensure that the Export action is available for this user. If you login as “John”, the Export action is unavailable.

CustomPermission_ExportAction

Tip

In most cases you do not need an option to create base user and base role objects (PermissionPolicyUser and PermissionPolicyRole) in the UI when the custom user and role (Employee and ExtendedSecurityRole) are available. To hide the base user’s and base role’s items, use the NewObjectViewController.NewObjectActionItemListMode property or other options listed in the How to remove or hide the base class from the New Action’s items list article.

Note

The Export action is available for the “Admin” user even though the “Can Export” option is not checked for the “Administrator” role. The “Is Administrative” option overrides all other permissions (built-in and custom) and grants full access to all operations.

See Also