UPDATE 2: Improved IsAuthenticated helper method can be found here.
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" ) %>
[…] MVC IsAuthorized and AuthorizedActionLink (Chris Cavanagh) […]
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;
}
Travis – Ah you found my deliberate mistake 😉 (that’ll teach me to copy & paste from Reflector). Thanks for pointing it out!
Np, thanx for the code. I’m using it in a project and it seems to be working out great so far.
Chris you might want to update the
public static bool IsAuthorized(this AuthorizeAttribute authorize, IPrincipal user)
method with Travis’s corrections.
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!
🙂 I found another bug in the code Chris.
The argument for original.Split() in SplitString(…) should be a comma instead of a period. First time I am dealing with multiple roles today, that’s why its just now coming up for me.
Travis – Thanks! I might have some other improvements to post too… I’ll take a look 🙂
[…] 16, 2010 · Leave a Comment Around a year ago I wrote this post about an MVC HTML helper that could render a link, taking the current authorization state into […]