Застрял в создании "расширения безопасности" html.ActionLink метод расширения - PullRequest
4 голосов
/ 23 сентября 2008

Я пытаюсь создать метод расширения для htmlHelper MVC. Цель состоит в том, чтобы включить или отключить ActionLink на основе атрибута AuthorizeAttribute на контроллере / действии. Заимствование из MVCSitemap
код, который создал Мартен Баллиау, я хотел проверить разрешения пользователя для контроллера / действия, прежде чем решить, как отобразить actionlink Когда я пытаюсь получить MvcHandler, я получаю нулевое значение. Есть ли лучший способ для атрибутов для контроллера / действия?

Вот код для метода расширения:

public static class HtmlHelperExtensions
{
    public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller)
    {
        //simplified for brevity 
        if (IsAccessibleToUser(action, controller))
        {
            return htmlHelper.ActionLink(linkText, action,controller);    
        }
        else
        {
            return String.Format("<span>{0}</span>",linkText);    
        }
    }

    public static bool IsAccessibleToUser(string action, string controller)
    {
        HttpContext context = HttpContext.Current;

        MvcHandler handler = context.Handler as MvcHandler;            

        IController verifyController = 
            ControllerBuilder
            .Current
            .GetControllerFactory()
            .CreateController(handler.RequestContext, controller);

        object[] controllerAttributes = verifyController.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true);
        object[] actionAttributes = verifyController.GetType().GetMethod(action).GetCustomAttributes(typeof(AuthorizeAttribute), true);

        if (controllerAttributes.Length == 0 && actionAttributes.Length == 0)
            return true;

        IPrincipal principal = handler.RequestContext.HttpContext.User;

        string roles = "";
        string users = "";
        if (controllerAttributes.Length > 0)
        {
            AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
            roles += attribute.Roles;
            users += attribute.Users;
        }
        if (actionAttributes.Length > 0)
        {
            AuthorizeAttribute attribute = actionAttributes[0] as AuthorizeAttribute;
            roles += attribute.Roles;
            users += attribute.Users;
        }

        if (string.IsNullOrEmpty(roles) && string.IsNullOrEmpty(users) && principal.Identity.IsAuthenticated)
            return true;

        string[] roleArray = roles.Split(',');
        string[] usersArray = users.Split(',');
        foreach (string role in roleArray)
        {
            if (role != "*" && !principal.IsInRole(role)) return false;
        }
        foreach (string user in usersArray)
        {
            if (user != "*" && (principal.Identity.Name == "" || principal.Identity.Name != user)) return false;
        }
        return true;
    }

}

Ответы [ 3 ]

4 голосов
/ 23 октября 2008

Вот рабочий код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal;
using System.Web.Routing;
using System.Web.Mvc;
using System.Collections;
using System.Reflection;
namespace System.Web.Mvc.Html
{
    public static class HtmlHelperExtensions
    {
        public static string SecurityTrimmedActionLink(
        this HtmlHelper htmlHelper,
        string linkText,
        string action,
        string controller)
        {
            return SecurityTrimmedActionLink(htmlHelper, linkText, action, controller, false);
        }
        public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, bool showDisabled)
        {
            if (IsAccessibleToUser(action, controller))
            {
                return htmlHelper.ActionLink(linkText, action, controller);
            }
            else
            {
                return showDisabled ? String.Format("<span>{0}</span>", linkText) : "";
            }
        }
        public static bool IsAccessibleToUser(string actionAuthorize, string controllerAuthorize)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            GetControllerType(controllerAuthorize);
            Type controllerType = GetControllerType(controllerAuthorize);
            var controller = (IController)Activator.CreateInstance(controllerType);
            ArrayList controllerAttributes = new ArrayList(controller.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true));
            ArrayList actionAttributes = new ArrayList();
            MethodInfo[] methods = controller.GetType().GetMethods();
            foreach (MethodInfo method in methods)
            {
                object[] attributes = method.GetCustomAttributes(typeof(ActionNameAttribute), true);
                if ((attributes.Length == 0 && method.Name == actionAuthorize) || (attributes.Length > 0 && ((ActionNameAttribute)attributes[0]).Name == actionAuthorize))
                {
                    actionAttributes.AddRange(method.GetCustomAttributes(typeof(AuthorizeAttribute), true));
                }
            }
            if (controllerAttributes.Count == 0 && actionAttributes.Count == 0)
                return true;

            IPrincipal principal = HttpContext.Current.User;
            string roles = "";
            string users = "";
            if (controllerAttributes.Count > 0)
            {
                AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
                roles += attribute.Roles;
                users += attribute.Users;
            }
            if (actionAttributes.Count > 0)
            {
                AuthorizeAttribute attribute = actionAttributes[0] as AuthorizeAttribute;
                roles += attribute.Roles;
                users += attribute.Users;
            }

            if (string.IsNullOrEmpty(roles) && string.IsNullOrEmpty(users) && principal.Identity.IsAuthenticated)
                return true;

            string[] roleArray = roles.Split(',');
            string[] usersArray = users.Split(',');
            foreach (string role in roleArray)
            {
                if (role == "*" || principal.IsInRole(role))
                    return true;
            }
            foreach (string user in usersArray)
            {
                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;
        }
    }
}

Мне не нравится использовать отражение, но я не могу добраться до ControllerTypeCache.

0 голосов
/ 07 октября 2015

Мне очень понравился код из сообщения @ Роберта, но было несколько ошибок, и я хотел кэшировать сбор ролей и пользователей, потому что рефлексия может быть немного дорогостоящей.

Исправлены ошибки: если есть и атрибут 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;
    }


}
0 голосов
/ 23 сентября 2008

Ваш ViewPage имеет ссылку на контекст представления, так что вы можете вместо этого сделать его методом расширения.

Тогда вы можете просто сказать, если Request.IsAuthenticated или Request.User.IsInRole (...)

использование будет похоже на <%= this.SecurityLink(text, demandRole, controller, action, values) %>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...