ASP.Net MVC Extension method to create a Security Aware Html.ActionLink

by robert 22. October 2008 12:58

I am a big fan of ASP.Net MVC and the DRY principle.

Extending the work done by Maarten Balliauw, the following is my attempt at creating an "security aware" action link that detects if a user is authorized to click (invoke) the action. The point is to show, hide or disable a link based on the Authorize attribute of the controller.

image

The code allows you to show a disabled link as a <span> label or hide it completely.

I'm trying to avoid using Reflection, but so far I haven't figured out how.

Here is the code:

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal;
using System.Web.Routing;
using System.Web.Mvc;
using System.Collections;
using System.Reflection;
namespace System.Web.Mvc.Html
{
    public static class HtmlHelperExtensions
    {
        public static string SecurityTrimmedActionLink(
        this HtmlHelper htmlHelper,
        string linkText,
        string action,
        string controller)
        {
            return SecurityTrimmedActionLink(htmlHelper, linkText, action, controller, false);
        }
        public static string SecurityTrimmedActionLink(this HtmlHelper htmlHelper, string linkText, string action, string controller, bool showDisabled)
        {
            if (IsAccessibleToUser(action, controller))
            {
                return htmlHelper.ActionLink(linkText, action, controller);
            }
            else
            {
                return showDisabled ? String.Format("<span>{0}</span>", linkText) : "";
            }
        }
        public static bool IsAccessibleToUser(string actionAuthorize, string controllerAuthorize)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            GetControllerType(controllerAuthorize);
            Type controllerType = GetControllerType(controllerAuthorize);
            var controller = (IController)Activator.CreateInstance(controllerType);
            ArrayList controllerAttributes = new ArrayList(controller.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true));
            ArrayList actionAttributes = new ArrayList();
            MethodInfo[] methods = controller.GetType().GetMethods();
            foreach (MethodInfo method in methods)
            {
                object[] attributes = method.GetCustomAttributes(typeof(ActionNameAttribute), true);
                if ((attributes.Length == 0 && method.Name == actionAuthorize) || (attributes.Length > 0 && ((ActionNameAttribute)attributes[0]).Name == actionAuthorize))
                {
                    actionAttributes.AddRange(method.GetCustomAttributes(typeof(AuthorizeAttribute), true));
                }
            }
            if (controllerAttributes.Count == 0 && actionAttributes.Count == 0)
                return true;

            IPrincipal principal = HttpContext.Current.User;
            string roles = "";
            string users = "";
            if (controllerAttributes.Count > 0)
            {
                AuthorizeAttribute attribute = controllerAttributes[0] as AuthorizeAttribute;
                roles += attribute.Roles;
                users += attribute.Users;
            }
            if (actionAttributes.Count > 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 true;
            }
            foreach (string user in usersArray)
            {
                if (user == "*" && (principal.Identity.Name == user))
                    return true;
            }
            return false;
        }

        public static Type GetControllerType(string controllerName)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            foreach (Type type in assembly.GetTypes())
            {
                if (type.BaseType.Name == "Controller" && (type.Name.ToUpper() == (controllerName.ToUpper() + "Controller".ToUpper())))
                {
                    return type;
                }
            }
            return null;
        }
    }
}

Tags:

ASP.Net MVC

Comments

10/22/2008 1:08:46 PM #

Sorry about the code format. I'm testing a new code formatting scheme.

robert United States

10/22/2008 11:18:06 PM #

This is actually pretty cool. Thanks for sharing. It is very useful i think.

Yazılım Turkey

10/23/2008 1:25:13 AM #

Trackback from DotNetKicks.com

ASP.Net MVC Extension method to create a Security Aware Html.ActionLin

DotNetKicks.com

10/23/2008 6:35:30 AM #

What are the benefits of toggling the link in your approach vs. giving the menu link an ID and doing something like:

menuHome.Visible = Roles.IsUserInRole("Admin");

J United States

10/24/2008 3:44:35 AM #

@J-

The benefit is that it makes the code easier to maintain.

If you put the list of roles on each link, one has to locate all the links that are affected and manually change them. This can get complicated with MVC's routing mechanisms.

With this approach, you add or remove a role (or user) from the Authorize Attribute in the controller class or method and that changes the behavior of all affected links, regardless of their location.

robert United States

10/24/2008 6:57:26 AM #

Robert,

This is very nice!!!  I've got it working in my "test" application!  I do have one question tho...you mentioned on Maarten's blog that you were having a problem with the MvcHandler returning a null everytime in the IsAccessibleToUser method...did you ever figure that out?  I'm having the same issue.  Thx for the great security utility, I don't think I'd be as interested in MVC without it...

Mike United States

10/25/2008 5:59:58 AM #

@Mike-
I abandoned the MvcHandler for the Assembly approach. I just couldn't get the RequestContext instantiated under this scenario.
I think this approach can take care of the Reflection issues if one creates a cache of all the method attributes and uses that to get the authorize information. Perhaps Maarten's sitemap can take advantage of this as well.

robert United States

12/9/2008 6:31:02 PM #

Do NOT ever use .ToUpper to insure comparing strings is case-insensitive.  Instead of this:
type.Name.ToUpper() == (controllerName.ToUpper() + "Controller".ToUpper()))

Do this:
type.Name.Equals(controllerName + "Controller", StringComparison. InvariantCultureIgnoreCase)

Marc Brooks United States

12/31/2008 10:34:13 AM #

This is such a Great Idea!

I too had a problem with using the "calling assembly". I thought about it and maybe: I think u could get rid of the GetControllerType(..) chunk of code by adding a type parameter to your extension methods.

For example, the signature would be:
public static string SecurityTrimmedActionLink[T](...)
Where typeof(T) will give u the controller Type.

the calling syntax on the view would then be:
[%= Html.SecurityTrimmedActionLink[AdminController]("LinkText","ActionX","Admin",new{ID=22}) %]
That worked for me!
I think you should consider submitting this clever concept to MVC futures (if it isnt already there).

I'm wondering if its worthwhile to consider a "wrapper div" that toggles display of its inner content.
Sorta like an HtmlHelper method that accepts innertags or partials as a rendering argument, and returns a div wrapped around the content. In classic ASP, those were "Panels"

pete w

6/11/2009 3:25:36 AM #

Pingback from chriscavanagh.wordpress.com

MVC AuthorizedActionLink «  Chris Cavanagh’s Blog

chriscavanagh.wordpress.com

6/11/2009 6:49:16 AM #

Here's an expression-based example you might find useful:

chriscavanagh.wordpress.com/.../

Chris Cavanagh United States

6/19/2009 1:31:54 PM #

Thanks a lot for sharing this. Definately bookmarked
Might be an old post, but new info to me Smile

Lån Penge Denmark

7/10/2009 11:07:55 PM #

Good post and nice design, is this a regular template?

Jim United Kingdom

7/15/2009 12:50:44 AM #

I am doing something of the same interest and will be taking note on this .Thank

Registry Cleaner Thailand

7/15/2009 4:34:02 PM #

nice info.. thanks

naruto wallpaper United States

7/15/2009 9:54:59 PM #

This is very nice!!! I've got it working in my "test" application! I do have one question tho...you mentioned on Maarten's blog that you were having a problem with the MvcHandler returning a null everytime in the IsAccessibleToUser method...did you ever figure that out? I'm having the same issue. Thx for the great security utility, I don't think I'd be as interested in MVC without it...

Poker game online United States

7/15/2009 11:55:14 PM #

How to use microsoft express to built a web page in asp.net?

webdesign United States

7/16/2009 12:48:07 AM #

Great blog - Just subscriped to your RSS feed.. Thanks

Maria United Kingdom

7/26/2009 6:26:16 PM #

You have a point there. I'm sending you some positive energy! :o) Thank you.

How To Win The Lottery United States

7/27/2009 2:11:25 AM #

Nice article here...

thanks for sharing....

Stop Dreaming Start Action United States

7/27/2009 2:14:28 AM #

thanks for sharing.

Stop Dreaming Start Action United States

8/1/2009 6:00:20 PM #

Thank you for sharing this fine piece. Very inspiring! (as always, btw)

San Diego Web Design United States

8/3/2009 9:00:57 PM #

Cool! Wouldn't mind reading more of this. You got some nice resources here - going to bookmark some of your pages. Thanks!

Learn Master Guitar United States

8/4/2009 9:24:56 PM #

There are certainly a lot of details like that to take into consideration.

Duck Hunting Canada United States

8/4/2009 9:24:59 PM #

You have a point. Very insightful. A nice perspective.

Duck Hunting Canada United States

8/6/2009 1:14:01 AM #

Valuable information which you have discussed with us through your article.

Commercial Mortgage United Kingdom

8/7/2009 2:48:51 AM #

How to convert an html document into an msword document?

seo United States

8/8/2009 9:50:09 AM #

There are certainly a lot of details like that to take into consideration. That�s a great point to bring up. I offer the thoughts above as general inspiration but clearly there are questions like the one you bring up where the most important thing will be working in honest good faith. I don�t know if best practices have emerged around things like that, but I am sure that your job is clearly identified as a fair game.

Link Building United Kingdom

8/8/2009 11:56:38 AM #

DO you make any money from this blog?

fun United Kingdom

8/9/2009 9:43:14 AM #

I've got it working in my "test" application! I do have one question tho...you mentioned on Maarten's blog that you were having a problem with the MvcHandler returning a null everytime in the IsAccessibleToUser method.Did you ever figure that out? I'm having the same issue. Thx for the great security utility.

emo hair

emo hair United States

8/10/2009 6:39:08 AM #

Valuable information which you have discussed with us through your article.

home improvement United States

8/13/2009 2:08:41 PM #

In searching for sites related to web hosting and specifically comparison hosting linux plan web, your site came up. Smile

payday loan United States

8/13/2009 2:08:45 PM #

VRy interesting to read it Tong Laughing

payday loan United States

8/14/2009 2:24:27 AM #

Makes a lot of sense and thanks for explaining that! I finally get it :o)

San Diego Real Estate Investing United States

8/15/2009 1:47:37 AM #

cheesy

cheesy songs United Kingdom

8/23/2009 1:03:35 AM #

An algorithm must be seen to be believed.

Extenze United States

8/23/2009 1:03:38 AM #

The Borg tried to assimilate your system. Resistance is futile.

Enzyte United States

8/24/2009 1:26:58 AM #

Funny, I actually had this on my mind a few days ago and now I come across your blog...

Learn & Master Guitar United States

8/24/2009 1:27:02 AM #

Thanks, you cleared up some things for me.

Duck Hunting Canada United States

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen

About the author

Something about the author

Tag cloud

    Month List

    Page List