Παρουσίαση με Ετικέτες

Λυπούμαστε, αλλά υπάρχουν άλλα διαθέσιμες Ετικέτες για να φιλτράρετε με αυτές.
User friendly way to add permissions
25 Ιανουαρίου 13 06:03 πμ | tolisss | 0 σχόλια   

XAFsecurity system is really flexible and can easily address complex scenarios. Depending on the nature of our application (e.g. geek level of business users Smile) we may want to modify the default design to make it more user friendly! This will be a detailed discussion on all the steps involved in doing so. In addition a sample application is available for download at the end of the post.

The goal is to create a user Role that will be able to Read and Navigate to predefined DashboardDefinition instances (SecurityOperation.ReadOnly). The DashboardDefinition is a simple business object.

If we had to use code we use a ModuleUpdater and the following snippet:

public class Updater : ModuleUpdater {

    public Updater(IObjectSpace objectSpace, Version currentDBVersion) : base(objectSpace, currentDBVersion) { }

    public override void UpdateDatabaseAfterUpdateSchema() {

        base.UpdateDatabaseAfterUpdateSchema();

 

        var role= ObjectSpace.FindObject<SecuritySystemRole>("Name='User'");

        var criteria = CriteriaOperator.Parse("Oid=?", "TOO hard to know the key value").ToString();

        const string operations = SecurityOperations.ReadOnlyAccess;

        role.AddObjectAccessPermission<DashboardDefinition>(criteria,operations);

 

    }

}

 

The handy AddObjectAccessPermission Role extension method hides the internals which are:

  1. Searches if a SecuritySystemTypePermissionObject exists for the DashboardDefinition type and creates it accordingly. The SecuritySystemTypePermissionObject is a persistent object with a Type property which is used to relate the permission with business objects. Moreover SecuritySystemTypePermissionObject has a set of properties (AllowRead, AllowNavigate etc.) used by the Security System to determine if permissions are granted for a business object.
  2. Creates a new SecuritySystemObjectPermissionsObject which holds the Criteria and Operation action and relates it with the SecuritySystemTypePermissionObject from step 1.

Although the AddObjectAccessPermission allows us to write user friendly code there are a few problems:

  1. This post is about a friendly (non code) way to add permissions and our goal is to modify the default XAF UI.
  2. It is difficult to construct the Criteria parameter of the AddObjectAccessPermission method (the developer should be aware of the name and value of the key property).

Let’s first see how the above code snippet is translated to a XAF UI and what steps needed from the end user.

image

The most difficult step as with the code approach is the Criteria construction. This time is even harder since we are not a developer any more but a business user. This means that even simple stuff like identifying the key property may look like a mountain.  In addition the end user needs a huge amount of time for creating permissions for a lot of objects.

The solution to this problem is to modify the default XAF UI and allow the business user to associate a Role with a DashboardDefinition object instance as shown bellow:

image

The above UI represents a many collection between Roles and DashboardDefintion. We can tell that is an M-M relation because only the link action is available (see left arrow)., The New DashboardDefinition action is hidden and the creation of the intermediate objects is done magically from XAF!

To create the DashboardDefinition collection shown in the above UI, we can use the ProvidedAssociationAttribute as discussed in Modifying Business Objects using Attributes post.

image

In this step using a simple attribute we guided XAF to create a totally different UI for associating a Role with a DashboardDefintion. What remains is to write code that will automatically create the required permissions by extending the SecuritySystemRole class.

Creating a custom Security Module

Extending the SecuritySystemRole class means that we need to create a custom Role class deriving from the SecuritySystemRole. The process is well documented How to: Implement Custom Security Objects (Users, Roles, Operation Permissions). However since we want a reusable functionality we recommend to create a module to host the custom Role class. XAF follows this recommendation with the Security module, our community project eXpandFrameWork with the XpandSecurityModule.

public class XRole:SecuritySystemRole {

    public XRole(Session session) : base(session) {

    }

 

}

 

Next step is to create an attribute with two parameters:

  1. OperationProviderProperty: Is the name of the property that will provide the SecurityOperation which will be applied to the collection of DashboardDefinition of our XRole.
  2. CollectionName: Is the name of the dynamically created DashboardDefinition collection member in our XRole.

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]

public class SecurityOperationsAttribute : Attribute {

    readonly string _collectionName;

    readonly string _operationProviderProperty;

 

    public SecurityOperationsAttribute(string collectionName, string operationProviderProperty) {

        _collectionName = collectionName;

        _operationProviderProperty = operationProviderProperty;

    }

 

    public string CollectionName {

        get { return _collectionName; }

    }

 

    public string OperationProviderProperty {

        get { return _operationProviderProperty; }

    }

}

 

Now its time to use this SecurityOperationsAttribute in our DashboardDefintion class which does not live in our custom Security module:

 

image

 

The collectionName parameter (DashboardDefinitions) is the name of the collection created from the ProvidedAssociationAttribute as discussed in the start of the post. The operationProviderProerty (DashboardOperation) does not yet exist in our XRole class and we need to create it in an abstract way since our Security modules has no knowledge of the DashboardDefinition existence. Writing abstract code with XAF is really a piece of cake! Our goal is to enumerate through all PersistentTypes (this includes DashboardDefintion) marked with the SecurityOperationAttribute. Then for each Persistent type we need to create a dynamic member in our XRole class to hold the SecurityOperation. Again note that our module is not even aware of what is the Role type.

public sealed partial class MySecurityModule : ModuleBase {

    public override void CustomizeTypesInfo(ITypesInfo typesInfo) {

        base.CustomizeTypesInfo(typesInfo);

        var roleTypeProvider = Application.Security as IRoleTypeProvider;

        if (roleTypeProvider != null) {

            foreach (var attribute in SecurityOperationsAttributes(typesInfo)) {

                CreateMember(typesInfo, roleTypeProvider, attribute);

            }

        }

    }

 

    void CreateMember(ITypesInfo typesInfo, IRoleTypeProvider roleTypeProvider, SecurityOperationsAttribute attribute) {

        var roleTypeInfo = typesInfo.FindTypeInfo(roleTypeProvider.RoleType);

        if (roleTypeInfo.FindMember(attribute.OperationProviderProperty) == null) {

            var memberInfo = roleTypeInfo.CreateMember(attribute.OperationProviderProperty, typeof (SecurityOperationsEnum));

            memberInfo.AddAttribute(new RuleRequiredFieldAttribute());

        }

    }

 

    IEnumerable<SecurityOperationsAttribute> SecurityOperationsAttributes(ITypesInfo typesInfo) {

        var typeInfos = typesInfo.PersistentTypes.Where(info => info.FindAttribute<SecurityOperationsAttribute>() != null);

        return typeInfos.SelectMany(info => info.FindAttributes<SecurityOperationsAttribute>());

    }

 

With the above code a new property will be added to the previously XRole UI.

 

image

 

 

Now we need a method to get the SecurityOperations given an XRole instance and the dynamic collection of DashboardDefinition objects. Note that the name property that provides these values exist in the SecurityOperationsAttribute marking our DashboardDefinition object:

 

static string GetSecurityOperation(ISecurityRole securityRole, XPMemberInfo memberInfo) {

    var typeInfo = XafTypesInfo.Instance.FindTypeInfo(memberInfo.CollectionElementType.ClassType);

    var roleTypeInfo = XafTypesInfo.Instance.FindTypeInfo(securityRole.GetType());

    var operationsAttribute = typeInfo.FindAttributes<SecurityOperationsAttribute>().FirstOrDefault(attribute => attribute.CollectionName == memberInfo.Name);

    return operationsAttribute != null ? Convert(securityRole, roleTypeInfo, operationsAttribute) : null;

}

 

static string Convert(ISecurityRole securityRole, ITypeInfo roleTypeInfo, SecurityOperationsAttribute operationsAttribute) {

    var value = roleTypeInfo.FindMember(operationsAttribute.OperationProviderProperty).GetValue(securityRole);

    if (value == null || ReferenceEquals(value, ""))

        return null;

    var securityOperations = (SecurityOperationsEnum)value;

    var fieldInfo = typeof(SecurityOperations).GetField(securityOperations.ToString(), BindingFlags.Public | BindingFlags.Static);

    if (fieldInfo != null)

        return fieldInfo.GetValue(null).ToString();

    throw new NotImplementedException(value.ToString());

}

 

Having a list of SecurityOperations from the GetSecurityOperation method we can use XAF’s metadata API to create the ObjectOperationPermissions as simple as:

 

public static IEnumerable<ObjectOperationPermission> ObjectOperationPermissions(this ISecurityRole securityRole, XPMemberInfo member) {

    var collection = ((XPBaseCollection)member.GetValue(securityRole)).OfType<object>();

    var securityOperation = GetSecurityOperation(securityRole, member);

    if (!string.IsNullOrEmpty(securityOperation)) {

        foreach (var operation in securityOperation.Split(ServerPermissionRequestProcessor.Delimiters, StringSplitOptions.RemoveEmptyEntries)) {

            foreach (var obj in collection) {

                yield return ObjectOperationPermissions(member, obj, operation);

            }

        }

    }

}

 

static ObjectOperationPermission ObjectOperationPermissions(XPMemberInfo member, object obj, string securityOperation) {

    return new ObjectOperationPermission(member.CollectionElementType.ClassType, Criteria(obj, member.CollectionElementType), securityOperation);

}

 

static string Criteria(object obj, XPClassInfo classInfo) {

    var keyProperty = classInfo.KeyProperty;

    var keyValue = keyProperty.GetValue(obj);

    return CriteriaOperator.Parse(keyProperty.Name + "=?", keyValue).ToString();

}

Finally we put all these methods to a class SecuritySystemRoleExtensions  and override our custom XRole GetPermissionsCore method as discussed in  How to: Implement Custom Security Objects (Users, Roles, Operation Permissions). So, in simple English this can b said: For each collection member in our XRole that his collection element type is marked with a SecurityOperationsAttribute call the above ObjectOperationPermissions extension method to get the permissions and add them to the list of XRole’s permission. XAF’s language does not differ much from English Smile, so this  will be:

public class XRole : SecuritySystemRole {

    public XRole(Session session)

        : base(session) {

    }

 

    protected override IEnumerable<IOperationPermission> GetPermissionsCore() {

        var operationPermissions = base.GetPermissionsCore();

        return OperationPermissionCollectionMembers().Aggregate(operationPermissions, (current, xpMemberInfo) => current.Union(this.ObjectOperationPermissions(xpMemberInfo).Cast<IOperationPermission>()));

    }

 

    IEnumerable<XPMemberInfo> OperationPermissionCollectionMembers() {

        return ClassInfo.OwnMembers.Where(info => info.IsAssociationList && info.CollectionElementType.HasAttribute(typeof(SecurityOperationsAttribute)));

    }

Today, we discussed how to mix BO’s metadata with instance data using a simple attribute in order to avoid tedious and repetitive work. To summarize when we want to create user friendly ObjectAccessPermissions  we can simply mark our BO as shown:

 

image

 

Note that even if DashboardDefintion class may live in a module we do not have source code, XAF will not sweat at all! It is really easy to dynamically replace attributes adjusting to your own preferences (see also How to customize a Business Model at runtime (Example)):

 

public override void CustomizeTypesInfo(ITypesInfo typesInfo) {

    base.CustomizeTypesInfo(typesInfo);

    var typeInfo = (TypeInfo) typesInfo.FindTypeInfo(typeof (DashboardDefinition));

    var memberInfo = typeInfo.FindMember("Roles");

 

    //replace ProvidedAssociationAttribute in Roles collection

    memberInfo.RemoveAttributes<ProvidedAssociationAttribute>();

    memberInfo.AddAttribute(new ProvidedAssociationAttribute("DashboardDefinition-Roles","MyDashboardDefintions",RelationType.ManyToMany, null));

 

    //replace SecurityOperationsAttribute

    typeInfo.RemoveAttributes<SecurityOperationsAttribute>();

    typeInfo.AddAttribute(new SecurityOperationsAttribute("MyDashboardDefintions", "MyDashboardOperation"));

}

 

The credits for this post go first to XAF with its unbelievable flexible API, second to a great XAFer named Stephen Manderson and third to me that wrote this post Smile. Moreover Stephen shared with us his Dashboard module which is the most wanted integration of XAF and our new Dashboard tool!

 

Next post we be all about Stephen’s Dashboard module, in the meantime let us know your thoughts in everything you heard today.

 

The sample with today’s discussion can be downloaded from here and is build against XAF v12.2.5.

 

Until next time, Happy XAFing!

Δημοσίευση στην κατηγορία: ,
Let’s create metadata based permission policies
08 Νοεμβρίου 12 04:05 πμ | tolisss | 0 σχόλια   

XAF has a great security system which can be used to protect our data either at client or server side. There is also great support for modifying the Security behavior at runtime using a permission matrix. However we need to provide initial before distributing our application. In this post we are going to discuss a way connecting methods that describe permission policies with our domain objects using metadata (Attributes).

For supplying initial data XAF provides a well documented approach through ModuleUpdaters. We can use them and write methods that create initial permissions. There are also many extension methods that can help us write faster code,

image

Full permission policy

This policy can be applied to any role and give full permission for the related objects. To connect a domain object with a method we need to do the following:

  1. Define a FullPermissionAttribute as,

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]

    public class FullPermissionAttribute : Attribute {

    }

     

  2. Decorate all related objects

    [FullPermission]

    public class MovieArtist : VideoRentalBaseObject {

    [FullPermission]

    public class Artist : VideoPerson {

  3. With the use of XAF’s sub types info metadata API write an abstract algorithm that will enumerate all domain objects and assign full permission to them for a certain role

    public static void CreateFullPermissionAttributes(this SecuritySystemRole systemRole,  bool defaultAllowValues = true) {

        var persistentTypes = XafTypesInfo.Instance.PersistentTypes.Where(info => info.FindAttribute<FullPermissionAttribute>() != null);

        foreach (var typeInfo in persistentTypes) {

            systemRole.CreateTypePermission(typeInfo.Type,  defaultAllowValues);

        }

    }

    public static SecuritySystemTypePermissionObject CreateTypePermission(this SecuritySystemRole systemRole, Type targetType, bool defaultAllowValues = true) {

        var permission = systemRole.CreateTypePermission(targetType);

        permission.TargetType = targetType;

        permission.AllowDelete = defaultAllowValues;

        permission.AllowNavigate = defaultAllowValues;

        permission.AllowRead = defaultAllowValues;

        permission.AllowWrite = defaultAllowValues;

        permission.AllowCreate = defaultAllowValues;

        return permission;

    }

  4. Invoke the CreateFullPermissionAttributes form the roles you want to apply inside the ModuleUpdater’s

    public override void UpdateDatabaseAfterUpdateSchema() {

        base.UpdateDatabaseAfterUpdateSchema();

        var employersRole = ObjectSpace.GetRole<SecuritySystemRole>("Employers");

        employersRole.CreateFullPermissionAttributes();

Permission Behavior policy

A common policy for an application is a “settings” policy. Which really means that for certain roles like the employee role there should be limited access to objects that store “settings” data. To implement this we can follow the follow steps

  1. Define an attribute to decorate our objects as in previous step. This time we do not know the policy name (settings) so we design this as an Enum parameter in the ctor.

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]

    public class PermissionBehaviorAttribute : Attribute {

        readonly string _name;

     

        public PermissionBehaviorAttribute(object @enum) {

            if (!@enum.GetType().IsEnum)

                throw new NotImplementedException();

            _name = Enum.GetName(@enum.GetType(), @enum);

        }

     

        public PermissionBehaviorAttribute(string name) {

            _name = name;

        }

     

        public string Name {

            get { return _name; }

        }

    }

     

  2. Design an enumeration to hold all possible security policies

    public enum PermissionBehavior {

        Admin,

        Settings,

        ReadOnlyAccess

    }

     

  3. Decorate related objects

    [PermissionBehavior(PermissionBehavior.ReadOnlyAccess)]

    public class Language : VideoRentalBaseObject {

  4. Create a method to describe the ReadOnlyAccess permission policy inside ModuleUpdater.

    public class Updater : ModuleUpdater {

        void ReadOnlyAccesPermissionBehaviour(SecuritySystemRole securitySystemRole, ITypeInfo typeInfo) {

            securitySystemRole.SetTypePermissions(typeInfo.Type, SecurityOperations.ReadOnlyAccess, SecuritySystemModifier.Allow);

        }

  5. With the use of XAF’s sub types info API write an abstract algorithm that will enumerate all domain objects and invoke a delegate with same parameters as the method in step 4

    public static void CreatePermissionBehaviour(this SecuritySystemRole systemRole, Enum behaviourEnum, Action<SecuritySystemRole, ITypeInfo> action) {

        var typeInfos = XafTypesInfo.Instance.PersistentTypes.Where(info => {

            var permissionBehaviorAttribute = info.FindAttribute<PermissionBehaviorAttribute>();

            return permissionBehaviorAttribute != null && permissionBehaviorAttribute.Name.Equals(Enum.GetName(behaviourEnum.GetType(), behaviourEnum));

        });

        foreach (var typeInfo in typeInfos) {

            action.Invoke(systemRole, typeInfo);

        }

    }

     

  6. Invoke the CreatePermissionBehaviour inside the ModulerUpdater feeding it with the ReadOnlyAccesPermissionBehaviour method of step 4

    employersRole.CreatePermissionBehaviour(PermissionBehavior.ReadOnlyAccess, ReadOnlyAccesPermissionBehaviour);

In this post we created reusable algorithms from any XAF projects to help us speeding up the configuration of the initial permissions. When of course talking about code reusability we talk about money saving!

P.S. In a real world application the above coding will be converted to a permission matrix UI representation like,
image

We would appreciate your feedback on this post. Has it been useful to you? Feel free to contact us with any further questions

Next post will be about what else? its model time!

Happy XAFing as always.


Δημοσίευση στην κατηγορία: ,

Search

Go

Το Ιστολόγιο

Ιστορικό Δημοσιεύσεων

Συνδρομές