Reputation: 5918
How would i go about testing whether an authorize attribute on method or controller in a WebApi/MVC project has a specific role(s)
So i could test a method doing something like the below?
[Test]
[TestCase("Put", new []{"Admin","TeamMember"})]
[TestCase("Post", new []{"Admin","TeamMember"})]
[TestCase("Get", new []{"TeamMember"})]
public void Ensure_Methods_have_Correct_Roles(string methodToTest, List<string> roles)
{
var controller = new myController();
Assert.IsTrue(controller.HasRoles(methodToTest, roles));
}
with the has Roles extension method being stubbed out like this
public static bool HasRoles(this Controller controller, string action, string[] roles)
{
var controllerType = controller.GetType();
var method = controllerType.GetMethod(action);
object[] filters = method.GetCustomAttributes(typeof(AuthorizationAttribute), true);
if(!filters.Any(x => x.GetType() == typeof(AuthorizationAttribute))
{
throw exception()
}
var rolesOnCurrentMethodsAttribute = // This is where i'm stuck
foreach(var role in rolesOnCurrentMethodsAttribute)
{
//pseudo-code
if(!roles.contains(role)
{
return false;
}
}
return true;
}
is this even sensible or should i be testing the controller action directly and testing whether the response is a 401/403? That would require mocking up a context though and would mean more testing code since i would have to test each method separately.
EDIT: Perhaps don't focus on whether it's sensible. Just is it doable?
My thinking was that the unit tests would be the canonical specification of what actions should have what roles (since there is currently no written spec, and probably won't ever have one). If a developer changes a role, then they need to have a good reason for it.
EDIT #2
Based on Con's Answer below, this is what i've ended up with, one method to check aan action, another to check the controller.
public static bool WebApiActionHasRoles(this ApiController controller, string action, string roles)
{
var controllerType = controller.GetType();
var method = controllerType.GetMethod(action);
object[] filters = method.GetCustomAttributes(typeof(System.Web.Http.AuthorizeAttribute), true);
if (!filters.Any())
{
throw new Exception();
}
var rolesOnCurrentMethodsAttribute = filters.SelectMany(attrib => ((System.Web.Http.AuthorizeAttribute)attrib).Roles.Split(new[] { ',' })).ToList();
var rolesToCheckAgainst = roles.Split(',').ToList();
return !rolesOnCurrentMethodsAttribute.Except(rolesToCheckAgainst).Any() && !rolesToCheckAgainst.Except(rolesOnCurrentMethodsAttribute).Any();
}
public static bool WebApiControllerHasRoles(this ApiController controller, string roles)
{
var controllerType = controller.GetType();
object[] filters = controllerType.GetCustomAttributes(typeof(System.Web.Http.AuthorizeAttribute), true);
if (!filters.Any())
{
throw new Exception();
}
var rolesOnCurrentMethodsAttribute = filters.SelectMany(attrib => ((System.Web.Http.AuthorizeAttribute)attrib).Roles.Split(new[] { ',' })).ToList();
var rolesToCheckAgainst = roles.Split(',').ToList();
return !rolesOnCurrentMethodsAttribute.Except(rolesToCheckAgainst).Any() && !rolesToCheckAgainst.Except(rolesOnCurrentMethodsAttribute).Any();
}
If you want to use it with MVC instead of Web Api controllers/Actions just change the System.Web.Http.AuthorizeAttribute
to System.Web.MVC.AuthorizeAttribute
and in the Method Signature change ApiController
to Controller
Upvotes: 6
Views: 2325
Reputation: 293
If you are referring to AuthorizeAttribute
vs AuthorizationAttribute
, is this what you need:
public static bool HasRoles(this Controller controller, string action, string[] roles)
{
var controllerType = controller.GetType();
var method = controllerType.GetMethod(action);
object[] filters = method.GetCustomAttributes(typeof(AuthorizeAttribute), true);
if(!filters.Any())
{
throw new Exception();
}
var rolesOnCurrentMethodsAttribute = filters.SelectMany(attrib => ((AuthorizeAttribute)attrib).Roles.Split(new[] { ',' })).ToList();
return roles.Except(rolesInMethod).Count() == 0 && rolesInMethod.Except(roles).Count() == 0;
}
Alternatively, if you want to make your tests stricter and enforce only one Authorize attribute per action:
public static bool HasRoles(this Controller controller, string action, string roles)
{
var controllerType = controller.GetType();
var method = controllerType.GetMethod(action);
var attrib = method.GetCustomAttributes(typeof(AuthorizeAttribute), true).FirstOrDefault() as AuthorizeAttribute;
if (attrib == null)
{
throw new Exception();
}
return attrib.Roles == roles;
}
Upvotes: 2