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

This example illustrates how to create custom security objects, such as permissions, roles, and users. You can implement a permission that allows administrators to secure an XAF application's exporting functionality. 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. Additionally, this example demonstrates how to implement a custom user object.

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

Note

This example is for XPO applications only. If you use an EF application, modify the code snippets in this topic according to the following instructions:

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

Tip

A complete sample project is available in the DevExpress Code Examples database at http://www.devexpress.com/example=E3794.

Implement Custom Role and User Objects

  • Extend the PermissionPolicyRole class with an additional CanExport property, which indicates that a role has the 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); }
        }
    }
    
  • Now you can customize the ExtendedSecurityRole Detail View layout (see View Items Layout Customization) to place the Can Export item in an appropriate location.

    CustomPermission_ModelEditor

  • The code snippet below demonstrates the custom User object implementation. Although customizing User implementation to support the ExportPermission is not required, you can execute this additional task to make this example more realistic.

    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 to 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. Add a Permission Request for the ExportPermission permission. Implement the IPermissionRequest interface in the following manner:

    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 known by 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 register your custom permission request using the static WcfDataServerHelper.AddKnownType method. Call this method in both 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

To let the ExportController controller know about the ExportPermission permission, add the following View Controller.

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());
    }
}
  • With the code above, the controller is deactivated 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 logon as "Admin". Open the Extended Security Role object's Detail View. The Can Export checkbox is checked for the "Exporter" role by default.

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

Note

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

See Also