Search This Blog

Wednesday, May 27, 2015

How to assign a default security role to a new application user when Windows authentication is enabled

I want to talk about a typical scenario where AuthenticationActiveDirectory and SecurityStrategyComplex/SecurityComplex/SecuritySimple strategies are involved. Currently, when the AuthenticationActiveDirectory.CreateUserAutomatically option is set to True (the default value), a new Windows user opening such an application gets full administrative privileges.  While it can be good in certain scenarios, in others it is best to restrict this new user by assigning some limiting access permissions, e.g. to be able to view some records only.
There are several ways to accomplish this task in XAF:

1. You can create a user object manually and assign a custom role to it via the AuthenticationActiveDirectory.CustomCreateUser event. To subscribe to the CustomCreateUser event, run the Application Designer and focus the AuthenticationActiveDirectory component. Then, in the Properties window, switch to Events and double click CustomCreateUser in the property grid.



As the result, the empty event handler will be created. For instance, you can add a code to it that creates a new user associated with a low-privileged"Default" role:

using DevExpress.Data.Filtering;
using DevExpress.ExpressApp.Security;
using DevExpress.ExpressApp.Security.Strategy;
// ... 
private void authenticationActiveDirectory1_CustomCreateUser(object sender, CustomCreateUserEventArgs e) {
    SecuritySystemUser user = e.ObjectSpace.CreateObject<SecuritySystemUser>();
    user.UserName = e.UserName;
    SecuritySystemRole defaultRole = 
        e.ObjectSpace.FindObject<SecuritySystemRole>(new BinaryOperator("Name", "Default"));
    if (defaultRole != null) {
        user.Roles.Add(defaultRole);
    }
    e.User = user;
    e.Handled = true;
}

The low-privileged "Default" role can be created in the Updater.cs (Updater.vb) file using by overriding the ModuleUpdater.UpdateDatabaseAfterUpdateSchema method (by default, the Solution Wizard adds a similar code):

public override void UpdateDatabaseAfterUpdateSchema() {
    base.UpdateDatabaseAfterUpdateSchema();
    // ... 
    SecuritySystemRole defaultRole = ObjectSpace.FindObject<SecuritySystemRole>(
new BinaryOperator("Name", "Default"));
    if(defaultRole == null) {
        defaultRole = ObjectSpace.CreateObject<SecuritySystemRole>();
        defaultRole.Name = "Default";
        defaultRole.AddObjectAccessPermission<SecuritySystemUser>("[Oid] = CurrentUserId()",
 SecurityOperations.ReadOnlyAccess);
        defaultRole.AddMemberAccessPermission<SecuritySystemUser>("ChangePasswordOnFirstLogon",
 SecurityOperations.Write);
        defaultRole.AddMemberAccessPermission<SecuritySystemUser>("StoredPassword",
 SecurityOperations.Write);
        defaultRole.SetTypePermissionsRecursively<SecuritySystemRole>(SecurityOperations.Read,
 SecuritySystemModifier.Allow);
        defaultRole.SetTypePermissionsRecursively<ModelDifference>(SecurityOperations.ReadWriteAccess,
 SecuritySystemModifier.Allow);
        defaultRole.SetTypePermissionsRecursively<ModelDifferenceAspect>(SecurityOperations.ReadWriteAccess,
 SecuritySystemModifier.Allow);
    }
}


2. Another solution is using inheritance from the SecurityStrategyComplex /SecurityComplex/ SecuritySimple classes and overriding their InitializeNewUserCore method. That is possible because these components support the ICanInitializeNewUser interface. Usually, I prefer to avoid inheritance where possible and use events where possible, because it is more flexible and easier to maintain.

3. Specially for the SecurityStrategyComplext component a similar CustomInitializeNewUser event is available. By default, a new object of the RoleType type is created if this type is SecuritySystemRole or its descendant. If there is no existing role named as the NewUserRoleName property value, then the latter is used to initialize the Name property of the newly created role and the IsAdministrative property is set to true. The newly created user object is also added to the Users collection of that role. Otherwise, the existing role (NewUserRoleName) is linked to the newly created user. So, specifying the SecurityStrategyComplex.NewUserRoleName property is the simplest solution here.

Please let me know in comments if these solutions meet your needs or you want something to be improved here. I can think of requests about a full automation without the need to write any code, but that is very difficult to implement at the framework level without introducing some conventions or contracts, and one solution may not always be appropriate for all users. As you can understand, each application is unique and creating the default permissions set for the new user is normally done in custom application code - the rare case when even the magic framework cannot help you get rid you from writing code...

No comments:

Post a Comment