|
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; |
|
using MvcSiteMapProvider.Security; |
|
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 AuthorizeAttributeAclModule2 |
|
: 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; |
|
//var requestContext = nodeHttpContext.Request.RequestContext; |
|
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); |
|
} |
|
} |
|
} |
|
} |