How to: Implement a Custom Security Operation that Can be Permitted at the Type Level

In the XAF Security System, the following operations can be permitted for a specific type by default: Read, Write, Create and Delete. In this topic, an implementation of the additional Export operation is shown. The customization of the ExportController which should consider whether or not the Export operation is allowed or denied for the currently displayed object type is also demonstrated.

Mobile applications do not support the ExportController. However, you can implement the approach described in this topic to allow or deny other operations.

ExportOperation_Result

Tip

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

Note

If you want to permit the export functionality for all types at once at the Role level, see the How to: Implement Custom Security Objects (Users, Roles, Operation Permissions) topic.

Customize the Security System

  • Extend the PermissionPolicyTypePermissionObject persistent object that is used to store permissions with the AllowExport property.

    using DevExpress.ExpressApp.DC;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.PermissionPolicy;
    // ...
    [XafDisplayName("Type Operation Permissions")]
    public class CustomTypePermissionObject : PermissionPolicyTypePermissionObject {
        public CustomTypePermissionObject(Session session)
            : base(session) {
        }
        [XafDisplayName("Export")]
        public SecurityPermissionState? ExportState {
            get {
                return GetPropertyValue<SecurityPermissionState?>("ExportState");
            }
            set {
                SetPropertyValue("ExportState", value);
            }
        }
    }
    
  • To implement a custom Operation Permission, add a class that supports the IOperationPermission interface.

    using DevExpress.ExpressApp.Security;
    using DevExpress.Persistent.Base;
    // ...
    public class ExportPermission : IOperationPermission {
            public ExportPermission(Type objectType, SecurityPermissionState state) {
                ObjectType = objectType;
                State = state;
            }
            public Type ObjectType { get; private set; }
    
            public string Operation {
                get {
                    return "Export";
                }
            }
            public SecurityPermissionState State { get; set; }
        }
    
  • The Security System uses Permission Requests to determine whether or not a permission is granted. Add a Permission Request for the ExportPermission permission. Implement the IPermissionRequest interface in the following manner.

    using DevExpress.ExpressApp.Security;
    // ...
    [Serializable]
    public class ExportPermissionRequest : ISerializable, IPermissionRequest {
        public ExportPermissionRequest(Type objectType) {
            ObjectType = objectType;
        }
        public ExportPermissionRequest(SerializationInfo information, StreamingContext context) {   
            string typeName = information.GetString("ObjectType");     
            ObjectType = Type.GetType(typeName);
        }
        public void GetObjectData(SerializationInfo info, StreamingContext context) {
            info.AddValue("ObjectType", ObjectType.FullName);
        }
        public Type ObjectType { get; private set; }
        public object GetHashObject() {
            return this.GetType().FullName;
        }
    }
    
  • All Permission Requests should have an appropriate Permission Request Processor known by the Security Strategy. To implement such a processor, inherit the PermissionRequestProcessorBase<ProcessorPermissionRequestType> class and pass the Permission Request type as the ancestor class' generic parameter.

    using System.Linq;
    using System.Collections.Generic;
    using DevExpress.ExpressApp.Security;
    using DevExpress.Persistent.Base;
    // ...
    public class ExportPermissionRequestProcessor : PermissionRequestProcessorBase<ExportPermissionRequest> {
        private IPermissionDictionary permissionDictionary;
        public ExportPermissionRequestProcessor(IPermissionDictionary permissionDictionary) {
            this.permissionDictionary = permissionDictionary;
        }
        public override bool IsGranted(ExportPermissionRequest permissionRequest) {
            IEnumerable<ExportPermission> exportPermissions = 
                permissionDictionary.GetPermissions<ExportPermission>().Where(p => p.ObjectType == permissionRequest.ObjectType);
            if (exportPermissions.Count() == 0) {
                return IsGrantedByPolicy(permissionDictionary);
            }
            else {
                return exportPermissions.Any(p => p.State == SecurityPermissionState.Allow);
            }
        }
        private bool IsGrantedByPolicy(IPermissionDictionary permissionDictionary) {
            if (GetPermissionPolicy(permissionDictionary) == SecurityPermissionPolicy.AllowAllByDefault) {
                return true;
            }
            return false;
        }
        private SecurityPermissionPolicy GetPermissionPolicy(IPermissionDictionary permissionDictionary) {
            SecurityPermissionPolicy result = SecurityPermissionPolicy.DenyAllByDefault;
            List<SecurityPermissionPolicy> permissionPolicies = 
                permissionDictionary.GetPermissions<PermissionPolicy>().Select(p => p.SecurityPermissionPolicy).ToList();
            if (permissionPolicies != null && permissionPolicies.Count != 0) {
                if (permissionPolicies.Any(p => p == SecurityPermissionPolicy.AllowAllByDefault)) {
                    result = SecurityPermissionPolicy.AllowAllByDefault;
                }
                else {
                    if (permissionPolicies.Any(p => p == SecurityPermissionPolicy.ReadOnlyAllByDefault)) {
                        result = SecurityPermissionPolicy.ReadOnlyAllByDefault;
                    }
                }
            }
            return result;
        }
    }
    
  • 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.

    ((SecurityStrategy)winApplication.Security).CustomizeRequestProcessors += CustomizeRequestProcessors;
    // ...
    private static void CustomizeRequestProcessors(object sender, CustomizeRequestProcessorsEventArgs e) {
        List<IOperationPermission> result = new List<IOperationPermission>();
        SecurityStrategyComplex security = sender as SecurityStrategyComplex;
        if (security != null) {
            PermissionPolicyUser user = security.User as PermissionPolicyUser;
            if (user != null) {
                foreach (PermissionPolicyRole role in user.Roles) {
                    foreach (PermissionPolicyTypePermissionObject persistentPermission in role.TypePermissions) {
                        CustomTypePermissionObject customPermission = persistentPermission as CustomTypePermissionObject;
                        if (customPermission != null && customPermission.ExportState != null) {
                            SecurityPermissionState state = (SecurityPermissionState)customPermission.ExportState;
                            result.Add(new ExportPermission(customPermission.TargetType, state));
                        }
                    }
                }
            }
        }
        IPermissionDictionary permissionDictionary = new PermissionDictionary(result);
        e.Processors.Add(typeof(ExportPermissionRequest), new ExportPermissionRequestProcessor(permissionDictionary));
    }
    
  • In the Middle Tier Security - WCF Service scenario, you should additionally register your custom permission request via 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));
    

Create Predefined Users And Roles

To check the implemented Export operation, add the predefined Admin and User users with appropriate roles to the application's database. Edit the Updater.cs(Updater.vb) file located in the DatabaseUpdate folder of your module project. Override the ModuleUpdater.UpdateDatabaseAfterUpdateSchema method in the following manner.

using DevExpress.Persistent.BaseImpl.PermissionPolicy;
// ...
public override void UpdateDatabaseAfterUpdateSchema() {
    base.UpdateDatabaseAfterUpdateSchema();
    PermissionPolicyUser admin = ObjectSpace.FindObject<PermissionPolicyUser>(new BinaryOperator("UserName", "Admin"));
    if (admin == null) {
        admin = ObjectSpace.CreateObject<PermissionPolicyUser>();
        admin.UserName = "Admin";
        admin.SetPassword("");
        PermissionPolicyRole adminRole = ObjectSpace.CreateObject<PermissionPolicyRole>();
        adminRole.Name = "Administrator Role";
        adminRole.IsAdministrative = true;
        admin.Roles.Add(adminRole);
    }
    PermissionPolicyUser user = ObjectSpace.FindObject<PermissionPolicyUser>(new BinaryOperator("UserName", "User"));
    if (user == null) {
        user = ObjectSpace.CreateObject<PermissionPolicyUser>();
        user.UserName = "User";
        user.SetPassword("");
        PermissionPolicyRole userRole = ObjectSpace.CreateObject<PermissionPolicyRole>();
        userRole.Name = "User Role";
        CustomTypePermissionObject taskTypePermission = ObjectSpace.CreateObject<CustomTypePermissionObject>();
        taskTypePermission.TargetType = typeof(Task);
        taskTypePermission.CreateState = SecurityPermissionState.Allow;
        taskTypePermission.DeleteState = SecurityPermissionState.Allow;
        taskTypePermission.NavigateState = SecurityPermissionState.Allow;
        taskTypePermission.ReadState = SecurityPermissionState.Allow;
        taskTypePermission.WriteState = SecurityPermissionState.Allow;
        taskTypePermission.ExportState = SecurityPermissionState.Allow;
        CustomTypePermissionObject userTypePermission = ObjectSpace.CreateObject<CustomTypePermissionObject>();
        userTypePermission.TargetType = typeof(PermissionPolicyUser);
        userTypePermission.NavigateState = SecurityPermissionState.Allow;
        userTypePermission.ReadState = SecurityPermissionState.Allow;
        userRole.TypePermissions.Add(taskTypePermission);
        userRole.TypePermissions.Add(userTypePermission);
        user.Roles.Add(userRole);
    }
    ObjectSpace.CommitChanges();
    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();
        }
    }
    ObjectSpace.CommitChanges();
}

As you can see, the User user is allowed to export Task objects. The exporting of PermissionPolicyUser objects is not allowed for this user. Here, it is assumed that the Task object from the business class library is added to your business model (see Add a Class From the Business Class Library). In this example, you can use any other business class instead of Task.

Verify if the Custom Operation is Allowed for a Current User

To let the ExportController controller and its ExportController.ExportAction Action know about the presence of the Export operation, add the following View Controller.

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Security;
using DevExpress.ExpressApp.SystemModule;
// ...
public class SecuredExportController : ObjectViewController {
    private ExportController exportController;
    protected override void OnActivated() {
        base.OnActivated();
        exportController = Frame.GetController<ExportController>();
        if (exportController  != null) {
            exportController.ExportAction.Executing += ExportAction_Executing;
            if (SecuritySystem.Instance is IRequestSecurity) {
                exportController.Active.SetItemValue("Security",
                    SecuritySystem.IsGranted(new ExportPermissionRequest(View.ObjectTypeInfo.Type)));
            }
        }
    }
    void ExportAction_Executing(object sender, System.ComponentModel.CancelEventArgs e) {
        SecuritySystem.Demand(new ExportPermissionRequest(View.ObjectTypeInfo.Type));
    }
}

This Controller subscribes to the Export Action's ActionBase.Executing event and calls the SecuritySystem.Demand method to check whether or not the Export operation is allowed before export is executed. An exception will be thrown if the current user is not allowed to export objects displayed in the current View. To test it, run the application, log on as User, navigate to the list of users and try to execute the Export Action.

You may want to avoid the security exception and deactivate the Export Action when exporting is not allowed. In this instance, additionally override the OnViewChanged method and change the Action's visibility in accordance with the SecuritySystem.IsGranted method result.

public class SecuredExportController : ObjectViewController {
    // ....
    protected override void OnViewChanged() {
        base.OnViewChanged();
        if (exportController != null) {
            exportController.ExportAction.Active.SetItemValue("Security",
                SecuritySystem.IsGranted(new ExportPermissionRequest(View.ObjectTypeInfo.Type)));
        }
    }
}

If you run the application and log on as User, you will see that the Export action is active for Task objects and is hidden for PermissionPolicyUser objects.

Adjust the Administrative UI

If you open the Role Detail View and select the Type Permissions tab, you will see no Export column in the nested List View. The object type of this List View is PermissionPolicyTypePermissionObject, while the AllowExport property is declared in the derived CustomTypePermissionObject class. To display the Export column, use UpCasting. Invoke the Model Editor, navigate to the PermissionPolicyRoleBase_TypePermissions_ListView node and add the ExportState column. Set the PropertyName to <CustomTypePermissionObject>ExportState for this column. The image below illustrates this customization.

ExportOperation_AddColumn

Navigate to the CustomTypePermissionObject_DetailView node make the following adjustments with its Layout:

  • group the operation checkboxes together;
  • move the TargetType property on top;

The following image demonstrates the required layout:

ExportOperation_Layout

You may notice that the New Action accompanying the Type Permissions List View contains duplicate Type Operation Permissions Action Items. One of these Items refer to the base PermissionPolicyTypePermissionObject type, and another - to your custom CustomTypePermissionObject. To hide the base class' item, customize the New Action's items list using the following Controller.

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.SystemModule;
using DevExpress.Persistent.BaseImpl.PermissionPolicy;
// ...
public class RemoveBaseTypePermissionNewActionItemController :
    ObjectViewController<ObjectView, PermissionPolicyTypePermissionObject> {
    protected override void OnFrameAssigned() {
        NewObjectViewController controller = Frame.GetController<NewObjectViewController>();
        if (controller != null) {
            controller .CollectDescendantTypes += 
                delegate(object sender, CollectTypesEventArgs e) {
                    e.Types.Remove(typeof(PermissionPolicyTypePermissionObject));
            };
        }
        base.OnFrameAssigned();
    }
}

See Also