Ud af boksen er MVC frameworket udstyret med en AuthorizeAttribute, således at man kan angive på sine actions at det kun er brugere i bestemte roller der har lov at tilgå den.
[Authorize(Roles="Employee")]
Det i sig selv virker smart nok, men hvor tit har du lavet en løsning hvor authentization ikke er mere komplekst end det?
På mit nuværende projekt løb jeg hurtigt på det problem at en bruger godt må tilgå en action, hvis det id metoden tager som parameter er id’et på hans egen bruger. Altså med andre ord, han/hun må godt se egne data.
Jeg ville stadig helst undgå at skulle skrive kode i starten af mine actions til at undersøge den slags, da det er en af de ting der er oplagt at lave ved hjælp af AOP. Da attributter kun kan tage statiske værdier, var det udelukket at løse det ved at lave min egen FilterAttribute. Efter at have været lidt i tænkeboks fandt jeg frem til at bruge Castle Windsors IInterceptor til at lave validering af actions.
public class AuthorizationInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (invocation.Arguments != null && invocation.Arguments.Length > 0 && invocation.Arguments[0] != null)
{
if (invocation.Arguments[0].GetType() == typeof(RequestContext))
{
var context = (RequestContext)invocation.Arguments[0];
var values = context.RouteData.Values;
var auth = new AuthorizationManager();
if (!auth.Authorize((string)values["controller"] + "/" + (string) values["action"], (string) values["id"]))
{
throw new UnauthorizedAccessException("You are not authorized for this action");
}
}
}
invocation.Proceed();
}
}
Selve det at lave authorization kan nu implementeres i en separat AuthorizationManager klasse, der også kan bruges i forbindelse med f.eks at generere en menu med de punkter, en bruger har ret til at se.
Efter blandt andet at have ladet mig inspirere af authorization via en config-fil fandt jeg frem til at jeg gerne ville samle konfigurationen, men at jeg dog ville undgå en xml-fil, og istedet have mulighed for at lave mere advanceret authentization. Det førte til nedenstående løsning, som jeg er blevet rimeligt godt tilfreds med, da det giver mig en stor grad af fleksibilitet.
public bool Authorize(string controllerAction, string id)
{
IPrincipal user = HttpContext.Current.User;
Employee empl = _repo.GetByUserName(user.Identity.Name);
var employeeId = 0;
int.TryParse(id, out employeeId);
var dic = new Dictionary<string, Predicate<Employee>>
{
{
"Employee/Edit", employee => user.IsInRole(Config.Admin) ||
(user.IsInRole(Config.Employee) &&
employee.Id == employeeId)
},
};
Predicate<Employee> pred;
return !dic.TryGetValue(controllerAction, out pred) || pred.Invoke(empl);
}