tags:

views:

2905

answers:

2

I'm trying to create an Extension Method for MVC's htmlHelper. The purpose is to enable or disable an ActionLink based on the AuthorizeAttribute set on the controller/action. Borrowing from the MVCSitemap
code that Maarten Balliauw created, I wanted to validate the user's permissions against the controller/action before deciding how to render the actionlink. When I try to get the MvcHandler, I get a null value. Is there a better way to the the attributes for the controller/action?

Here is the code for the extension method:

public static class HtmlHelperExtensions
{
    public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller)
    {
        //simplified for brevity 
        if (IsAccessibleToUser(action, controller))
        {
            return htmlHelper.ActionLink(linkText, action,controller);    
        }
        else
        {
            return String.Format("<span>{0}</span>",linkText);    
        }
    }

    public static bool IsAccessibleToUser(string action, string controller)
    {
        HttpContext context = HttpContext.Current;

        MvcHandler handler = context.Handler as MvcHandler;            

        IController verifyController = 
            ControllerBuilder
            .Current
            .GetControllerFactory()
            .CreateController(handler.RequestContext, controller);

        object[] controllerAttributes = verifyController.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true);
        object[] actionAttributes = verifyController.GetType().GetMethod(action).GetCustomAttributes(typeof(AuthorizeAttribute), true);

        if (controllerAttributes.Length == 0 && actionAttributes.Length == 0)
            return true;

        IPrincipal principal = handler.RequestContext.HttpContext.User;

        string roles = "";
        string users = "";
        if (controllerAttributes.Length > 0)
        {
            AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
            roles += attribute.Roles;
            users += attribute.Users;
        }
        if (actionAttributes.Length > 0)
        {
            AuthorizeAttribute attribute = actionAttributes[0] as AuthorizeAttribute;
            roles += attribute.Roles;
            users += attribute.Users;
        }

        if (string.IsNullOrEmpty(roles) && string.IsNullOrEmpty(users) && principal.Identity.IsAuthenticated)
            return true;

        string[] roleArray = roles.Split(',');
        string[] usersArray = users.Split(',');
        foreach (string role in roleArray)
        {
            if (role != "*" && !principal.IsInRole(role)) return false;
        }
        foreach (string user in usersArray)
        {
            if (user != "*" && (principal.Identity.Name == "" || principal.Identity.Name != user)) return false;
        }
        return true;
    }

}
A: 

Your ViewPage has a reference to the view context, so you could make it an extension method on that instead.

Then you can just say if Request.IsAuthenticated or Request.User.IsInRole(...)

usage would be like <%= this.SecurityLink(text, demandRole, controller, action, values) %>

Ben Scheirman
I'm trying to get the roles from the AuthorizeAttribute to compare them to the User Roles. I'm not sure how this would do that.
robDean
The point is that once the Roles are specified in the AuthorizeAttribute, you no longer have to add them to each individual link.
robDean
+2  A: 

Here is the working code:

http://www.inq.me/post/ASPNet-MVC-Extension-method-to-create-a-Security-Aware-HtmlActionLink.aspx

I don't like using reflection, but I can't get to the ControllerTypeCache.

robDean