Created
February 21, 2015 16:23
-
-
Save NightOwl888/30bef95ee9dd0b24c870 to your computer and use it in GitHub Desktop.
AuthorizeAttributeAclModule for use with routes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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