Как создать пользовательский атрибут AuthorizeAttribute, специфичный для области, контроллера и действия? - PullRequest
8 голосов
/ 03 февраля 2011

Другими словами, действительно ли это глупая идея?

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeActionAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        // get the area, controller and action
        var area = filterContext.RouteData.Values["area"];
        var controller = filterContext.RouteData.Values["controller"];
        var action = filterContext.RouteData.Values["action"];
        string verb = filterContext.HttpContext.Request.HttpMethod;

        // these values combined are our roleName
        string roleName = String.Format("{0}/{1}/{2}/{3}", area, controller, action, verb);

        // set role name to area/controller/action name
        this.Roles = roleName;

        base.OnAuthorization(filterContext);
    }
}

UPDATE Я пытаюсь избежать следующего в сценарии, где у нас есть чрезвычайно детализированные разрешения ролей, потому что роли настраиваются для каждого клиента и присоединяются к группам пользователей:

public partial class HomeController : Controller
{
    [Authorize(Roles = "/supplierarea/homecontroller/indexaction/")]
    public virtual ActionResult Index()
    {
        return View();
    }

    [Authorize(Roles = "/supplierarea/homecontroller/aboutaction/")]
    public virtual ActionResult About()
    {
        return View();
    }
}

Может ли кто-нибудь объяснить мне безопасный способ написания этого атрибута AuthorizeRouteAttribute для доступа к информации о маршруте и использования его в качестве имени роли? Как говорит Леви, RouteData.Values ​​не является безопасным.

Является ли использование исполняемого httpContext.Request.Path более безопасным или лучшим способом?

public override void OnAuthorization(AuthorizationContext filterContext)
{
    if (filterContext == null)
    {
        throw new ArgumentNullException("filterContext");
    }

    if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
    {
        // auth failed, redirect to login page
        filterContext.Result = new HttpUnauthorizedResult();
        return;
    }

    var path = filterContext.HttpContext.Request.Path;
    var verb = filterContext.HttpContext.Request.HttpMethod;

    // these values combined are our roleName
    string roleName = String.Format("{0}/{1}", path, verb);

    if (!filterContext.HttpContext.User.IsInRole(roleName))
    {
        // role auth failed, redirect to login page
        filterContext.Result = new HttpUnauthorizedResult();
        // P.S. I want to tell the logged in user they don't 
        // have access, not ask them to login. They are already
        // logged in!
        return;
    }

    //
    base.OnAuthorization(filterContext);
}

Возможно, это иллюстрирует проблему немного дальше:

enum Version
{
    PathBasedRole,
    InsecureButWorks,
    SecureButMissingAreaName
}

string GetRoleName(AuthorizationContext filterContext, Version version)
{
    //
    var path = filterContext.HttpContext.Request.Path;
    var verb = filterContext.HttpContext.Request.HttpMethod;

    // recommended way to access controller and action names
    var controller = 
        filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
    var action = 
        filterContext.ActionDescriptor.ActionName;
    var area = "oh dear...."; // mmmm, where's thearea name???

    //
    var insecureArea = filterContext.RouteData.Values["area"];
    var insecureController = filterContext.RouteData.Values["controller"];
    var insecureAction = filterContext.RouteData.Values["action"];

    string pathRoleName = 
        String.Format("{0}/{1}", path, verb);
    string insecureRoleName = 
        String.Format("{0}/{1}/{2}/{3}", 
        insecureArea, 
        insecureController, 
        insecureAction, 
        verb);
    string secureRoleName = 
        String.Format("{0}/{1}/{2}/{3}", 
        area, 
        controller, 
        action, 
        verb);

    string roleName = String.Empty;

    switch (version)
    {
        case Version.InsecureButWorks:
            roleName = insecureRoleName;
            break;
        case Version.PathBasedRole:
            roleName = pathRoleName; 
            break;
        case Version.SecureButMissingAreaName:
            // let's hope they don't choose this, because
            // I have no idea what the area name is
            roleName = secureRoleName;
            break;
        default:
            roleName = String.Empty;
            break;
    }

    return roleName;
}

Ответы [ 3 ]

18 голосов
/ 03 февраля 2011

Пожалуйста не делайте это.

Если вам действительно нужно, вы можете использовать Type контроллера или MethodInfo действия для принятия решений по безопасности.Но основание всего на строках вызывает проблемы.Помните, что не существует гарантированного соответствия значений маршрутизации 1: 1 фактическому контроллеру.Если вы используете кортеж маршрутизации (a, b, c) для проверки доступа к SomeController :: SomeAction, но кто-то обнаруживает, что (a, b ', c) также выполняет то же действие, этот человек может обойти ваши механизмы безопасности.

Редактировать для ответа на комментарии:

У вас есть доступ к типу контроллера и MethodInfo действия через свойство ActionDescriptor параметра filterContext .Это единственный верный способ определить, какое действие будет действительно выполнять при обработке конвейера MVC, поскольку вполне возможно, что ваш поиск не совсем соответствует тому, что происходит за кулисами с MVC.Если у вас есть Type / MethodInfo / что угодно, вы можете использовать любую информацию, которую пожелаете (например, их полные имена), для принятия решений по безопасности.

В качестве практического примера рассмотрим область MyArea с контроллером FooController.и действие TheAction.Обычно способ, которым вы нажимаете этот FooController :: TheAction, заключается в следующем URL:

/ MyArea / Foo / TheAction

И маршрутизация дает кортеж (Area = "MyArea ", Controller =" Foo ", Action =" TheAction ").

Однако вы также можете нажать FooController :: TheAction по этому URL:

/ Foo / TheAction

И маршрутизация выдаст кортеж (Area = "", Controller = "Foo", Action = "TheAction").Помните, что области связаны с маршрутами, а не контроллерами.И поскольку контроллер может быть поражен несколькими маршрутами (если определения совпадают), то контроллер также может быть логически связан с несколькими областями.Вот почему мы говорим разработчикам никогда не использовать маршруты (или области или тег по расширению) для принятия решений по безопасности.

Кроме того, в вашем классе есть ошибка, заключающаяся в том, что он изменчив (он изменяет своиСобственность Роли в OnAuthorization).Атрибуты фильтра действий должны быть неизменяемыми, поскольку они могут кэшироваться частями конвейера и использоваться повторно.В зависимости от того, где этот атрибут объявлен в вашем приложении, это открывает временную атаку, которую посетитель вредоносного сайта может затем использовать для предоставления доступа к любому желаемому действию.

Для получения дополнительной информации см. Также мои ответы на:

5 голосов
/ 09 февраля 2011

Если вы хотите сделать это, принимая во внимание рекомендацию Леви, ответ будет следующим:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace MvcApplication1.Extension.Attribute
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class AuthorizeActionAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// Called when a process requests authorization.
        /// </summary>
        /// <param name="filterContext">The filter context, which encapsulates information for using <see cref="T:System.Web.Mvc.AuthorizeAttribute"/>.</param>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="filterContext"/> parameter is null.</exception>
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // auth failed, redirect to login page
                filterContext.Result = new HttpUnauthorizedResult();

                return;
            }

            // these values combined are our roleName
            string roleName = GetRoleName(filterContext);

            if (!filterContext.HttpContext.User.IsInRole(roleName))
            {
                filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page.");
                filterContext.Result = new RedirectResult("~/Error/Unauthorized");

                return;
            }

            //
            base.OnAuthorization(filterContext);
        }

        /// <summary>
        /// Gets the name of the role. Theorectical construct that illustrates a problem with the
        /// area name. RouteData is apparently insecure, but the area name is available there.
        /// </summary>
        /// <param name="filterContext">The filter context.</param>
        /// <param name="version">The version.</param>
        /// <returns></returns>
        string GetRoleName(AuthorizationContext filterContext)
        {
            //
            var verb = filterContext.HttpContext.Request.HttpMethod;

            // recommended way to access controller and action names
            var controllerFullName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerType.FullName;
            var actionName = filterContext.ActionDescriptor.ActionName;

            return String.Format("{0}.{1}-{2}", controllerFullName, actionName, verb);
        }
    }
}

Я не хотел предоставлять HttpUnauthorizedResult в случае, если пользователь не в роли,потому что результатом является отправка пользователя на страницу входа.Учитывая, что они уже вошли в систему, это очень запутывает пользователя.

1 голос
/ 07 февраля 2013

Это короткое уведомление! Обязательно используйте filterContext.RouteData.DataTokens["area"]; вместо filterContext.RouteData.Values["area"];

Удачи.

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