Having in mind the main Themis idea, the demand, it's time to move to role definitions, specifying correlation between roles and demands.

Role classes

At the very beginning it's worth to notice what Themis does not provide. All the role management, assigning users to roles, persisting and retrieving roles objects, all of these topics are not covered by Themis. The project assumes that you use some role provider (preferably something much stronger than standard .NET membership provider). What Themis wants you to do is to create a role system which stores in role objects much more information than only role name. It's easy to reduce the whole authorization system to the level where roles are nothing more than strings. For the purpose of showing what Themis considers a good role/membership system view the following example of roles' classes:

public class LibraryUserRole
{
    public LibraryUserRole(DateTime registrationDate)
    {
        RegistrationDate = registrationDate;
    }

    public DateTime RegistrationDate {get; private set;}
}

public class LibraryWorkerRole
{
    public LibraryWorkerRole(int trustLevel)
    {
        TrustLevel= trustLevel;
    }

    public int TrustLevel{get; private set;}
}
(To make it running with NHibernate it's simple to add a base class Role with a property of User type. Then you could query it in a very objective, polymorphic way, asking for all the Roles having User property set to an instance of the authorized user.)
As you can see, the roles in a Themis paradigm are something more than simple strings. They brings context, hence allowing more advanced operations.

Role definitions

It's time to make a final step and write configuration with use of demands introduced earlier and role classes added before. The defining role authorizations starts in Themis abstract class RoleDefinition<TRole> having only one protected method:

public abstract class RoleDefinition<TRole> : IModelProvider
        where TRole : class
{
    // other members
    protected void Add<TDemand, TResult>(Expression<Func<TDemand, TRole, TResult>> expression)
        where TDemand : class, IDemand<TResult>
    {
        // body commented out
    }
}
What this method allows you is creating a deriving class configuring one role class. In majority of cases you should have one role definition class per one class role. Let's take a look to an example role definition:

public class LibraryWorkerRoleDefinition : RoleDefinition<LibraryWorkerRole>
{
	public LibraryWorkerRoleDefinition ()
	{
		CanEdit<Book>((b, r) => b.RequiredTrustLevel <= r.TrustLevel);
		CanView<Book>((b, r) => b.RequiredTrustLevel <= r.TrustLevel);
		CanDelete<Book>((b, r) => b.Disposed == true && b.RequiredTrustLevel <= r.TrustLevel);
	}
}
I hope the configuration is pretty self-explanatory: having subject of permission as the first parameter (book) and the role as the second rules can be easily written. The LibraryWorkerRoleDefinition derives from the RoleDefinition which is not provided by Themis. That kind of class should be written by user of Themis, which with helper classes included in Themis.dll, can be done easily with a few lines of code:

public class RoleDefinition<TRole>: BaseRoleDefinition<TRole> where TRole : class
{
	/// <summary>
        /// The method adding a view permission for a role.
        /// </summary>
        /// <typeparam name="TEntity">The type of an entity for which permissions are configured.</typeparam>
        /// <param name="expression">The authorization expression itself.</param>
        /// <remarks>
        /// This method transforms the expression based on entity to the expression based
        /// on <see cref="ViewDemand{TEntity}"/> and adds the transformed 
        /// one via <see cref="RoleDefinition{TRole}.Add{TDemand,TResult}"/> methods
        /// </remarks>
	protected void CanView<TEntity>(Expression<Func<TEntity, TRole, bool>> expression)
            where TEntity : class
        {
            // the expression getting the entity object from the demand
            Expression<Func<ViewDemand<TEntity>, TEntity>> accessor = v => v.Entity;
            var result = ExpressionHelper.MapDemandMemberToDemand(expression, accessor);
            
            Add(result);
        }

        // the other methods are pretty the same but using other demand types: EditDemand<TEntity>, DeleteDemand<TEntity> in place of ViewDemand<TEntity>
}

The following code allows one writing expressions using entity as the parameter, not the demand, which finally shortens the expressions by not having chains of properties (for example: _d.Entity.Disposed == true).

Summing up

Configuring roles in Themis consists of writing definition classes, one per each role. It's common to create one root, abstract RoleDefinition class, which covers the ugliest part: transformations between expressions. Having the helper classes within Themis.dll helps to do it without any friction.

Wait a minute, what about integration with the one of the finest ORMs - NHibernate? Go to the next section called NHibernate integration

Last edited Jan 24, 2011 at 5:53 PM by scooletz, version 12

Comments

No comments yet.