7

I'm trying to peek at any authentication attributes which may be decorating action methods in my controllers in an MVC 3 application. I'm doing this in my own HtmlHelper extension methods which are basically wrappers to ActionLink (to give you the context of what information I have available at runtime). I have a basic solution in place, but overloaded methods have just made it explode. I know that the framework is internally resolving urls to action methods, but after looking through the code for System.Web.Mvc.LinkExtensions, I still haven't found precisely how that is happening, so I'm a bit stuck as to how to tackle this issue.

Here's the code I have so far for resolving the relevant method:

private static bool _IsUserAuthorized(HtmlHelper html,
  string controllerName, string actionName)
{
  controllerName = controllerName ??
    html.ViewContext.RouteData.GetRequiredString("controller");

  var factory = ControllerBuilder.Current.GetControllerFactory();
  var controller = factory.CreateController(
    html.ViewContext.RequestContext, controllerName);

  Type controllerType = controller.GetType();
  var methodInfo = controllerType.GetMethod(actionName,
    BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

  ... check authentication
}

So my current problem is that when a method is overridden, I get "ambiguous match found" exceptions. I'm guessing I need to process the RouteValues to resolve any parameters to the method so I can unambiguously identify the right one. Does anyone have some pointers on how to do this? Alternately, does the framework already provide a means of resolving the exact method needed?

Thanks so much!

5
  • OK, so I've kept digging in the MVC3 source code and it looks like I need to get a ControllerDescriptor instance and use that to get an ActionDescriptor for the appropriate action method. So if that's the case, how do I get the ControllerContext of the appropriate controller when the requested action method is not the controller in HtmlHelper.ViewContext.Controller? Commented Apr 30, 2012 at 20:47
  • Getting a ControllerContext turns out to be pretty straightforward. Getting a ControllerDescriptor, not so much. Any thoughts? Commented Apr 30, 2012 at 21:08
  • Found code here for getting the ControllerDescriptor and ActionDescriptor. Getting closer... Commented Apr 30, 2012 at 21:18
  • The link above was a red herring. Can't figure out how to use MethodInvoker and I really would rather not add a dependency on System.Windows.Forms. ugh. Back to MVC code Commented Apr 30, 2012 at 21:33
  • Well, I have an answer now and I was going to post the code I came up with, but I have to wait another six hours before I can do so. Short answer: got a controller descriptor with new ReflectedControllerDescriptor, then called FindAction on that object to get the ActionDescriptor I needed. I'll post the code when I can. Commented Apr 30, 2012 at 21:51

2 Answers 2

2

EDIT: Updated the method to include insights from this page. This final version peeks the AuthorizationFilters for the requested action method and checks whether the user is authorized to perform the action.

So I dug around in System.Web.Mvc.ControllerActionInvoker and found the methods and constructors I needed.
ControllerDescriptor.FindAction() ended up being the key. Below, I've copied the method I wrote that will retrieve all the attributes:

private static bool _IsUserAuthorized(HtmlHelper htmlHelper,
                                      string controllerName,
                                      string actionName)
{
    ControllerContext controllerContext = null;
    
    // if controllerName is null or empty,
    // we'll use the current controller in HtmlHelper.ViewContext.
    if (string.IsNullOrEmpty(controllerName))
    {
        controllerContext = htmlHelper.ViewContext.Controller.ControllerContext;
    } else {  // use the controller factory to get the requested controller
        var factory = ControllerBuilder.Current.GetControllerFactory();
        
        var controller = (ControllerBase)factory.CreateController(
            htmlHelper.ViewContext.RequestContext, controllerName);
            
        controllerContext = new ControllerContext(
            htmlHelper.ViewContext.RequestContext, controller);
    }
    
    var controllerType = controllerContext.Controller.GetType();
    var controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
    var actionDescriptor = controllerDescriptor.FindAction(
        controllerContext, actionName);
    
    if (actionDescriptor == null)
    {
        return false;
    }
    
    var filters = new FilterInfo(
        FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor)
    );
    
    var authContext = new AuthorizationContext(controllerContext, actionDescriptor);
    foreach (IAuthorizationFilter authFilter in filters.AuthorizationFilters)
    {
        authFilter.OnAuthorization(authContext);
        if (authContext.Result != null)
        {
            return false;
        }
    }
    
    return true;
}
Sign up to request clarification or add additional context in comments.

Comments

0

The normal way to add authorization code is using an Authorization Filter.

IAuthorizationFilter.OnAuthorization provides an AuthorizationContext object which has an ActionDescriptor property.

1 Comment

We're using Authorization Filters already, but unless I'm missing it, the framework doesn't provide you with a means of inspecting that authorization prior to invoking an action. I'm trying to hide links from users who don't have access to perform the actions being linked to, so I need to inspect the authorization attributes before rendering html. Handling OnAuthorization will be too late in the chain for the functionality I need.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.