Управление ролью и разрешениями ASP.NET MVC3 → С назначением разрешения времени выполнения

ASP.NET MVC позволяет пользователям назначать разрешения для функциональности (т.е. действия) во время разработки, например.

[Authorize(Roles = "Administrator,ContentEditor")]
public ActionResult Foo()
{
    return View();
}

Чтобы проверить разрешение, можно использовать следующий оператор в представлении (Razor):

@if (User.IsInRole("ContentEditor"))
{
    <div>This will be visible only to users in the ContentEditor role.</div>
}

Проблема с этим подходом заключается в том, что все разрешения должны быть установлены и назначены как атрибуты во время разработки. (Атрибуты скомпилированы в DLL, поэтому я в настоящее время не знаю о механизме применения атрибутов (для разрешения дополнительных разрешений), таких как [Authorize (Roles = "Administrator, ContentEditor" )] во время выполнения.

В нашем прецеденте клиент должен иметь возможность изменять, какие у пользователей есть разрешения после развертывания.

Например, клиент может пожелать разрешить пользователю в роли ContentEditor редактировать содержимое определенного типа. Возможно, пользователю не разрешили изменять значения таблицы поиска, но теперь клиент хочет разрешить это, не предоставив пользователю все разрешения в следующей более высокой роли. Вместо этого клиент просто хочет изменить разрешения, доступные для текущей роли пользователя.

Какие существуют варианты стратегий, позволяющих определять разрешения для MVC-контроллеров/представлений/действий за пределами атрибутов (как в базе данных) и оцениваться и применяться во время выполнения?

Если возможно, мы очень хотели бы придерживаться максимально возможного уровня возможностей для ASP.NET-членства и роли поставщика, чтобы мы могли продолжать использовать другие преимущества, которые он предоставляет.

Заранее благодарю за любые идеи или идеи.

Ответ 1

Какие существуют варианты стратегий для разрешения разрешений на MVC Контроллеры/представления/действия, которые должны быть определены вне атрибутов (как в базы данных) и оценивается и применяется во время выполнения?

Пользовательский атрибут Authorize является одной из возможностей для достижения этой цели:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        Roles = ... go ahead and fetch those roles dynamically from wherever they are stored
        return base.AuthorizeCore(httpContext);
    }
}

а затем:

[MyAuthorize]
public ActionResult Foo()
{
    return View();
}

Ответ 2

Как я ленив, я не мог потрудиться, катив мой собственный атрибут, и использовал FluentSecurity. В дополнение к возможности применения правил во время выполнения он позволяет настраивать способ проверки членства в роли. В моем случае у меня есть параметр файла конфигурации для каждой роли, а затем я реализую что-то вроде следующего:

// Map application roles to configuration settings
private static readonly Dictionary<ApplicationRole, string> 
    RoleToConfigurationMapper = new Dictionary<ApplicationRole, string>
        {
            { ApplicationRole.ExceptionLogViewer, "ExceptionLogViewerGroups" }
        };

роли приложения затем применяются так

SecurityConfigurator.Configure(
    configuration =>
    {
        configuration.GetAuthenticationStatusFrom(() =>
            HttpContext.Current.User.Identity.IsAuthenticated);
        configuration.GetRolesFrom(() => 
            GetApplicationRolesForPrincipal(HttpContext.Current.User));
        configuration.ForAllControllers().DenyAnonymousAccess();
        configuration.For<Areas.Administration.Controllers.LogViewerController>()
            .RequireRole(ApplicationRole.ExceptionLogViewer);
    });

filters.Add(new HandleSecurityAttribute());

а затем проверка выполняется

public static object[] GetApplicationRolesForPrincipal(IPrincipal principal)
{
    if (principal == null)
    {
        return new object[0];
    }

    List<object> roles = new List<object>();
    foreach (KeyValuePair<ApplicationRole, string> configurationMap in
             RoleToConfigurationMapper)
    {
        string mappedRoles = (string)Properties.Settings.Default[configurationMap.Value];

        if (string.IsNullOrEmpty(mappedRoles))
        {
            continue;
        }

        string[] individualRoles = mappedRoles.Split(',');
        foreach (string indvidualRole in individualRoles)
        {
            if (!roles.Contains(configurationMap.Key) && principal.IsInRole(indvidualRole))
            {
                roles.Add(configurationMap.Key);
                if (!roles.Contains(ApplicationRole.AnyAdministrationFunction))
                {
                    roles.Add(ApplicationRole.AnyAdministrationFunction);
                }
            }
        }
    }

    return roles.ToArray();
}

Конечно, вы могли бы вытащить роли из базы данных. Самое приятное в том, что я могу применять разные правила во время разработки, плюс кто-то уже сделал для меня тяжелую работу!

Ответ 3

Вы также можете рассмотреть возможность обеспечения безопасности на основе задач/действий и динамического назначения разрешений для выполнения этих задач для разных групп.

http://lostechies.com/derickbailey/2011/05/24/dont-do-role-based-authorization-checks-do-activity-based-checks/

Вам нужно будет немного убрать провайдера, чтобы работать с ним, но можно оставаться в очереди с авторизацией .net

http://www.lhotka.net/weblog/PermissionbasedAuthorizationVsRolebasedAuthorization.aspx

Ответ 4

Если вам необходимо выполнить авторизацию на основе метода или контроллера (запретить доступ ко всему методу или контроллеру), вы можете переопределить OnAuthorization в базе контроллера и выполнить свою авторизацию. Затем вы можете создать таблицу для поиска того, какие разрешения назначены этому контроллеру/методу, и оттуда.

Вы также можете создать собственный глобальный фильтр, который очень похож.

Еще один вариант, используя ваш второй подход, - сказать что-то вроде этого:

@if (User.IsInRole(Model.MethodRoles)) 
{ 
    <div>This will be visible only to users in the ContentEditor role.</div> 
} 

И затем в вашем контроллере заполните MethodRoles ролью, назначенной этому методу.