UPDATE: Fixed broken IsAuthenticated logic (thanks Travis).
Robert Dean published a great post last year about his Security Aware Html.ActionLink. It allowed you to conditionally render links based on the [AuthorizeAttribute] assigned to a controller’s action methods.
Here’s an alternative implementation based on expressions, so you get type safety, intellisense and slightly simpler code
First, a helper to determine if an action is authorized:
public static class AuthorizationExtensions { private static Dictionary<Expression, AuthorizeAttribute[]> expressionAuthorizers = new Dictionary<Expression, AuthorizeAttribute[]>(); /// <summary> /// Determines whether the specified action is authorized. /// </summary> /// <typeparam name="TController">The type of the controller.</typeparam> /// <param name="helper">The helper.</param> /// <param name="actionMethod">The action method.</param> /// <returns> /// <c>true</c> if the specified helper is authorized; otherwise, <c>false</c>. /// </returns> public static bool IsAuthorized<TController>( this HtmlHelper helper, Expression<Action<TController>> action ) { var call = action.Body as MethodCallExpression; if ( call == null ) return false; var authorizers = expressionAuthorizers.ContainsKey( action ) ? expressionAuthorizers[ action ] : expressionAuthorizers[ action ] = GetAttributes<AuthorizeAttribute>( call ); return ( authorizers.Length > 0 ) ? authorizers.All( a => a.IsAuthorized( helper.ViewContext.HttpContext.User ) ) : true; } /// <summary> /// Gets the specified attributes for an action method. /// </summary> /// <param name="call">The call.</param> /// <returns></returns> private static TAttribute[] GetAttributes<TAttribute>( MethodCallExpression call ) where TAttribute : Attribute { return call.Object.Type.GetCustomAttributes( typeof( TAttribute ), true ) .Union( call.Method.GetCustomAttributes( typeof( TAttribute ), true ) ) .Cast<TAttribute>() .ToArray(); } /// <summary> /// Determines whether the specified <see cref="AuthorizeAttribute"/> authorizes the specified user. /// </summary> /// <param name="authorize">The <see cref="AuthorizeAttribute"/>.</param> /// <param name="user">The user.</param> /// <returns> /// <c>true</c> if the specified user is authorized; otherwise, <c>false</c>. /// </returns> public static bool IsAuthorized( this AuthorizeAttribute authorize, IPrincipal user ) { if ( !user.Identity.IsAuthenticated ) return false; var users = authorize.Users.SplitString(); if ( users.Length > 0 && !users.Contains( user.Identity.Name, StringComparer.OrdinalIgnoreCase ) ) return false; var roles = authorize.Roles.SplitString(); if ( roles.Length > 0 && !roles.Any( user.IsInRole ) ) return false; return true; } /// <summary> /// Splits and trims the specified string. /// </summary> /// <param name="original">The original.</param> /// <returns></returns> public static string[] SplitString( this string original ) { return string.IsNullOrEmpty( original ) ? new string[ 0 ] : original.Split( '.' ).Select( s => s.Trim() ).Where( s => !string.IsNullOrEmpty( s ) ).ToArray(); } }
With that, it’s possible to write markup like this:
<% if ( Html.IsAuthorized<ModelingController>( c => c.Index() ) ) {%> <div>My conditional content...</div> <%}%>
If you’re using ASP.NET MVC Futures, it’s straightforward to write an authorized ActionLink:
public static class LinkExtensions { /// <summary> /// Render an HTML action link if authorized by the target action. /// </summary> /// <typeparam name="TController">The type of the controller.</typeparam> /// <param name="helper">The helper.</param> /// <param name="action">The action.</param> /// <param name="linkText">The link text.</param> /// <returns></returns> public static string AuthorizedActionLink<TController>( this HtmlHelper helper, Expression<Action<TController>> action, string linkText ) where TController : Controller { return AuthorizedActionLink<TController>( helper, action, linkText, null ); } /// <summary> /// Render an HTML action link if authorized by the target action. /// </summary> /// <typeparam name="TController">The type of the controller.</typeparam> /// <param name="helper">The helper.</param> /// <param name="action">The action.</param> /// <param name="linkText">The link text.</param> /// <param name="htmlAttributes">The HTML attributes.</param> /// <returns></returns> public static string AuthorizedActionLink<TController>( this HtmlHelper helper, Expression<Action<TController>> action, string linkText, object htmlAttributes ) where TController : Controller { var routeValuesFromExpression = ExpressionHelper.GetRouteValuesFromExpression<TController>( action ); return helper.IsAuthorized( action ) ? helper.RouteLink( linkText, routeValuesFromExpression, new RouteValueDictionary( htmlAttributes ) ) : null; } }
NOTE – You could do the same thing without MVC Futures, but would need a little more code (you’d need something like ExpressionHelper’s GetRouteValuesFromExpression method).
Here’s a markup example:
<%= Html.AuthorizedActionLink<ModelingController>( c => c.Index(), "Modeling" ) %>
6 responses so far ↓
Dew Drop - June 12, 2009 | Alvin Ashcraft's Morning Dew // June 12, 2009 at 6:38 am |
[...] MVC IsAuthorized and AuthorizedActionLink (Chris Cavanagh) [...]
Travis // June 16, 2009 at 5:14 pm |
Unless I’m missing something, but I think IsAuthorizied(..) has some backwards logic, should it not be this instead?
public static bool IsAuthorized(this AuthorizeAttribute authorize, IPrincipal user)
{
if (!user.Identity.IsAuthenticated) return false;
var users = authorize.Users.Split();
if (users.Length > 0 && users.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) return true;
var roles = authorize.Roles.Split();
if (roles.Length > 0 && roles.Any(user.IsInRole)) return true;
return false;
}
Chris Cavanagh // June 16, 2009 at 8:26 pm |
Travis – Ah you found my deliberate mistake
(that’ll teach me to copy & paste from Reflector). Thanks for pointing it out!
Travis // June 17, 2009 at 10:34 am |
Np, thanx for the code. I’m using it in a project and it seems to be working out great so far.
Chuck Wagner // June 20, 2009 at 10:01 pm |
Chris you might want to update the
public static bool IsAuthorized(this AuthorizeAttribute authorize, IPrincipal user)
method with Travis’s corrections.
Chris Cavanagh // June 20, 2009 at 10:37 pm |
Chuck – Actually I’ve already fixed the code, but it’s a little different to Travis’s suggestion. It’s basically the same as AuthorizeAttribute’s AuthorizeCore method, but I’d originally mis-typed it. The only real change was inverting the Contains / All expressions:
if ( users.Length > 0 && !users.Contains( …
Let me know if the logic still seems wrong though (I could easily have missed something else).
Thanks for the feedback!