Skip to main content
A newer version of this page is available. .

How to: Implement Custom Security Objects (Users, Roles, Operation Permissions)

  • 12 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.

Mobile 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 applications:

Refer to the Business Model Design with Entity Framework topic for more information about the EF data model design.

Tip

A complete sample project is available in the DevExpress Code Examples database at https://supportcenter.devexpress.com/ticket/details/e3794/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>("CanExport"); }
            set { SetPropertyValue<bool>("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 User object’s implementation. You can execute this additional task to make this example more realistic. However, customizing the User‘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>("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("Subject", ref subject, value); }
        }
        private DateTime dueDate;
        public DateTime DueDate {
            get { return dueDate; }
            set { SetPropertyValue("DueDate", ref dueDate, value); }
        }
        private Employee assignedTo;
        [Association("Employee-Task")]
        public Employee AssignedTo {
            get { return assignedTo; }
            set { SetPropertyValue("AssignedTo", ref assignedTo, value); }
        }
    }
    
  • 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 and in the Global.asax.cs (Global.asax.vb) file from the ASP.NET 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

    ((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();
    

    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 - WCF Service 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", 
                    SecuritySystem.IsGranted(new ExportPermissionRequest()));
            }
        }
    }
    void ExportAction_Executing(object sender, System.ComponentModel.CancelEventArgs e) {
        SecuritySystem.Demand(new ExportPermissionRequest());
    }
}
  • 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.FindObject<Employee>(new BinaryOperator("UserName", "Admin"));
        if (userAdmin == null) {
            userAdmin = ObjectSpace.CreateObject<Employee>();
            userAdmin.UserName = "Admin";
            userAdmin.IsActive = true;
            userAdmin.SetPassword("");
            userAdmin.Roles.Add(administratorRole);
        }
        Employee userSam = ObjectSpace.FindObject<Employee>(new BinaryOperator("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.FindObject<Employee>(new BinaryOperator("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.FindObject<Task>(new BinaryOperator("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.FindObject<ExtendedSecurityRole>(
            new BinaryOperator("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.FindObject<ExtendedSecurityRole>(
            new BinaryOperator("Name", "Exporter"));
        if (exporterRole == null) {
            exporterRole = ObjectSpace.CreateObject<ExtendedSecurityRole>();
            exporterRole.Name = "Exporter";
            exporterRole.CanExport = true;
        }
        return exporterRole;
    }
    private ExtendedSecurityRole CreateUserRole() {
        ExtendedSecurityRole userRole = ObjectSpace.FindObject<ExtendedSecurityRole>(
            new BinaryOperator("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.AddObjectPermission<PermissionPolicyUser>(SecurityOperations.ReadOnlyAccess,
                 "[Oid] = CurrentUserId()", SecurityPermissionState.Allow);
        }
        return userRole;
    }
}

Confirm that the Application UI Changes based on Role

Run the WinForms or ASP.NET 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