Skip to content

Instantly share code, notes, and snippets.

@vinneyk
Created December 27, 2013 15:19
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vinneyk/8148425 to your computer and use it in GitHub Desktop.
Save vinneyk/8148425 to your computer and use it in GitHub Desktop.
Early prototype of ClaimsAuthorizedNavigationMenuHelper. Create navigation menus with authorization trimming using Thinktecture's `ClaimsAuthorizeAttribute` and `ClaimsAuthorization` class. Dependends on Thinktecture.IdentityModel. Requires no configuration outside of the necessary Claims Auth setup. Please send comments and suggestions!
// Partial view to render the final, security-trimmed navigation menu
// only those items for which the current user is authorized will be rendered.
// You would like want to output cache this action result varying by user.
@Html.BuildNavigation(new List<NavigationItem>
{
new NavigationItem("Production", MVC.Production.Home.Index()),
new NavigationItem("Inventory", MVC.Inventory.Home.Index()),
new NavigationItem("Quality Control", MVC.QualityControl.Home.Index()),
new NavigationItem("Customers", MVC.Sales.Home.Index()),
new NavigationItem("Vendors", MVC.Vendors.Companies.Index()),
})
public static class ClaimsAuthorizationHelper
{
public static bool CheckAccess(RequestContext requestContext)
{
var routeData = requestContext.RouteData;
var controllerName = routeData.Values["controller"] as string;
var actionName = routeData.Values["action"] as string;
var controller = GetControllerByName(requestContext, controllerName);
var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
var controllerContext = new ControllerContext(requestContext, controller);
var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
var resourceClaims = actionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof (ClaimsAuthorizeAttribute), false)
.Cast<ClaimsAuthorizeAttribute>()
.SelectMany(auth => auth.GetClaims()).ToList();
resourceClaims.AddRange(actionDescriptor.GetCustomAttributes(typeof(ClaimsAuthorizeAttribute), false).Cast<ClaimsAuthorizeAttribute>()
.SelectMany(c => c.GetClaims()));
var hasAccess = ClaimsAuthorization.CheckAccess(actionName, resourceClaims.ToArray());
return hasAccess;
}
public static ControllerBase GetControllerByName(RequestContext requestContext, string controllerName)
{
var factory = ControllerBuilder.Current.GetControllerFactory();
var controller = factory.CreateController(requestContext, controllerName);
if (controller == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "The current controller factory, \"{0}\", did not return a controller for the name \"{1}\".", factory.GetType(), controllerName));
}
return (ControllerBase)controller;
}
}
public class ClaimsAuthorizeAttribute : Thinktecture.IdentityModel.Authorization.Mvc.ClaimsAuthorizeAttribute
{
private readonly string _action;
private readonly string[] _resources;
public ClaimsAuthorizeAttribute(string action, params string[] resources)
:base(action, resources)
{
_action = action;
_resources = resources;
}
public IEnumerable<Claim> GetClaims()
{
return _resources.Select(r => new Claim(_action, r));
}
}
public static class NavigationHelper
{
public static MvcHtmlString BuildNavigation(this HtmlHelper htmlHelper, IEnumerable<NavigationItem> navigationItems)
{
var container = new TagBuilder("ul");
container.MergeAttribute("id", "menu");
var innerHtmlBuilder = new StringBuilder();
foreach (var item in navigationItems.Where(item => IsAuthorized(htmlHelper, item.RouteValueDictionary)))
{
innerHtmlBuilder.Append(
new TagBuilder("li")
{
InnerHtml = htmlHelper.ActionLink(
item.LinkText,
item.RouteValueDictionary["action"] as string,
item.RouteValueDictionary["controller"] as string,
item.RouteValueDictionary, null).ToHtmlString()
});
}
container.InnerHtml = innerHtmlBuilder.ToString();
return new MvcHtmlString(container.ToString());
}
private static bool IsAuthorized(this HtmlHelper htmlHelper, RouteValueDictionary routeValues)
{
var routeData = BuildRouteData(htmlHelper.RouteCollection, routeValues);
var context = BuildRequestContext(htmlHelper, routeData);
return ClaimsAuthorizationHelper.CheckAccess(context);
}
private static RouteData BuildRouteData(IEnumerable<RouteBase> routeCollection, RouteValueDictionary routeValues)
{
object controllerValue;
routeValues.TryGetValue("controller", out controllerValue);
var controllerName = controllerValue as string;
object actionValue;
routeValues.TryGetValue("action", out actionValue);
var actionName = actionValue as String;
object areaValue;
routeValues.TryGetValue("area", out areaValue);
var areaName = areaValue as String ?? "";
var routeData = new RouteData();
routeData.Values.Add("action", actionName);
routeData.Values.Add("controller", controllerName);
routeData.Values.Add("area", areaName);
AddNamespaceInfo(routeData, routeCollection, areaName, controllerName, actionName);
return routeData;
}
private static RequestContext BuildRequestContext(this HtmlHelper htmlHelper, RouteData routeData)
{
var claimsPrincipal = htmlHelper.ViewContext.HttpContext.User as ClaimsPrincipal;
var requestContext = new RequestContext(htmlHelper.ViewContext.HttpContext, routeData);
requestContext.HttpContext.User = claimsPrincipal;
return requestContext;
}
private static void AddNamespaceInfo(RouteData routeData, IEnumerable<RouteBase> routeCollection, string areaName, string controllerName, string actionName)
{
var route = routeCollection.GetRoute(areaName, controllerName, actionName);
if (route != null)
{
routeData.DataTokens.Add("Namespaces", route.DataTokens["Namespaces"]);
}
}
public class NavigationItem
{
public NavigationItem() { }
// Convenience constrictor for T4MVC. Can be removed if not using T4MVC.
public NavigationItem(string linkText, ActionResult actionResult)
: this(linkText, actionResult.GetRouteValueDictionary()) { }
public NavigationItem(string linkText, RouteValueDictionary routeValues)
{
LinkText = linkText;
RouteValueDictionary = routeValues;
}
public string LinkText { get; set; }
public RouteValueDictionary RouteValueDictionary { get; set; }
}
internal static class RouteHelpers
{
public static string GetAreaName(this RouteBase route)
{
return route.GetDataTokenValue("area");
}
public static string GetControllerName(this RouteBase route)
{
return route.GetDefaultValue("controller");
}
public static string GetActionName(this RouteBase route)
{
return route.GetDefaultValue("action");
}
private static string GetDataTokenValue(this RouteBase route, string key)
{
var castRoute = route as Route;
object rawValue = null;
if (castRoute != null && castRoute.DataTokens != null && castRoute.DataTokens.TryGetValue(key, out rawValue))
{
return rawValue as string;
}
return null;
}
private static string GetDefaultValue(this RouteBase route, string key)
{
var castRoute = route as Route;
object rawValue = null;
if (castRoute != null && castRoute.Defaults != null && castRoute.Defaults.TryGetValue(key, out rawValue))
{
return rawValue as string;
}
return null;
}
public static Route GetRoute(this IEnumerable<RouteBase> routeCollection, string areaName, string controllerName, string actionName)
{
var routes = String.IsNullOrWhiteSpace(areaName)
? routeCollection
: FilterRoutesByAreaName(routeCollection, areaName);
return (from routeBase in routes
let thisRoute = routeBase as Route
where thisRoute != null
&& thisRoute.GetControllerName().Equals(controllerName, StringComparison.OrdinalIgnoreCase)
&& thisRoute.GetActionName().Equals(actionName, StringComparison.OrdinalIgnoreCase)
select thisRoute).FirstOrDefault();
}
public static IEnumerable<RouteBase> FilterRoutesByAreaName(IEnumerable<RouteBase> routes, string areaName)
{
return from route in routes
let area = route.GetAreaName() ?? String.Empty
where String.Equals(areaName, area, StringComparison.OrdinalIgnoreCase)
select route;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment