Generic permission manager template

What I am trying to do is create a class with static methods to manage the rights of different types of users on some resource types (which are NHibernate entity objects). Specifically, I would like to check the current participant (in an asp.net MVC project) against an object id to see if he can view or edit the object. The signature I mean is as follows:

PermissionManager.CanView<TEntity>(object id);

      

Now I have done the following steps:

1) such an interface:

public interface ICanAccessQuery<TAccount, TEntity>
    where TAccount : IAccountOwner
{
    bool CanView(TAccount user, object entityKey);
    bool CanEdit(TAccount user, object entityKey);
}

      

2) some implementations like this:

public class TeacherCanAccessCourseReportsQuery : ICanAccessQuery<Teacher, CourseReport>
{
    public bool CanView(Teacher user, object entityKey)
    {
        var predicate = PredicateBuilder.Create<CourseReport>(x => x.Id == (long)entityKey);

        var conditions = PredicateBuilder.Create<CourseReport>(x => x.Teacher.Id == user.Id);
        conditions = conditions.Or(x => x.Teacher.Tutor.Id == user.Id);
        conditions = conditions.Or(x => x.CoachingTeachers.Any(t => t.Id == user.Id));

        predicate = predicate.And(conditions);

        return RepositoryProvider.Get<CourseReport>().Count(predicate) > 0;
    }

    public bool CanEdit(Teacher user, object entityKey)
    {
        // similar implementation
    }
}

      

3) static Configure () method inside my PermissionManager class, which is called in Global.asax:

public static IDictionary<string, object> _permissions = new Dictionary<string, object>();

public static void Configure()
{
    _permissions.Add(typeof(Teacher).Name + typeof(CourseReport).Name, new TeacherCanAccessCourseReportsQuery());
}

      

4) inside the PermissionManager class:

public static bool CanView<TEntity>(object primaryKey, params string[] enabledRoles)
{
    var accounts = RepositoryProvider.Get<Account, AccountRepository>();
    var principal = Thread.CurrentPrincipal as MyCustomPrincipal;

    if (enabledRoles.Any(r => principal.IsInRole(r)))
        return true;

    IAccountOwner user = accounts.GetUser(principal.AccountId);

    var can = false;
    var @switch = new Dictionary<Type, Action> {
            { typeof(Teacher), () => can = CanView<Teacher, TEntity>(user as Teacher, primaryKey) },
            { typeof(TrainingCenter), () => can = CanView<TrainingCenter, TEntity>(user as TrainingCenter, primaryKey) }
    };

    @switch[user.GetType()]();

    return can;
}

private static bool CanView<TAccount, TEntity>(TAccount user, object primaryKey)
        where TAccount : IAccountOwner
{
    var key = typeof(TAccount).Name + typeof(TEntity).Name;
    if (_permissions.ContainsKey(key))
    {
        return (((ICanAccessQuery<TAccount, TEntity>)_permissions[key]).CanView(user, primaryKey);
    }
    return false;
}

      

The same methods will be defined for CanEdit ... exactly the same except for the name of the method that will be called.

I ask: is there a better way to define what I mean in a more OOP way?

+3


source to share


1 answer


I have implemented the best solution that someone might be interested in.

Is it the "Can I Access?" Interface? request:

public interface ICanAccessQuery<TAccount, TEntity>
    where TAccount : IAccountOwner
    where TEntity : IStoredEntity
{
    bool CanView(TAccount user, TEntity entity);
    bool CanEdit(TAccount user, TEntity entity);
}

      

Now my entities are implementing an empty interface IStoredEntity

to make the constraint.

And this is an example implementation:



public class TeacherCanAccessOrdersQuery : ICanAccessQuery<Teacher, Order>
{
    public bool CanView(Teacher user, Order entity)
    {
        var predicate = PredicateBuilder.Create<Order>(x => x.Id == entity.Id && x => x.Account.Id == user.Account.Id);
        return RepositoryProvider.Get<Order>().Count(predicate) > 0;
    }

    public bool CanEdit(Teacher user, Order entity)
    {
        // similar implementation
    }
}

      

Finally, my new class AuthorizationProvider

(name changed from PermissionManager, didn't like it):

public class AuthorizationProvider
{
    public enum Abilities
    {
        View,
        Edit
    };

    private static IDictionary<string, object> _authorizations = new Dictionary<string, object>();

    // this method should be called at application bootstrap, such as Global.asax in an asp.net app
    public static void Configure()
    {
        _authorizations.Add(typeof(Teacher).Name + typeof(CourseReport).Name, new TeacherCanAccessCourseReportsQuery());
        _authorizations.Add(typeof(Teacher).Name + typeof(Order).Name, new TeacherCanAccessOrdersQuery());
        // other rules user type-entity type
    }

    // Can I view entity with primary key X?
    public static bool CanI<TEntity>(Abilities ability, object entityKey)
        where TEntity : IStoredEntity
    {
        TEntity entity = RepositoryProvider.Get<TEntity>().Load(entityKey);
        return CanI<TEntity>(ability, entity, AccountRoles.Admin);
    }

    // Can I view entity (and if I have a specific role, I surely can)?
    public static bool CanI<TEntity>(Abilities ability, TEntity entity, params string[] authorizedRoles)
        where TEntity : IStoredEntity
    {
        var principal = Thread.CurrentPrincipal as MyCustomPrincipal;

        if (authorizedRoles.Any(r => principal.IsInRole(r)))
            return true;

        var user = RepositoryProvider.Get<Account, AccountRepository>().GetUser(principal.AccountId);

        // my system has only two types of users
        if (user is Teacher)
        {
            return Can<Teacher, TEntity>(user as Teacher, ability, entity);
        }
        else if (user is TrainingCenter)
        {
            return Can<TrainingCenter, TEntity>(user as TrainingCenter, ability, entity);
        }
        return false;
    }

    /// Can user X (view|edit) entity Y?
    /// With some reflection I call the needed method. In this way I can add "abilities" to my ICanAccessQuery
    /// interface and its implementations without altering this class.
    public static bool Can<TAccount, TEntity>(TAccount user, Abilities ability, TEntity entity)
        where TAccount : IAccountOwner
        where TEntity : IStoredEntity
    {
        var key = typeof(TAccount).Name + typeof(TEntity).Name;
        if (_authorizations.ContainsKey(key))
        {
            var query = (ICanAccessQuery<TAccount, TEntity>)_authorizations[key];
            string methodName = "Can" + ability.ToString();

            var method = typeof(ICanAccessQuery<TAccount, TEntity>).GetMethod(methodName);
            return (bool)method.Invoke(query, new object[] { user, entity });
        }
        return false;
    }
}

      

Example usage in an asp.net mpc controller:

public ActionResult Details(long? id)
{
    if (!id.HasValue)
        return new EmptyResult();

    if (!AuthorizationProvider.CanI<CourseReport>(AuthorizationProvider.Abilities.View, id.Value))
        return RedirectToAccessDenied();

     // etc.
}

      

+1


source







All Articles