I have a number of Controllers in my project that all inherit from a controller I've named BaseController. I wrote a custom attribute that I applied to the entire BaseController class, so that each time an action runs in any of my controllers, that attribute will run first.
The problem is that I have a couple of controller actions that I'd like to ignore that attribute, but I don't know how to do it.
Can anyone help? I'm using MVC 1.
Thanks.
In your custom attribute, you can add this ShouldRun() check like this:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (ShouldRun(filterContext))
{
// proceed with your code
}
}
private bool ShouldRun(ActionExecutingContext filterContext)
{
var ignoreAttributes = filterContext.ActionDescriptor.GetCustomAttributes(typeof(IgnoreMyCustomAttribute), false);
if (ignoreAttributes.Length > 0)
return false;
return true;
}
ShouldRun() simply checks whether there's a "IgnoreMyCustomAttribute" on your action. If it's there, then your custom attribute won't do anything.
You'll now want to create a simple IgnoreMyCustomAttribute, which doesn't do anything:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class IgnoreMyCustomAttribute: ActionFilterAttribute
{
}
Whenever you decorate your controller action with [IgnoreMyCustom], then MyCustomAttribute won't do anything. e.g.:
[IgnoreMyCustom]
public ViewResult MyAction() {
}
I had a similar need for something like this and found that by creating an authorization filter (implementing/deriving from FilterAttribute, IAuthorizationFilter) rather than a regular action filter (deriving from ActionFilterAttribute), and setting Inherited=true and AllowMultiple=false on the attribute, that it would only run once at the appropriate spot.
This means I am able to "cascade" my filter down from a base controller (the site-wide default), to a derived controller (for example the AdminController or whatever), or even further down to an individual action method.
For example,
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, Inherited=true, AllowMultiple=false)]
public class MyCustomAttribute : FilterAttribute, IAuthorizationFilter
{
private MyCustomMode _Mode;
public MyCustomAttribute(MyCustomMode mode)
{
_Mode = mode;
}
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
// run my own logic here.
// set the filterContext.Result to anything non-null (such as
// a RedirectResult?) to skip the action method's execution.
//
//
}
}
public enum MyCustomMode
{
Enforce,
Ignore
}
And then to use it, I can apply it to my super-controller,
[MyCustomAttribute(Ignore)]
public class BaseController : Controller
{
}
And I can change/override it for specific controllers, or even for specific actions!
[MyCustomAttribute(Enforce)]
public class AdministrationController : BaseController
{
public ActionResult Index()
{
}
[MyCustomAttribute(Ignore)]
public ActionResult SomeBasicPageSuchAsAHelpDocument()
{
}
}
This allowed me to "turn off" the filter for specific cases, while still being able to apply it as a default on either the whole controller or whole application.
Good luck!
I'm not sure there is an easy way to remove attributes in this situation. But I have done something similar for a project and what I did, as it was only in a few instances I didn't want my attribute to run, was to create two attributes.
My first attribute was applied to my base controller as you've done but it was aware of the existance of a second attribute and by implementing that second attribute I could disable the attribute on the base class from running.
Not sure if it was the best solution but it worked for me.
This was applied to the base controller:
/// <summary>
/// This is used to force the schema to HTTP is it is HTTPS.
/// RequireHttpsAttribute or OptionalHttpsAttribute takes precedence if used.
/// </summary>
public class RequireHttpAttribute : FilterAttribute, IAuthorizationFilter
{
public virtual void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
throw new ArgumentNullException("filterContext");
if (filterContext.HttpContext.Request.IsSecureConnection)
{
object[] attributes = filterContext.ActionDescriptor.GetCustomAttributes(true);
if (!attributes.Any(a => a is RequireHttpsAttribute || a is OptionalHttpsAttribute))
{
HandleHttpsRequest(filterContext);
}
}
}
protected virtual void HandleHttpsRequest(AuthorizationContext filterContext)
{
// only redirect for GET method, otherwise browser may not propogate the verb and request body correctly
if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException(MvcResources.RequireHttpAttribute_MustNotUseSsl);
// redirect to HTTP version
string url = "http://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
Like so:
[RequireHttp]
public abstract class Controller : System.Web.Mvc.Controller
{
}
I could then use what is effectively a dummy attribute to disable it.
/// <summary>
/// This attribute allows the action to be server on HTTP and HTTPS but neither is enforce.
/// RequireHttpsAttribute takes precedence if used.
/// </summary>
public class OptionalHttpsAttribute : FilterAttribute
{
// This is deliberately empty, the attribute is used by RequireHttpAttribute to stop it changing schema to HTTP
}
Like so:
[OptionalHttps]
public ActionResult OptionalHttps()
{
return View();
}
Related
I have created a custom authorize attribute, but I need some actions to allow anonymous access. I've tried three different approaches without success: use AllowAnonymous, update the existing attribute with additional parameters, and create a new overriding attribute. Basically it seems that the controller-level attribute always gets called before the action-level attribute.
Here's the controller:
[AuthorizePublic(Sites = AuthSites.Corporate)]
public class CorporateController : SecuredController
{
[AuthorizePublic(Sites = AuthSites.Corporate, AllowAnonymous = true)]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
}
And the attribute:
public class AuthorizePublic : AuthorizeAttribute
{
public AuthSites Sites { get; set; }
public bool AllowAnonymous { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// Logic
}
}
As a last resort I can move the login actions onto their own controller, but before I do that, am I missing something to get one of these approaches to work? I'm a bit surprised that action-level attributes aren't overriding controller-level attributes.
It is the implementation of the OnAuthorization method of AuthorizeAttribute that scans for AllowAnonymousAttribute. So, you must either not override this method or re-implement this check if you want that part to work. Since you have only provided a cut-down implementation of AuthorizeAttribute, it cannot be assumed that you are not overriding this method (and thus overriding the logic that makes the check).
Also, your example controller doesn't actually show usage of the AllowAnonymousAttribute. Instead, it sets a property named AllowAnonymous. If you expect anonymous users to reach that action method, you should decorate it with the attribute that MVC is actually scanning for.
[AuthorizePublic(Sites = AuthSites.Corporate)]
public class CorporateController : SecuredController
{
[AllowAnonymous]
public ActionResult Login(string returnUrl)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
}
Alternatively, if you need to customize the AllowAnonymous behavior in some way, you can keep using the property you have, but you have to implement the Reflection code yourself to scan for AuthorizePublic and check the AllowAnonymous property.
public class AuthorizePublic : AuthorizeAttribute
{
public AuthSites Sites { get; set; }
public bool AllowAnonymous { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var actionDescriptor = httpContext.Items["ActionDescriptor"] as ActionDescriptor;
if (actionDescriptor != null)
{
AuthorizePublic attribute = GetAuthorizePublicAttribute(actionDescriptor);
if (attribute.AllowAnonymous)
return true;
var sites = attribute.Sites;
// Logic
}
return true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
// Pass the current action descriptor to the AuthorizeCore
// method on the same thread by using HttpContext.Items
filterContext.HttpContext.Items["ActionDescriptor"] = filterContext.ActionDescriptor;
base.OnAuthorization(filterContext);
}
// Gets the Attribute instance of this class from an action method or contoroller.
// An action method will override a controller.
private AuthorizePublic GetAuthorizePublicAttribute(ActionDescriptor actionDescriptor)
{
AuthorizePublic result = null;
// Check if the attribute exists on the action method
result = (AuthorizePublic)actionDescriptor
.GetCustomAttributes(attributeType: typeof(AuthorizePublic), inherit: true)
.SingleOrDefault();
if (result != null)
{
return result;
}
// Check if the attribute exists on the controller
result = (AuthorizePublic)actionDescriptor
.ControllerDescriptor
.GetCustomAttributes(attributeType: typeof(AuthorizePublic), inherit: true)
.SingleOrDefault();
return result;
}
}
AuthorizeAttribute implements both Attribute and IAuthorizationFilter. With that in mind, the IAuthorizationFilter part of AuthorizeAttribute is a different runtime instance of the class than the Attribute part. So the former must use Reflection to read the property of the latter in order for it to work. You can't just read the AllowAnonymous property from the current instance and expect it to work, because you are setting the value in the attribute and the code is executing in the filter.
MVC and Web API are completely separate frameworks with their own separate configuration even though they can co-exist in the same project. MVC will completely ignore any controllers or attributes defined in Web API and vise versa.
I commonly need to authorize a particular parameter to be evaluated in a service call within an action in MVC5. For instance, let's say that my action is public ActionResult Edit(string partnerName).
Today, I handle this by always evaluating if (!User.CanAccessPartnerModule(THIS_MODULE_ID, partnerName)) throw new UnauthorizedException();
However, I would like to be able to do something like this:
[Authorize(Roles = THIS_MODULE_ID)]
public ActionResult Edit([AuthorizePartnerModule(THIS_MODULE_ID)] string partnerName)
{
...
}
To be clear, 1) I don't think the AuthorizeAttribute would be necessary if this were implemented as I envision, and 2) the thing that doesn't exist is the AuthorizePartnerModuleAttribute.
Is there a ready-made attribute or tutorial that explains how this may be accomplished? And if not, is this not advisable to do?
You could extend authorization with a custom authorization filter by creating a subclass of AuthorizeAttribute
using System.Web;
using System.Web.Mvc;
namespace Filters
{
public class AuthorizePartnerModule : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
// getting the parameter from the request
string partnerName = httpContext.Request.Params["groupId"].ToString();
// custom validation
return User.CanAccessPartnerModule(THIS_MODULE_ID, partnerName);
}
}
}
And then, you could validate your action method with:
[AuthorizePartnerModule(Roles = THIS_MODULE_ID)]
public ActionResult Edit(string partnerName)
{
...
}
Another option would be to create a custom ActionFilter (an implementation of IActionFilter). An ActionFilter implements two methods:
OnActionExecuting is executed right before the action method
OnActionExecuted is executed right after the action method execution.
So, you could make the necessary validation with something like:
using System.Web.Mvc;
namespace Filters {
public class AuthorizePartnerModule : FilterAttribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext filterContext)
{
// getting the parameter from the request
string partnerName = filterContext.ActionParameters["partnerName"].ToString();
// custom validation
if (!User.CanAccessPartnerModule(THIS_MODULE_ID, partnerName))
{
filterContext.Result = new HttpNotFoundResult();
}
}
public void OnActionExecuted(ActionExecutedContext filterContext)
{
// do nothing
}
}
}
In this case, however ,you would have to validate like that:
[Authorize(Roles = THIS_MODULE_ID)]
[AuthorizePartnerModule]
public ActionResult Edit(string partnerName)
{
...
}
I want to redirect the user to a different view if they are using a mobile browser. I've decided I'd like to do this using MVC filters by applying it to actions which I want to have a mobile view.
I believe this redirect needs to happen in OnActionExecuted, however the filterContext does not contain information on the view - it does, however in OnResultExecuted, but by this time I believe it is too late to change the view.
How can I intercept the view name and change the ViewResult?
This is what I have in the result executed and what I'd like to have work in Action Executed.
public class MobilePageFilter : ActionFilterAttribute
{
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if(filterContext.Result is ViewResult)
{
if (isMobileSite(filterContext.HttpContext.Session[SetMobile.SESSION_USE_MOBILE]))
{
ViewResult viewResult = (ViewResult)filterContext.Result;
string viewName = viewResult.ViewName;
filterContext.Result = new ViewResult
{
ViewName = "Mobile/" + viewName,
ViewData = viewResult.ViewData,
TempData = viewResult.TempData
};
}
}
base.OnResultExecuted(filterContext);
}
}
I would recommend you the following blog post which explains a better alternative to achieve what you are asking for rather than using action filters.
This is what I ended up doing, and wrapped up into a reusable attribute and the great thing is it retains the original URL while redirecting (or applying whatever result you wish) based on your requirements:
public class AuthoriseSiteAccessAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Perform your condition, or straight result assignment here.
// For me I had to test the existance of a cookie.
if (yourConditionHere)
filterContext.Result = new SiteAccessDeniedResult();
}
}
public class SiteAccessDeniedResult : ViewResult
{
public SiteAccessDeniedResult()
{
ViewName = "~/Views/SiteAccess/Login.cshtml";
}
}
Then just add the attribute [SiteAccessAuthorise] to your controllers you wish to apply the authorisation access to (in my case) or add it to a BaseController. Make sure though the action you are redirecting to's underlying controller does not have the attribute though, or you'll be caught in an endless loop!
I've run into several cases in ASP.NET MVC where I wanted to apply an action filter on every action except one or two. For example, say you have an AccountController. Every action in it requires the user be logged in, so you add [Authorize] at the controller level. But say you want to include the login page in AccountController. The problem is, users sent to the login page aren't authorized, so this would result in an infinite loop.
The obvious fix (other than moving the Login action to another controller) is to move the [Authorize] from the controller to all action methods except Login. Well that ain't fun, especially when you have a lot of methods or forget to add [Authorize] to a new method.
Rails makes this easy with an ability to exclude filters. ASP.NET MVC doesn't let you. So I decided to make it possible and it was easier than I thought.
/// <summary>
/// This will disable any filters of the given type from being applied. This is useful when, say, all but on action need the Authorize filter.
/// </summary>
[AttributeUsage(AttributeTargets.Method|AttributeTargets.Class, AllowMultiple=true)]
public class ExcludeFilterAttribute : ActionFilterAttribute
{
public ExcludeFilterAttribute(Type toExclude)
{
FilterToExclude = toExclude;
}
/// <summary>
/// The type of filter that will be ignored.
/// </summary>
public Type FilterToExclude
{
get;
private set;
}
}
/// <summary>
/// A subclass of ControllerActionInvoker that implements the functionality of IgnoreFilterAttribute. To use this, just override Controller.CreateActionInvoker() and return an instance of this.
/// </summary>
public class ControllerActionInvokerWithExcludeFilter : ControllerActionInvoker
{
protected override FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
//base implementation does all the hard work. we just prune off the filters to ignore
var filterInfo = base.GetFilters(controllerContext, actionDescriptor);
foreach( var toExclude in filterInfo.ActionFilters.OfType<ExcludeFilterAttribute>().Select(f=>f.FilterToExclude).ToArray() )
{
RemoveWhere(filterInfo.ActionFilters, filter => toExclude.IsAssignableFrom(filter.GetType()));
RemoveWhere(filterInfo.AuthorizationFilters, filter => toExclude.IsAssignableFrom(filter.GetType()));
RemoveWhere(filterInfo.ExceptionFilters, filter => toExclude.IsAssignableFrom(filter.GetType()));
RemoveWhere(filterInfo.ResultFilters, filter => toExclude.IsAssignableFrom(filter.GetType()));
}
return filterInfo;
}
/// <summary>
/// Removes all elements from the list that satisfy the condition. Returns the list that was passed in (minus removed elements) for chaining. Ripped from one of my helper libraries (where it was a pretty extension method).
/// </summary>
private static IList<T> RemoveWhere<T>(IList<T> list, Predicate<T> predicate)
{
if (list == null || list.Count == 0)
return list;
//note: didn't use foreach because an exception will be thrown when you remove items during enumeration
for (var i = 0; i < list.Count; i++)
{
var item = list[i];
if (predicate(item))
{
list.RemoveAt(i);
i--;
}
}
return list;
}
}
/// <summary>
/// An example of using the ExcludeFilterAttribute. In this case, Action1 and Action3 require authorization but not Action2. Notice the CreateActionInvoker() override. That's necessary for the attribute to work and is probably best to put in some base class.
/// </summary>
[Authorize]
public class ExampleController : Controller
{
protected override IActionInvoker CreateActionInvoker()
{
return new ControllerActionInvokerWithExcludeFilter();
}
public ActionResult Action1()
{
return View();
}
[ExcludeFilter(typeof(AuthorizeAttribute))]
public ActionResult Action2()
{
return View();
}
public ActionResult Action3()
{
return View();
}
}
The example is right there. As you can see, this was pretty straightforward to do and works great. I hope it's useful to anyone?
I prefer the solution outlined here. Though it's not as generic a solution as yours, I found it a bit more straightforward.
In my case, I was looking for a way to enable a CompressionFilter on everything but a few items. So I created an empty attribute like this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class DisableCompression : Attribute { }
Then in the main attribute, check for the presence of the attribute like so:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class CompressionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
bool disabled = filterContext.ActionDescriptor.IsDefined(typeof(DisableCompression), true) ||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(DisableCompression), true);
if (disabled)
return;
// action filter logic here...
}
}
Though the page I linked to mentions that this is for MVC 3, it seems to work well enough way back in MVC 1 as well.
EDIT: showing some usage here in response to comments. Before I made the changes above, it looked exactly like this, except without the [DisableCompression] attribute flagging the method I wanted to exclude. There's no other refactoring involved.
[CompressionFilter]
public abstract class BaseController : Controller
{
}
public class SomeController : BaseController
{
public ActionResult WantThisActionCompressed()
{
// code
}
[DisableCompression]
public ActionResult DontWantThisActionCompressed()
{
// code
}
}
I assume for years ago that the [AllowAnnonymous] attribute hadn't been added to ASP.NET MVC. Today I can have the [Authorize] attribute on top of my controller applying to all the Action methods and I just simply override this in Actions I require unauthorized users by adding the [AllowAnonymous] attributes to the specific actions.
If I want only administrator to access the action called "ManagerUser", I know I can do this:
[Authorize( Roles = Constants.ROLES_ADMINISTRATOR )]
public ActionResult ManageUser( string id )
{
}
What if I want to give everyone access except to administrator? I do not want to write all roles up there on function :|.
Any recommendations/way outs?
You can create your own custom Authorize attribute, something like "AuthorizeAllExceptAdmin." Within that class you would simply need to check whether or not the current user was an admin, and if they were reject it, otherwise accept it.
Here's a good tutorial, but you'll probably end up with something like:
public class AuthorizeAllExceptAdmin : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !httpContext.User.IsInRole(Constants.ROLES_ADMINISTRATOR);
}
}
Then your controller method becomes:
[AuthorizeAllExceptAdmin]
public ActionResult SomethingOnlyNonAdminsCanDo()
{
}
Here's an example of the custom attribute that takes in roles to deny.
public class DoNotAuthorize : AuthorizeAttribute
{
private IEnumerable<string> _rolesToReject;
public DoNotAuthorize(IEnumerable<string> rolesToReject)
{
_rolesToReject = rolesToReject;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
foreach (var role in _rolesToReject)
{
if (httpContext.User.IsInRole(role))
return false;
}
return true;
}
}
Then your controller method becomes:
[DoNotAuthorize(new [] {Constants.ROLES_ADMINISTRATOR})]
public ActionResult SomethingOnlyNonAdminsCanDo()
{
}
I would put some thought into it before choosing one of the above options. If you think you'll have several methods (or entire controllers) with similar authorization requirements (i.e, several actions an admin can not perform) then I would stick with the non-parameterized custom attribute. This way, you can evolve them all together (by only changing the custom attribute) later on. For example, maybe later on you want admins to be able to go into a special mode where they can perform these actions.
Alternatively, if the autorization is more varied amongst the actions, then using the parameterized list makes sense, since they'll evolve relatively independently.
Besides creating a custom AuthorizeAttribute, suggested by manu, you could use PrincipalPermission, with a Deny-SecurityAction:
[PrincipalPermission(SecurityAction.Deny, Role="Administrator")]
In my app I don't use roles so I have to query the database to determine whether the user has access or not. The benefits of the code below is that you can redirect the user to a certain action very easily. I explained the code in my blog post at http://blog.athe.la/2009/12/implementing-permission-via-windows-authentication-in-asp-mvc-using-action-filters/
public class DatabaseRepository()
{
private readonly DatabaseDataContext db = new DatabaseDataContext();
public bool UserHasPermission(string userLogon) {
return (from permission this.db.Permissions
where permission.HasPermissionSw == true
select permission).Contains(userLogon);
}
}
public class UserHasPermission: ActionFilterAttribute
{
private readonly DatabaseRepository databaseRepository = new DatabaseRepository();
private readonly string redirectAction;
public UserHasPermission(string redirectTo)
{
this.redirectAction = redirectTo;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string userLogon = filterContext.HttpContext.User.Identity.Name;
if (!this.databaseRepository.UserHasPermission(userLogon))
{
string routeController = filterContext.Controller.ControllerContext.RouteData.Values["controller"];
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = routeController, action = this.redirectAction }));
}
}
}
Your controller would then look something like this:
[UserHasPermission("NoAccess")]
public ActionResult SecretArea()
{
// run all the logic
return View();
}
public ActionResult NoAccess()
{
return View();
}