Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.Net MVC how to determine if a user can access a URL?

So I was reading another question regarding login loop when you have a user logging in, set to return to a URL which they might not have access to after logging in (ie. an admin page, and the user logs in with a normal account).

The solution under WebForms seems to be to utilize the UrlAuthorizationModule.CheckUrlAccessForPrincipal method. However that does not work for URLs going to Action Methods secured with the Authorize Attribute. I figured I could work out which method the URL was pointing at and reflect over it to solve my problem - but I can't seem to work out how I get this information out of the routing table.

Anyone ever worked with this, or have a solution for this? If I can just get hold of the route information from a URL I think I could work the rest out, but if anyone has a generic solution - ie. some hidden method akin to the before mentioned one for MVC, then that would be totally awesome as well.

I'm not asking how to check if the User has acces to a specified Controller/Action pair. I first and foremost need to work out how to get the Controller/Action pair from the RouteTable based off the URL. The reason for all the background story, is in case that there does indeed exist an equivalent to UrlAuthorizationModule.CheckUrlAccessForPrincipal for MVC.

like image 386
kastermester Avatar asked Feb 04 '10 02:02

kastermester


2 Answers

John Farrell (jfar)'s answer (SecurityTrimmingExtensions class) updated for MVC 4:

public static class SecurityCheck
{
    public static bool ActionIsAuthorized(string actionName, string controllerName)
    {
        IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();
        ControllerBase controller = factory.CreateController(HttpContext.Current.Request.RequestContext, controllerName) as ControllerBase;
        var controllerContext = new ControllerContext(HttpContext.Current.Request.RequestContext, controller);
        var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
        var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
        AuthorizationContext authContext = new AuthorizationContext(controllerContext, actionDescriptor);
        foreach (var authAttribute in actionDescriptor.GetFilterAttributes(true).Where(a => a is AuthorizeAttribute).Select(a => a as AuthorizeAttribute))
        {
            authAttribute.OnAuthorization(authContext);
            if (authContext.Result != null)
                return false;
        }
        return true;
    }
}
like image 92
Brian Richardson Avatar answered Oct 15 '22 04:10

Brian Richardson


I ported and hacked this code from the MvcSitemap:

public static class SecurityTrimmingExtensions 
{

    /// <summary>
    /// Returns true if a specific controller action exists and
    /// the user has the ability to access it.
    /// </summary>
    /// <param name="htmlHelper"></param>
    /// <param name="actionName"></param>
    /// <param name="controllerName"></param>
    /// <returns></returns>
    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;
    }

It could use some caching but for my case that was a premature optimization.

like image 27
John Farrell Avatar answered Oct 15 '22 04:10

John Farrell