Skip to content

Instantly share code, notes, and snippets.

@NightOwl888
Created February 21, 2015 16:23
Show Gist options
  • Save NightOwl888/30bef95ee9dd0b24c870 to your computer and use it in GitHub Desktop.
Save NightOwl888/30bef95ee9dd0b24c870 to your computer and use it in GitHub Desktop.
AuthorizeAttributeAclModule for use with routes
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Async;
using System.Web.Routing;
using MvcSiteMapProvider.Web.Mvc;
using MvcSiteMapProvider.Web.Mvc.Filters;
using MvcSiteMapProvider.Web.Routing;
namespace MvcSiteMapProvider.Security
{
/// <summary>
/// An ACL module that determines whether the current user has access to a given node based on the MVC AuthorizeAttribute.
/// </summary>
public class AuthorizeAttributeAclModule
: IAclModule
{
public AuthorizeAttributeAclModule(
IMvcContextFactory mvcContextFactory,
IControllerDescriptorFactory controllerDescriptorFactory,
IControllerBuilder controllerBuilder,
IGlobalFilterProvider filterProvider
)
{
if (mvcContextFactory == null)
throw new ArgumentNullException("mvcContextFactory");
if (controllerDescriptorFactory == null)
throw new ArgumentNullException("controllerDescriptorFactory");
if (controllerBuilder == null)
throw new ArgumentNullException("controllerBuilder");
if (filterProvider == null)
throw new ArgumentNullException("filterProvider");
this.mvcContextFactory = mvcContextFactory;
this.controllerDescriptorFactory = controllerDescriptorFactory;
this.controllerBuilder = controllerBuilder;
this.filterProvider = filterProvider;
}
protected readonly IMvcContextFactory mvcContextFactory;
protected readonly IControllerDescriptorFactory controllerDescriptorFactory;
protected readonly IControllerBuilder controllerBuilder;
protected readonly IGlobalFilterProvider filterProvider;
#region IAclModule Members
/// <summary>
/// Determines whether node is accessible to user.
/// </summary>
/// <param name="siteMap">The site map.</param>
/// <param name="node">The node.</param>
/// <returns>
/// <c>true</c> if accessible to user; otherwise, <c>false</c>.
/// </returns>
public bool IsAccessibleToUser(ISiteMap siteMap, ISiteMapNode node)
{
// Not Clickable? Always accessible.
if (!node.Clickable)
return true;
var httpContext = mvcContextFactory.CreateHttpContext();
// Is it an external Url?
if (node.HasExternalUrl(httpContext))
return true;
return this.VerifyNode(siteMap, node, httpContext);
}
#endregion
#region Protected Members
protected virtual bool VerifyNode(ISiteMap siteMap, ISiteMapNode node, HttpContextBase httpContext)
{
// Create a TextWriter with null stream as a backing stream
// which doesn't consume resources
using (var nullWriter = new StreamWriter(Stream.Null))
{
var nodeHttpContext = this.CreateHttpContextForNode(node, httpContext, nullWriter);
var routes = nodeHttpContext.Request.RequestContext.RouteData;
if (routes == null)
return true; // Static URLs will sometimes have no route data, therefore return true.
// Time to delve into the AuthorizeAttribute defined on the node.
// Let's start by getting all metadata for the controller...
var controllerType = siteMap.ResolveControllerType(routes.GetAreaName(), routes.GetOptionalString("controller"));
if (controllerType == null)
return true;
return this.VerifyController(nodeHttpContext, controllerType);
}
}
protected virtual bool VerifyController(HttpContextBase nodeHttpContext, Type controllerType)
{
// Get controller factory
var controllerFactory = controllerBuilder.GetControllerFactory();
// Create controller context
bool factoryBuiltController = false;
var controllerContext = this.CreateControllerContext(nodeHttpContext.Request.RequestContext, controllerType, controllerFactory, out factoryBuiltController);
try
{
return this.VerifyControllerAttributes(nodeHttpContext.Request.RequestContext.RouteData, controllerType, controllerContext);
}
finally
{
// Release controller
if (factoryBuiltController)
{
controllerFactory.ReleaseController(controllerContext.Controller);
}
else
{
var disposable = controllerContext.Controller as IDisposable;
if (disposable != null)
disposable.Dispose();
}
}
}
protected virtual HttpContextBase CreateHttpContextForNode(ISiteMapNode node, HttpContextBase httpContext, TextWriter writer)
{
// Create a Uri for the current node. If we have an absolute URL,
// it will be used instead of the baseUri.
var nodeUri = new Uri(httpContext.Request.Url, node.Url);
// Create a new HTTP context using the node's URL instead of the current one.
var result = this.mvcContextFactory.CreateHttpContext(node, nodeUri, writer);
// Set the User, RouteData and HttpContext - this is what is used by
// AuthorizationAttribute.AuthorizeCore
result.User = httpContext.User;
result.Request.RequestContext.RouteData = node.GetRouteData(result);
result.Request.RequestContext.HttpContext = result;
return result;
}
protected virtual bool VerifyControllerAttributes(RouteData routes, Type controllerType, ControllerContext controllerContext)
{
// Get controller descriptor
var controllerDescriptor = controllerDescriptorFactory.Create(controllerType);
if (controllerDescriptor == null)
return true;
// Get action descriptor
var actionDescriptor = this.GetActionDescriptor(routes.GetOptionalString("action"), controllerDescriptor, controllerContext);
if (actionDescriptor == null)
return true;
// Verify security
var authorizeAttributes = this.GetAuthorizeAttributes(actionDescriptor, controllerContext);
return this.VerifyAuthorizeAttributes(authorizeAttributes, controllerContext, actionDescriptor);
}
protected virtual bool VerifyAuthorizeAttributes(IEnumerable<AuthorizeAttribute> authorizeAttributes, ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
// Verify all attributes
foreach (var authorizeAttribute in authorizeAttributes)
{
try
{
var authorized = this.VerifyAuthorizeAttribute(authorizeAttribute, controllerContext, actionDescriptor);
if (!authorized)
return false;
}
catch
{
// do not allow on exception
return false;
}
}
return true;
}
#if MVC2
protected virtual IEnumerable<AuthorizeAttribute> GetAuthorizeAttributes(ActionDescriptor actionDescriptor, ControllerContext controllerContext)
{
return actionDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).OfType
<AuthorizeAttribute>().ToList()
.Union(
actionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).OfType
<AuthorizeAttribute>().ToList());
}
#else
protected virtual IEnumerable<AuthorizeAttribute> GetAuthorizeAttributes(ActionDescriptor actionDescriptor, ControllerContext controllerContext)
{
return filterProvider.GetFilters(controllerContext, actionDescriptor)
.Where(f => typeof(AuthorizeAttribute).IsAssignableFrom(f.Instance.GetType()))
.Select(f => f.Instance as AuthorizeAttribute);
}
#endif
protected virtual bool VerifyAuthorizeAttribute(AuthorizeAttribute authorizeAttribute, ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
var authorizationContext = this.mvcContextFactory.CreateAuthorizationContext(controllerContext, actionDescriptor);
// Set the HttpContext of the request - this is what is used by
// AuthorizationAttribute.AuthorizeCore
authorizationContext.HttpContext = controllerContext.HttpContext;
authorizeAttribute.OnAuthorization(authorizationContext);
if (authorizationContext.Result != null)
return false;
return true;
}
protected virtual ActionDescriptor GetActionDescriptor(string actionName, ControllerDescriptor controllerDescriptor, ControllerContext controllerContext)
{
ActionDescriptor actionDescriptor = null;
var found = this.TryFindActionDescriptor(actionName, controllerContext, controllerDescriptor, out actionDescriptor);
if (!found)
{
actionDescriptor = controllerDescriptor.GetCanonicalActions().Where(a => a.ActionName == actionName).FirstOrDefault();
}
return actionDescriptor;
}
protected virtual bool TryFindActionDescriptor(string actionName, ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, out ActionDescriptor actionDescriptor)
{
actionDescriptor = null;
try
{
var actionSelector = new ActionSelector();
actionDescriptor = actionSelector.FindAction(controllerContext, controllerDescriptor, actionName);
if (actionDescriptor != null)
return true;
}
catch
{
return false;
}
return false;
}
protected virtual ControllerContext CreateControllerContext(RequestContext requestContext, Type controllerType, IControllerFactory controllerFactory, out bool factoryBuiltController)
{
ControllerBase controller = null;
string controllerName = requestContext.RouteData.GetOptionalString("controller");
// Whether controller is built by the ControllerFactory (or otherwise by Activator)
factoryBuiltController = TryCreateController(requestContext, controllerName, controllerFactory, out controller);
if (!factoryBuiltController)
{
TryCreateController(controllerType, out controller);
}
// Create controller context
var controllerContext = mvcContextFactory.CreateControllerContext(requestContext, controller);
// Set the HttpContext of the node so it can be used later.
controllerContext.HttpContext = requestContext.HttpContext;
return controllerContext;
}
protected virtual bool TryCreateController(RequestContext requestContext, string controllerName, IControllerFactory controllerFactory, out ControllerBase controller)
{
controller = null;
if (controllerFactory != null)
{
try
{
controller = controllerFactory.CreateController(requestContext, controllerName) as ControllerBase;
if (controller != null)
return true;
}
catch
{
return false;
}
}
return false;
}
protected virtual bool TryCreateController(Type controllerType, out ControllerBase controller)
{
controller = null;
try
{
controller = Activator.CreateInstance(controllerType) as ControllerBase;
if (controller != null)
return true;
}
catch
{
return false;
}
return false;
}
#endregion
private class ActionSelector
: AsyncControllerActionInvoker
{
// Needed because FindAction is protected, and we are changing it to be public
public new ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
return base.FindAction(controllerContext, controllerDescriptor, actionName);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment