patterncsharpMinor
Fluent API of a Role based access control implementation
Viewed 0 times
controlapirolebasedimplementationfluentaccess
Problem
I am trying to provide a fluent API for authorization based on roles.
As you will see I separated my implementation in two related Interfaces the Session and the Query. The session provides all roles, permissions and user information, so one may query it later via the
I also separated the implementation in two concepts of roles, which are "normal" roles (via
In "normal" roles, one simply states that the user have it. In the parameterised roles one states that user have a role for one specific resource. Consider the following examples:
All teachers may lecture a lesson. Here the role would be teachers and the action would be lecture. This is a "normal" role
All teachers of a subject can publish the exam results of that subject in college platform. In this case you need to provide which subject that is taught by the Teacher. This is a parameterized role because one needs to provide the resource subject.
Please note that none of either implementations have a role hierarchy support (yet).
This code block contains all interfaces and some delegate definitions as well.
```
public delegate IEnumerable GetUserPermissions(IPrincipal user, object resource);
public delegate IEnumerable GetUserRoles(IPrincipal user, object resource);
public delegate bool IsUserInRole(IPrincipal user, object resource);
public interface IRbacSession
{
IDictionary> UserRoles { get; }
IEnumerable RolePermissions { get; }
IRbacQuery Query { get; }
void AddPermission(string roleName, string action);
void UserIsInRoleIf(string role, Predicate predicate);
}
public interface ISpecializedRbacSession : IRbacSession
{
IDictionary UserRolesForType { get; }
IDictionary UserPermissions { get; }
new ISpecializedRbacQuery Query { get; }
void AddUserRoleForTypeIf(string role, IsUserInRole predicate);
void AddPermission(string roleName, string action);
}
public interface IRbacQuery
{
As you will see I separated my implementation in two related Interfaces the Session and the Query. The session provides all roles, permissions and user information, so one may query it later via the
Query object. I also separated the implementation in two concepts of roles, which are "normal" roles (via
SimpleRbacSession) and parameterised roles (via SpecializedRbacSession). In "normal" roles, one simply states that the user have it. In the parameterised roles one states that user have a role for one specific resource. Consider the following examples:
All teachers may lecture a lesson. Here the role would be teachers and the action would be lecture. This is a "normal" role
All teachers of a subject can publish the exam results of that subject in college platform. In this case you need to provide which subject that is taught by the Teacher. This is a parameterized role because one needs to provide the resource subject.
Please note that none of either implementations have a role hierarchy support (yet).
This code block contains all interfaces and some delegate definitions as well.
```
public delegate IEnumerable GetUserPermissions(IPrincipal user, object resource);
public delegate IEnumerable GetUserRoles(IPrincipal user, object resource);
public delegate bool IsUserInRole(IPrincipal user, object resource);
public interface IRbacSession
{
IDictionary> UserRoles { get; }
IEnumerable RolePermissions { get; }
IRbacQuery Query { get; }
void AddPermission(string roleName, string action);
void UserIsInRoleIf(string role, Predicate predicate);
}
public interface ISpecializedRbacSession : IRbacSession
{
IDictionary UserRolesForType { get; }
IDictionary UserPermissions { get; }
new ISpecializedRbacQuery Query { get; }
void AddUserRoleForTypeIf(string role, IsUserInRole predicate);
void AddPermission(string roleName, string action);
}
public interface IRbacQuery
{
Solution
public delegate IEnumerable GetUserPermissions(IPrincipal user, object resource);
public delegate IEnumerable GetUserRoles(IPrincipal user, object resource);
public delegate bool IsUserInRole(IPrincipal user, object resource);I seldom create new delegate types, I tend to use
Action and Func delegates instead, but I like how it makes an enjoyable read, especially here:IDictionary UserRolesForType { get; }
IDictionary UserPermissions { get; }Why is the former called
UserRolesForType? Then why isn't the latter called UserPermissionsForType? Consistency is your friend!We could consider the distinction between "normal" and "parameterized" queries as superfluous:
public interface IRbacQuery
{
IEnumerable GetUserRoles(IPrincipal user);
IEnumerable GetUserRoles(IPrincipal user, T resource);
IEnumerable GetUserPermissions(IPrincipal user);
IEnumerable GetUserPermissions(IPrincipal user, T resource);
bool IsUserInRole(IPrincipal user, string role);
bool IsUserInRole(IPrincipal user, string role, T resource);
bool IsUserAbleTo(IPrincipal user, string action);
bool IsUserAbleTo(IPrincipal user, string action, T resource);
IEnumerable GetRolePermissions(string roleName);
bool IsRoleAbleTo(string roleName, string action);
}The implementating types would expose both variants:
public virtual IEnumerable GetUserRoles(IPrincipal user)
{
return session.UserRoles
.Where(role => role.Value(user))
.Select(role => role.Key);
}
public virtual IEnumerable GetUserRoles(IPrincipal user, T resource)
{
var userRoles = session.UserRolesForType.TryGetOrEmpty(typeof (T));
if (userRoles == null)
{
return new string[0];
}
return userRoles(user, resource);
}The generic overload would no longer need to cast
session into an ISpecializedRbacSession (assuming GetRolesForType becomes a member of IRbacSession), and it seems to me the client code would get simpler... unless you already only expose the "specialized" interface, so the client code already sees both overloads?The nesting of the types in the last code block is interesting:
Rbac
User
UserRole
UserRoleResource
What
_Can
_Role
_User
Is
Principal
UserRole
Do
_Action
ActionRequirements
Can
UserAction
ActionResource
I'd have to spend a great deal of time on this code to understand the presence of an
_ underscore prefix on some of the public classes you have here, and why User, What, Is, Do and Can aren't themselves nested under Rbac, because there's strong coupling going on here:public Rbac(IRbacSession session)
{
User = new User(session);
What = new What(session);
Is = new Is(session);
Do = new Do(session);
Can = new Can(session);
}I like the constructor-injected
session though.As for the implementations themselves, I find the dictionary assignations could use some
var:IDictionary userPermissions = new Dictionary();Compare to:
var userPermissions = new Dictionary();In the same method, seeing this line:
foreach (var permission in permissionAssignment)Made me scroll all the way up to this declaration:
private readonly IDictionary> permissionAssignment = new Dictionary>();If
permissionAssignment had been called _permissionAssignment, I would have known at a glance that it was a private field, ...but that's just me being used to that underscore and being a bit nitpicky, the code looks good, really.Code Snippets
public delegate IEnumerable<string> GetUserPermissions(IPrincipal user, object resource);
public delegate IEnumerable<string> GetUserRoles(IPrincipal user, object resource);
public delegate bool IsUserInRole(IPrincipal user, object resource);IDictionary<Type, GetUserRoles> UserRolesForType { get; }
IDictionary<Type, GetUserPermissions> UserPermissions { get; }public interface IRbacQuery
{
IEnumerable<string> GetUserRoles(IPrincipal user);
IEnumerable<string> GetUserRoles<T>(IPrincipal user, T resource);
IEnumerable<string> GetUserPermissions(IPrincipal user);
IEnumerable<string> GetUserPermissions<T>(IPrincipal user, T resource);
bool IsUserInRole(IPrincipal user, string role);
bool IsUserInRole<T>(IPrincipal user, string role, T resource);
bool IsUserAbleTo(IPrincipal user, string action);
bool IsUserAbleTo<T>(IPrincipal user, string action, T resource);
IEnumerable<string> GetRolePermissions(string roleName);
bool IsRoleAbleTo(string roleName, string action);
}public virtual IEnumerable<string> GetUserRoles(IPrincipal user)
{
return session.UserRoles
.Where(role => role.Value(user))
.Select(role => role.Key);
}
public virtual IEnumerable<string> GetUserRoles<T>(IPrincipal user, T resource)
{
var userRoles = session.UserRolesForType.TryGetOrEmpty(typeof (T));
if (userRoles == null)
{
return new string[0];
}
return userRoles(user, resource);
}public Rbac(IRbacSession session)
{
User = new User(session);
What = new What(session);
Is = new Is(session);
Do = new Do(session);
Can = new Can(session);
}Context
StackExchange Code Review Q#52155, answer score: 8
Revisions (0)
No revisions yet.