So after searching for a robust security solution for my MVC3 app, I came across this blog post by Rick Anderson. It details a WhiteList approach where a custom implementation o
There is where I'm at so far. I feel like I've bastardized it but I'm not sure how else to do it with the requirements I'm looking to satisfy. Am I going about this all wrong or is it just in my head?
public class LogonAuthorize : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
{
// If a child action cache block is active, we need to fail immediately, even if authorization
// would have succeeded. The reason is that there's no way to hook a callback to rerun
// authorization before the fragment is served from the cache, so we can't guarantee that this
// filter will be re-run on subsequent requests.
throw new InvalidOperationException("AuthorizeAttribute cannot be used within a child action caching block."); //Text pulled from System.Web.Mvc.Resources
}
// Bypass authorization on any action decorated with AllowAnonymousAttribute, indicationg the page allows anonymous access and
// does not restrict access anyone (Similar to a WhiteList security model).
bool allowAnnonymous = filterContext.ActionDescriptor.IsDefined(typeof (AllowAnonymousAttribute), true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof (AllowAnonymousAttribute), true);
if (allowAnnonymous) return;
if (CustomAuthorizeCore(filterContext))
{
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, filterContext); //CacheValidateHandler doesn't have access to our AuthorizationContext, so we pass it in using the data object.
}
HandleUnauthorizedRequest(filterContext);
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
var filterContext = (AuthorizationContext)data;
validationStatus = CustomOnCacheAuthorization(filterContext);
}
protected HttpValidationStatus CustomOnCacheAuthorization(AuthorizationContext filterContext)
{
if (filterContext.HttpContext == null)
{
throw new ArgumentNullException("filterContext.HttpContext");
}
bool isAuthorized = CustomAuthorizeCore(filterContext);
return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest;
}
protected bool CustomAuthorizeCore(AuthorizationContext filterContext)
{
HttpContextBase httpContext = filterContext.HttpContext;
if (httpContext == null)
throw new ArgumentNullException("filterContext.HttpContext");
Trace.WriteLine("Current User: " + (httpContext.User.Identity.IsAuthenticated ? httpContext.User.Identity.Name : "Anonymous"));
if (!httpContext.User.Identity.IsAuthenticated)
return false;
string objectId = (httpContext.Request.RequestContext.RouteData.Values["id"] ?? Guid.Empty).ToString();
Trace.WriteLine("Hierarchy Permissions check for Object: " + objectId);
string controllerName = httpContext.Request.RequestContext.RouteData.GetRequiredString("controller");
string actionName = httpContext.Request.RequestContext.RouteData.GetRequiredString("action");
Trace.WriteLine("Policy Permissions check for Controller: " + controllerName + ", and Action: " + actionName);
//if(!CheckHierarchyPermissions || (!CheckHierarchyPermissions && !CheckBusinessLogicPermissions))
//{
// //Check database permissions by getting DB reference from DependancyResolver
// DependencyResolver.Current.GetService(typeof (SecurityService)); //change this to an interface later
// return false;
//}
return true;
}
#region Old methods decorated with Obsolete() attributes to track down unintended uses
[Obsolete("This overridden implementation of AuthorizeAttribute does not use the Users collection.", true)]
public new string Users { get; set; }
[Obsolete("This overridden implementation of AuthorizeAttribute does not use the Roles collection.", true)]
public new string Roles { get; set; }
[Obsolete("This overridden implementation of AuthorizeAttribute does not use the AuthorizeCore method.", true)]
protected new bool AuthorizeCore(HttpContextBase httpContext)
{
return false;
}
[Obsolete("This overridden implementation of AuthorizeAttribute does not use the OnCacheAuthorization method.", true)]
protected new virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
{
return HttpValidationStatus.Invalid;
}
#endregion
}
UPDATE: Just a quick update on this one, I never did find a way to dynamically build the name of the Role I was checking for from the combination of Action name and Controller name, and still work within the limitations of the way the requests are made and caching, etc. However the pattern of the WhiteList approach to authorization as detailed on the Blog I linked above is included in MVC4. MVC4 is beta only at the moment, but I don't expect they'll remove it between now and the final version.