Link do akcji "Security aware"?

Jak utworzyć łącze akcji "Security aware", które wykrywa, czy użytkownik jest upoważniony do kliknięcia (wywołania) akcji?
Ukryj link, jeśli użytkownik nie może korzystać z tej akcji...

W zależności od

  • www.config (authorization) oraz
  • [Autoryzuj] atrybuty działań

PS
Myślę, że to zła praktyka mieszać te 2 w MVC?

Author: Peter Gfader, 2010-04-27

3 answers

To jest jakiś kod pobrany z projektu MvcSitemap i zmodyfikowany na mój własny użytek. Jeśli dobrze pamiętam ten kod został zmodyfikowany dla MVC2 i niektóre funkcje mogą być z powrotem przeniesione do MVC1.

Jego nie złe praktyki w ogóle mieszać MVC i FormsAuthentication razem, domyślne metody uwierzytelniania MVC są zbudowane wokół istniejących Asp.net Infrastruktura bezpieczeństwa.

Kod określający, czy użytkownik ma uprawnienia:

public static class SecurityTrimmingExtensions 
{

    public static bool HasActionPermission( this HtmlHelper htmlHelper, string actionName, string controllerName )
    {
        //if the controller name is empty the ASP.NET convention is:
        //"we are linking to a different controller
        ControllerBase controllerToLinkTo = string.IsNullOrEmpty(controllerName) 
                                                ? htmlHelper.ViewContext.Controller
                                                : GetControllerByName(htmlHelper, controllerName);

        var controllerContext = new ControllerContext(htmlHelper.ViewContext.RequestContext, controllerToLinkTo);

        var controllerDescriptor = new ReflectedControllerDescriptor(controllerToLinkTo.GetType());

        var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);

        return ActionIsAuthorized(controllerContext, actionDescriptor);
    }


    private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
    {
        if (actionDescriptor == null)
            return false; // action does not exist so say yes - should we authorise this?!

        AuthorizationContext authContext = new AuthorizationContext(controllerContext);

        // run each auth filter until on fails
        // performance could be improved by some caching
        foreach (IAuthorizationFilter authFilter in actionDescriptor.GetFilters().AuthorizationFilters)
        {
            authFilter.OnAuthorization(authContext);

            if (authContext.Result != null)
                return false;
        }

        return true;
    }

    private static ControllerBase GetControllerByName(HtmlHelper helper, string controllerName)
    {
        // Instantiate the controller and call Execute
        IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();

        IController controller = factory.CreateController(helper.ViewContext.RequestContext, controllerName);

        if (controller == null)
        {
            throw new InvalidOperationException(

                String.Format(
                    CultureInfo.CurrentUICulture,
                    "Controller factory {0} controller {1} returned null",
                    factory.GetType(),
                    controllerName));

        }

        return (ControllerBase)controller;
    }

}

Html Helpers

public static class SecurityTrimmedLink
{
    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName)
    {
        return htmlHelper.HasActionPermission(actionName, "")
                   ? htmlHelper.ActionLink(linkName, actionName)
                   : MvcHtmlString.Create("");
    }        

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName, RouteValueDictionary routeValueDictionary )
    {
        return htmlHelper.HasActionPermission(actionName, "")
                   ? htmlHelper.ActionLink(linkName, actionName, routeValueDictionary)
                   : MvcHtmlString.Create("");
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName, object routeValues, object htmlAttributes )
    {
        return htmlHelper.HasActionPermission(actionName, "")
                   ? htmlHelper.ActionLink(linkName, actionName, routeValues, htmlAttributes)
                   : MvcHtmlString.Create("");
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName, string controllerName)
    {
        return htmlHelper.HasActionPermission(actionName, controllerName)
                   ? htmlHelper.ActionLink(linkName, actionName, controllerName)
                   : MvcHtmlString.Create("");
    }

    public static MvcHtmlString SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkName, string actionName, string controllerName, object routeValues, object htmlAttributes)
    {
        return htmlHelper.HasActionPermission(actionName, controllerName)
                   ? htmlHelper.ActionLink(linkName, actionName, controllerName, routeValues, htmlAttributes)
                   : MvcHtmlString.Create("");
    }
}

UWAGA: To nie zadziała w MVC 5, ponieważ wywołanie FindAction() nigdy nie zwraca deskryptora akcji

Próbowałem znaleźć problem, ale nie mogłem i skończyło się na programowaniu pracy. :(

 30
Author: jfar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-18 22:45:38

Kod Jfara działał dla mnie przez większość czasu, ale musiałem wprowadzić pewne modyfikacje dla MVC4. Jest to jedyna metoda, która musiała się zmienić:

private static bool ActionIsAuthorized(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
    if (actionDescriptor == null)
        return false; // action does not exist so say yes - should we authorise this?!

    AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor);

    // run each auth filter until on fails
    // performance could be improved by some caching
    foreach (var filter in FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor))
    {
        var authFilter = filter.Instance as IAuthorizationFilter;

        if (authFilter == null)
            continue;

        authFilter.OnAuthorization(authContext);

        if (authContext.Result != null)
            return false;
    }

    return true;
}
 6
Author: viggity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-26 20:14:14

Część obszaru jest nieco bardziej skomplikowana niż dodawanie pewnych przeciążeń. UseNamespaceFallback hack nie działa, ponieważ będzie instantiate the wrong, gdy masz identycznie nazwanych kontrolerów w różnych obszarach.

Musisz mieć sposób, aby uzyskać poprawną przestrzeń nazw dla obszaru

Inaczej to

 IController controller = factory.CreateController(helper.ViewContext.RequestContext, controllerName); 

Pójdzie źle.

Obecnie mam takie linki w widoku

@Html.SecurityTrimmedActionLink("this link", "Index", "Home",new {Area=string.Empty});   
@Html.SecurityTrimmedActionLink("this link", "Index", "FunctionAdministration", new   {Area="Administration" }, null);

Wewnątrz

public static bool HasActionPermission(this HtmlHelper htmlHelper, string actionName, string controllerName, object area)

Dostanę przestrzeń nazw dla obszar lub domyślna przestrzeń nazw, gdy obszar jest pusty.

private static string GetNamespaceForArea(string area, RouteCollection routeColl)
    {
        string ns = string.Empty;
        foreach (RouteBase routeBase in routeColl)
        {
            if (routeBase.GetType() == (typeof (Route)))
            {
                Route route = (Route) routeBase;
                RouteValueDictionary dataTokens = route.DataTokens;
                ;
                if (area != null)
                {
                    if (dataTokens.ContainsKey("area"))
                    {
                        if (area.Equals(dataTokens["area"]))
                        {
                            ns = (string) ((string[]) dataTokens["Namespaces"])[0];
                            break;
                        }
                    }
                    else
                    {
                        if (area.Equals(string.Empty))
                        {
                            ns = (string) ((string[]) dataTokens["Namespaces"])[0];
                            break;
                        }
                    }
                }
            }
        }
        return ns;
    }

Musisz skonfigurować domyślną przestrzeń nazw w swojej trasie w globalasax na przykład w ten sposób (domyślnie NS " ActionLinkTest.Kontrolery"):

routes.MapRoute(
            "Default", // Route name
            "{controller}/{action}/{id}", // URL with parameters
            new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
            , new[] { "ActionLinkTest.Controllers" }
        );

Użyj go do utworzenia kontrolera na podstawie nazwy typu:

ControllerBase controllerToLinkTo = string.IsNullOrEmpty(controllerName) ? htmlHelper.ViewContext.Controller :(ControllerBase) Activator.CreateInstance(Type.GetType(type));

In global.asax Definiuj obszary

areaRegistration.Add("Administration","Areas.Administration");
 2
Author: Jamee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-22 10:08:09