Skip to main content
.NET 6.0+

Add a Security Operation that Can be Permitted or Denied for a Specific Type

  • 10 minutes to read

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 that determines whether or not the Export operation is allowed or denied for the currently displayed object type is also demonstrated.

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

ExportOperation_Result

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 ExportState property.

    using DevExpress.ExpressApp.DC;
    using DevExpress.Persistent.Base;
    using DevExpress.Persistent.BaseImpl.EF.PermissionPolicy;
    // ...
    [XafDisplayName("Type Operation Permissions")]
    public class CustomTypePermissionObject : PermissionPolicyTypePermissionObject {
        [XafDisplayName("Export")]
        public virtual SecurityPermissionState? ExportState { get; set; }
    }
    
  • If you are using the EF Core ORM, add a DbSet for the CustomTypePermissionObject type to the DbContext:

    File: MySolution.Module\MySolutionDbContext.cs

    public class MySolutionEFCoreDbContext : DbContext {
        // ...
        public DbSet<CustomTypePermissionObject> CustomTypePermissionObjects { get; set; }
    }
    
  • 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;
    // ...
    public class ExportPermissionRequest : IPermissionRequest {
        public ExportPermissionRequest(Type objectType) {
            ObjectType = objectType;
        }
        public Type ObjectType { get; private set; }
        public object GetHashObject() {
            return ObjectType.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’s 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 Startup.cs file from the ASP.NET Core Blazor and WinForms application projects, as well as in the Global.asax.cs file from the ASP.NET Web Forms 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 file.

    Blazor

    File: MySolution.Blazor.Server\Startup.cs

    public class Startup {
        // ...
        public void ConfigureServices(IServiceCollection services) {
            // ...
            services.AddXaf(Configuration, builder => {
                // ...
                builder.Security
                    .UseIntegratedMode(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) {
                                    ApplicationUser user = security.User as ApplicationUser;
                                    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));
                                // ...
                            };
                        };
                    })
                    // ...
            });
            // ...
        }
        // ...
    }
    

    WinForms

    File: MySolution.Win\Startup.cs

    public class ApplicationBuilder : IDesignTimeApplicationFactory {
        public static WinApplication BuildApplication(string connectionString) {
            var builder = WinApplication.CreateBuilder();
            // ...
            builder.Security
                .UseIntegratedMode(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) {
                                ApplicationUser user = security.User as ApplicationUser;
                                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));
                        };
                        // ...
                    };
                    // ...
                })
                // ...
        }
        // ...
    }
    

    ASP.NET Web Forms

    File: MySolution.Web\Global.asax.cs

    ((SecurityStrategy)WebApplication.Instance.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) {
            ApplicationUser user = security.User as ApplicationUser;
            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 scenario, you should additionally call the static WcfDataServerHelper.AddKnownType method to register your custom permission request. Call this method in both the application server and client application code before the data server and client application are initialized.

    File: MySolution.MiddleTier\Program.cs_, MySolution.Win\Program.cs_

    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 file located in the DatabaseUpdate folder of your module project. Override the ModuleUpdater.UpdateDatabaseAfterUpdateSchema method in the following manner.

File: MySolution.Module\DatabaseUpdate\Updater.cs

using DevExpress.Persistent.BaseImpl.PermissionPolicy;
// ...
public override void UpdateDatabaseAfterUpdateSchema() {
    base.UpdateDatabaseAfterUpdateSchema();
    ApplicationUser user = ObjectSpace.FirstOrDefault<ApplicationUser>(u => u.UserName == "User");
    if(user == null) {
        user = ObjectSpace.CreateObject<ApplicationUser>();
        user.UserName = "User";
        user.SetPassword("");
        ObjectSpace.CommitChanges();
        ((ISecurityUserWithLoginInfo)user).CreateUserLoginInfo(SecurityDefaults.PasswordAuthentication, ObjectSpace.GetKeyValueAsString(user));
    }
    PermissionPolicyRole userRole = CreateUserRole();
    user.Roles.Add(userRole);

    ApplicationUser userAdmin = ObjectSpace.FirstOrDefault<ApplicationUser>(u => u.UserName == "Admin");
    if(userAdmin == null) {
        userAdmin = ObjectSpace.CreateObject<ApplicationUser>();
        userAdmin.UserName = "Admin";
        userAdmin.SetPassword("");
        ObjectSpace.CommitChanges();
        ((ISecurityUserWithLoginInfo)userAdmin).CreateUserLoginInfo(SecurityDefaults.PasswordAuthentication, ObjectSpace.GetKeyValueAsString(userAdmin));
    }
    PermissionPolicyRole adminRole = ObjectSpace.FirstOrDefault<PermissionPolicyRole>(r => r.Name == "Administrators");
    if(adminRole == null) {
        adminRole = ObjectSpace.CreateObject<PermissionPolicyRole>();
        adminRole.Name = "Administrators";
    }
    adminRole.IsAdministrative = true;
    userAdmin.Roles.Add(adminRole);
    ObjectSpace.CommitChanges();

    for (int i = 1; i <= 10; i++) {
        string subject = string.Format("Task {0}", i);
        EmployeeTask task = ObjectSpace.FirstOrDefault<EmployeeTask>(t => t.Subject == subject);
        if (task == null) {
            task = ObjectSpace.CreateObject<EmployeeTask>();
            task.Subject = subject;
            task.DueDate = DateTime.Today;
            task.Save();
        }
    }
    ObjectSpace.CommitChanges();
}

private PermissionPolicyRole CreateUserRole() {
    PermissionPolicyRole userRole = ObjectSpace.FirstOrDefault<PermissionPolicyRole>(role => role.Name == "Default");
    if(userRole == null) {
        userRole = ObjectSpace.CreateObject<PermissionPolicyRole>();
        userRole.Name = "User Role";
        CustomTypePermissionObject taskTypePermission = ObjectSpace.CreateObject<CustomTypePermissionObject>();
        taskTypePermission.TargetType = typeof(EmployeeTask);
        taskTypePermission.CreateState = SecurityPermissionState.Allow;
        taskTypePermission.DeleteState = SecurityPermissionState.Allow;
        taskTypePermission.ReadState = SecurityPermissionState.Allow;
        taskTypePermission.WriteState = SecurityPermissionState.Allow;
        taskTypePermission.ExportState = SecurityPermissionState.Allow;
        CustomTypePermissionObject userTypePermission = ObjectSpace.CreateObject<CustomTypePermissionObject>();
        userTypePermission.TargetType = typeof(ApplicationUser);
        userTypePermission.ReadState = SecurityPermissionState.Allow;
        userRole.TypePermissions.Add(taskTypePermission);
        userRole.TypePermissions.Add(userTypePermission);
        userRole.AddNavigationPermission(@"Application/NavigationItems/Items/Default/Items/MyDetails", SecurityPermissionState.Allow);
        userRole.AddNavigationPermission(@"Application/NavigationItems/Items/Default/Items/EmployeeTask_ListView", SecurityPermissionState.Allow);
        // ...
    }
    return userRole;
}

As you can see, the User user is allowed to export EmployeeTask objects. The user is not allowed to export ApplicationUser objects. Here, it is assumed that the EmployeeTask object from the business class library is added to your business model (see How to: Add the Scheduler Module (.NET 6+)). In this example, you can use any other business class instead of EmployeeTask.

Verify if the Custom Operation is Allowed for the Current User

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

File: MySolution.Module\Controllers\SecuredExportController.cs

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;
        }
    }
    void ExportAction_Executing(object sender, System.ComponentModel.CancelEventArgs e) {
        if (!Application.GetSecurityStrategy().IsGranted(new ExportPermissionRequest(View.ObjectTypeInfo.Type))) {
            throw new UserFriendlyException("Export operation is prohibited.");
        }
    }
}

This Controller subscribes to the Export Action’s ActionBase.Executing event and calls the SecurityStrategy.IsGranted 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 OnActivated method and change the Action’s visibility in accordance with the SecurityStrategy.IsGranted method result.

File: MySolution.Module\Controllers\SecuredExportController.cs

public class SecuredExportController : ObjectViewController {
    // ....
    protected override void OnActivated() {
        base.OnActivated();
        exportController = Frame.GetController<ExportController>();

        if (exportController != null) {
            exportController.ExportAction.Active.SetItemValue("Security",
                Application.GetSecurityStrategy().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 EmployeeTask objects and is hidden for ApplicationUser 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 and make the following adjustments to its Layout:

  • Group the operation checkboxes together.
  • Move the TargetType property to the 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, use the following Controller to customize the New Action’s items list.

File: MySolution.Module\Controllers\RemoveBaseTypePermissionNewActionItemController.cs

using DevExpress.ExpressApp;
using DevExpress.ExpressApp.SystemModule;
using DevExpress.Persistent.BaseImpl.PermissionPolicy;
// ...
public class RemoveBaseTypePermissionNewActionItemController :
    ObjectViewController<ObjectView, PermissionPolicyTypePermissionObject> {
    protected override void OnFrameAssigned() {
        NewObjectViewController newObjectViewController = Frame.GetController<NewObjectViewController>();
        if(newObjectViewController != null) {
            newObjectViewController.CollectDescendantTypes += (s, e) => {
                e.Types.Remove(typeof(PermissionPolicyTypePermissionObject));
            };
            newObjectViewController.ObjectCreating += (s, e) => {
                if(e.ObjectType == typeof(PermissionPolicyTypePermissionObject)) {
                    e.NewObject = e.ObjectSpace.CreateObject(typeof(CustomTypePermissionObject));
                }
            };
        }
        base.OnFrameAssigned();
    }
}
See Also