Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
//
// 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
You can’t perform that action at this time.