Skip to content

Instantly share code, notes, and snippets.

@IEvangelist
Last active March 22, 2018 21:41
Show Gist options
  • Save IEvangelist/2023df7d43062169082b41c49fc54c9b to your computer and use it in GitHub Desktop.
Save IEvangelist/2023df7d43062169082b41c49fc54c9b to your computer and use it in GitHub Desktop.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Xunit;

namespace IEvangelist.Tests
{
    public class ActionTests
    {
        private const string RootNamespace = "[ Enter Root Namespace ]";
        private const string Controller = nameof(Controller);
        private const string WhiteListed = nameof(UnauthorizedActionInController.GetZero);

        private static readonly Type ControllerType = typeof(Controller);
        private static readonly string[] ExecutableExtensions = { ".exe", ".dll" };

        public ActionTests()
        {
            LoadAllAssemblies();
        }

        private void LoadAllAssemblies()
        {
            var assemblies =
                Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty,
                                         $"{RootNamespace}.*")
                         .Where(IsExeOrDll)
                         .Select(Assembly.LoadFrom)
                         .Select(assembly => 
                                 TryCatchIgnore(() => AppDomain.CurrentDomain.Load(assembly.GetName())))
                         .ToList();

            Assert.False(assemblies.IsNullOrEmpty());        
        }

        [Fact]
        public void AllActionsOrParentControllerHaveAuthorizationAttributeTest()
        {
            var allControllers = GetAllControllerTypes();
            var unauthorizedControllers = 
                GetControllerTypesThatAreMissing<AuthorizeAttribute>(allControllers);
            
            Assert.True(allControllers.Count > unauthorizedControllers.Count);
            
            foreach (var controller in unauthorizedControllers)
            {
                var actions =
                    controller.GetMethods(BindingFlags.Public | BindingFlags.Instance)
                              .Where(m => m.GetCustomAttribute<HttpMethodAttribute>() != null)
                              .ToList();

                var unauthorizedActions = 
                    actions.Where(action => action.GetCustomAttribute<AuthorizeAttribute>() == null
                                         && action.GetCustomAttribute<AllowAnonymousAttribute>() == null)
                           .ToList();

                if (unauthorizedActions.IsNullOrEmpty() ||
                   (unauthorizedActions.Count == 1 && 
                    unauthorizedActions[0].Name == WhiteListed))
                {
                    continue;
                }

                unauthorizedActions.ForEach(action => Console.WriteLine($"{action} is unauthorized!"));
                Assert.True(false, $"Unauthorized action found!");
            }
        }

        private static List<Type> GetAllControllerTypes()
            => AppDomain.CurrentDomain
                        .GetAssemblies()
                        .Where(a => a.FullName.StartsWith(RootNamespace))
                        .SelectMany(a => a.GetTypes()
                                          .Where(t => t.FullName.Contains(Controller) ||
                                                      t.BaseType == ControllerType ||
                                                      t.DeclaringType == ControllerType))
                        .ToList();

        private static List<Type> GetControllerTypesThatAreMissing<TAttribute>(
            IEnumerable<Type> types)
            where TAttribute : Attribute
            => types.Where(t => t.GetCustomAttribute<TAttribute>() == null)
                    .ToList();

        private static bool IsExeOrDll(string path)
            => ExecutableExtensions.Any(extension => extension.Equals(Path.GetExtension(path), StringComparison.OrdinalIgnoreCase));

        private static T TryCatchIgnore<T>(Func<T> func)
        {
            try
            {
                return func();
            }
            catch { }

            return default(T);
        }
    }

    public class UnauthorizedTestController : Controller
    {
    }

    public class UnauthorizedActionInController : Controller
    {
        [HttpGet]
        public int GetZero() => 0;

        [Authorize, HttpPost]
        public IActionResult Post([FromBody] int number) => Ok();

        [OverrideAge, HttpDelete]
        public IActionResult Delete() => Ok();
    }
    
    public class OverrideAge : Authorize {  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment