I’ve covered my SignalR Event aggregation library in an earlier post found here. That library has a Constraint API so that you can control which events a specific user should receive. It requires you to write one constraint handler for each type of event. But how about standard declarative Role Authorization?
The built in Role authorization in for example ASP.NET uses the CurrentPrincipal to evaluate the current users roles. This does not work very well with event aggregation, since the current user is in most cases the service account or the user that triggered the event. The library does however supply the username for the client that will receive the event. This can be used to constraint the event in a more declarative fashion.
First we need to create a custom Attribute
[AttributeUsage(AttributeTargets.Class)] public class EventAuthorizationAttribute : Attribute { public EventAuthorizationAttribute(string role) { Role = role; } public string Role { get; private set; } }
Then we create a Constraint handler that is used for all events
public class AuthorizationConstraintHandler : EventConstraintHandler<Contracts.Events.Event> { private static Dictionary<Type, string> cache; static AuthorizationConstraintHandler() { var type = typeof(Contracts.Events.Event); cache = type.Assembly.GetTypes() .Where(t => !t.IsAbstract && type.IsAssignableFrom(t)) .Select(t => new { Type = t, Attribute = t.GetCustomAttributes(typeof(EventAuthorizationAttribute),false).FirstOrDefault() as EventAuthorizationAttribute } ) .Where(t => t.Attribute != null ) .ToDictionary(t => t.Type, t => t.Attribute.Role); } public override bool Allow(Contracts.Events.Event message, string username, dynamic constraint) { if (!cache.ContainsKey(message.GetType())) return true; var principal = new WindowsPrincipal(new WindowsIdentity(username)); return principal.IsInRole(cache[message.GetType()]); } }
We create a cache in a form of a dictionary that holds each events required role (If any). That is later used from the Allow method when a actual event is prepared to be sent to a client. We create a WindowsPrincipal using the supplied username and authorize that user against the given role.
CurrentPrincipal and Enterprise Library
In the real project I wrote this for, we use Enterprise libraries Rule Authorization in layers below the constraint handler, for this to work the CurrentPrincipal has to be set to the correct one.
var principal = new WindowsPrincipal(new WindowsIdentity(username)); var tempPrincipal = Thread.CurrentPrincipal; try { Thread.CurrentPrincipal = principal; //Do stuff in the context of correct principal } finally { Thread.CurrentPrincipal = tempPrincipal; }