// | |
// This code is referenced in the following blog posts: | |
// * https://colinmackay.scot/2018/08/17/ensure-controller-actions-classes-have-authorisation/ | |
// | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Threading.Tasks; | |
using System.Web.Mvc; | |
using NUnit.Framework; | |
using Shouldly; | |
namespace MVC.UnitTests.Controllers | |
{ | |
[TestFixture] | |
public class ControllerAccessTests | |
{ | |
[Test] | |
public void All_Controller_Actions_Have_Authorisation() | |
{ | |
// This can be any controller on the MVC assembly. | |
Assembly mvcAssembly = typeof(HomeController).Assembly; | |
Type[] controllerTypes = mvcAssembly.GetTypes() | |
.Where(t => !t.IsAbstract) | |
.Where(t => t.IsSubclassOf(typeof(Controller))) | |
.ToArray(); | |
List<string> failures = new List<string>(); | |
foreach (Type controllerType in controllerTypes) | |
{ | |
if (!HasClassLevelAttributes(controllerType)) | |
{ | |
MethodInfo[] actionMethods = controllerType.GetMethods(BindingFlags.Instance | BindingFlags.Public) | |
.Where(m => ReturnsActionResult(m) || ReturnsAsyncActionResult(m)) | |
.ToArray(); | |
foreach (MethodInfo actionMethod in actionMethods) | |
{ | |
object[] attributes = actionMethod.GetCustomAttributes(typeof(AuthorizeAttribute), true); | |
if (attributes.Any() == false) | |
{ | |
attributes = actionMethod.GetCustomAttributes(typeof(AllowAnonymousAttribute), true); | |
if (attributes.Any() == false) | |
{ | |
failures.Add($"{controllerType.FullName}.{actionMethod.Name}"); | |
} | |
} | |
} | |
} | |
} | |
failures.Count.ShouldBe(0, | |
"You need to specify [AllowAnonymous] or [Authorize] or a derivative on the following actions, or the class that contains them.\r\n * "+string.Join("\r\n * ", failures)+Environment.NewLine+Environment.NewLine); | |
} | |
private static bool ReturnsActionResult(MethodInfo method) | |
{ | |
return method.ReturnType == typeof(ActionResult) || method.ReturnType.IsSubclassOf(typeof(ActionResult)); | |
} | |
private static bool ReturnsAsyncActionResult(MethodInfo method) | |
{ | |
Type returnType = method.ReturnType; | |
if (!returnType.IsGenericType) | |
return false; | |
if (returnType == typeof(Task<ActionResult>)) | |
return true; | |
if (returnType.GetGenericTypeDefinition() != typeof(Task<>)) | |
return false; | |
Type[] genericTypes = returnType.GenericTypeArguments; | |
if (genericTypes.Length != 1) | |
return false; | |
Type asyncReturnType = genericTypes[0]; | |
bool result= asyncReturnType.IsSubclassOf(typeof(ActionResult)); | |
return result; | |
} | |
private static bool HasClassLevelAttributes(Type controllerType) | |
{ | |
object[] classAuthAttributes = controllerType.GetCustomAttributes(typeof(AuthorizeAttribute), true); | |
if (!classAuthAttributes.Any()) | |
{ | |
object[] classAnonAttributes = controllerType.GetCustomAttributes(typeof(AllowAnonymousAttribute), true); | |
if (!classAnonAttributes.Any()) | |
return false; | |
} | |
return true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment