Мне очень понравился код из сообщения @ Роберта, но было несколько ошибок, и я хотел кэшировать сбор ролей и пользователей, потому что рефлексия может быть немного дорогостоящей.
Исправлены ошибки: если есть и атрибут Controller, и атрибут Action, то, когда роли объединяются, между ролями контроллера и ролями действия не вставляется дополнительная запятая, которая не будет проанализирована правильно.
[Authorize(Roles = "SuperAdmin,Executives")]
public class SomeController() {
[Authorize(Roles = "Accounting")]
public ActionResult Stuff() {
}
}
тогда строка ролей заканчивается SuperAdmin,ExecutivesAccounting
, моя версия гарантирует, что руководители и бухгалтерский учет отделены.
Мой новый код также игнорирует Auth для действий HttpPost, потому что это может сбить с толку, хотя и маловероятно.
Наконец, он возвращает MvcHtmlString
вместо string
для более новых версий MVC
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Collections;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Security.Principal;
public static class HtmlHelperExtensions
{
/// <summary>
/// only show links the user has access to
/// </summary>
/// <returns></returns>
public static MvcHtmlString SecurityLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, bool showDisabled = false)
{
if (IsAccessibleToUser(action, controller))
{
return htmlHelper.ActionLink(linkText, action, controller);
}
else
{
return new MvcHtmlString(showDisabled ? String.Format("<span>{0}</span>", linkText) : "");
}
}
/// <summary>
/// reflection can be kinda slow, lets cache auth info
/// </summary>
private static Dictionary<string, Tuple<string[], string[]>> _controllerAndActionToRolesAndUsers = new Dictionary<string, Tuple<string[], string[]>>();
private static Tuple<string[], string[]> GetAuthRolesAndUsers(string actionName, string controllerName)
{
var controllerAndAction = controllerName + "~~" + actionName;
if (_controllerAndActionToRolesAndUsers.ContainsKey(controllerAndAction))
return _controllerAndActionToRolesAndUsers[controllerAndAction];
Type controllerType = GetControllerType(controllerName);
MethodInfo matchingMethodInfo = null;
foreach (MethodInfo method in controllerType.GetMethods())
{
if (method.GetCustomAttributes(typeof(HttpPostAttribute), true).Any())
continue;
if (method.GetCustomAttributes(typeof(HttpPutAttribute), true).Any())
continue;
if (method.GetCustomAttributes(typeof(HttpDeleteAttribute), true).Any())
continue;
var actionNameAttr = method.GetCustomAttributes(typeof(ActionNameAttribute), true).Cast<ActionNameAttribute>().FirstOrDefault();
if ((actionNameAttr == null && method.Name == actionName) || (actionNameAttr != null && actionNameAttr.Name == actionName))
{
matchingMethodInfo = method;
}
}
if (matchingMethodInfo == null)
return new Tuple<string[], string[]>(new string[0], new string[0]);
var authAttrs = new List<AuthorizeAttribute>();
authAttrs.AddRange(controllerType.GetCustomAttributes(typeof(AuthorizeAttribute), true).Cast<AuthorizeAttribute>());
var roles = new List<string>();
var users = new List<string>();
foreach(var authAttr in authAttrs)
{
roles.AddRange(authAttr.Roles.Split(','));
users.AddRange(authAttr.Roles.Split(','));
}
var rolesAndUsers = new Tuple<string[], string[]>(roles.ToArray(), users.ToArray());
try
{
_controllerAndActionToRolesAndUsers.Add(controllerAndAction, rolesAndUsers);
}
catch (System.ArgumentException ex)
{
//possible but unlikely that two threads hit this code at the exact same time and enter a race condition
//instead of using a mutex, we'll just swallow the exception when the method gets added to dictionary
//for the second time. mutex only allow single worker regardless of which action method they're getting
//auth for. doing it this way eliminates permanent bottleneck in favor of a once in a bluemoon time hit
}
return rolesAndUsers;
}
public static bool IsAccessibleToUser(string actionName, string controllerName)
{
var rolesAndUsers = GetAuthRolesAndUsers(actionName, controllerName);
var roles = rolesAndUsers.Item1;
var users = rolesAndUsers.Item2;
IPrincipal principal = HttpContext.Current.User;
if (!roles.Any() && !users.Any() && principal.Identity.IsAuthenticated)
return true;
foreach (string role in roles)
{
if (role == "*" || principal.IsInRole(role))
return true;
}
foreach (string user in users)
{
if (user == "*" && (principal.Identity.Name == user))
return true;
}
return false;
}
public static Type GetControllerType(string controllerName)
{
Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes())
{
if (type.BaseType.Name == "Controller" && (type.Name.ToUpper() == (controllerName.ToUpper() + "Controller".ToUpper())))
{
return type;
}
}
return null;
}
}