ASP.Net MVC: Can the AuthorizeAttribute be overriden? - c#

My current project is an internal web application built using ASP.Net MVC which I am adding authentication to. I have a pre-built HTTPModule which creates a IPrincipal with the appropriate roles. If the user isn't authenticated I get a user object with the role "Public"
As this is an internal application most of the pages are private and only viewable to the role "Admin". As I have a base controller I can do this:
[Authorize(Roles="Admin")]
public abstract class MyControllerBase : Controller
{
...
}
I have a problem though as some of the actions are viewable on a public website and if I attribute them like so:
[Authorize(Roles="Public")]
public class LoginController : MyController
{
public ActionResult Index()
{
}
}
The page fails to load as the user isn't authenticated. It would seem the Role of "Public is being ignored on the inherited class. Does anyone know if the roles can be overridden by inherited classes?
I am also trying to avoid attributing all the controllers with Roles="Admin"
Thanks, Keith.

You can derive a new attribute from AuthorizeAttribute and override the OnAuthorization method, then apply your customized attribute instead of Authorize. Below is the OnAuthorization method from one of my customized attributes that redirects to an error page if the privileges aren't sufficient instead of redirecting to the logon page.
I'm not sure exactly what this will buy you, though. When you decorate your class with the attribute, presumably you'll have to allow both Admin and Public (so who are you restricting since Public is anyone who is not authenticated?). You'd then have to decorate each of the controller methods that need to be restricted to Admin individually since the class attribute would allow access otherwise. You can achieve this behavior with the regular Authorize attribute by simply decorating just those non-publicly available methods (or classes that have no publicly available methods).
I suppose you could have your attribute check to see if the method being called is also decorated with the attribute and simply approve the authorization, which would effectively defer the authorization to the method level. You'd probably have to peek into the RouteData on the AuthorizationContext to get the action and use reflection to try and find the appropriate method based on parameters and request type.
public override void OnAuthorization( AuthorizationContext filterContext )
{
if (filterContext == null)
{
throw new ArgumentNullException( "filterContext" );
}
if (AuthorizeCore( filterContext.HttpContext ))
{
SetCachePolicy( filterContext );
}
else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
// auth failed, redirect to login page
filterContext.Result = new HttpUnauthorizedResult();
}
else
{
ViewDataDictionary viewData = new ViewDataDictionary();
viewData.Add( "Message", "You do not have sufficient privileges for this operation." );
filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData };
}
}
protected void SetCachePolicy( AuthorizationContext filterContext )
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge( new TimeSpan( 0 ) );
cachePolicy.AddValidationCallback( CacheValidateHandler, null /* data */);
}

Well in the end I think my answer was in the question. Instead of putting the Authorize attribute on my base controller I have derived a new AdminBaseController.
[HandleError]
public abstract class MyControllerBase : Controller
{
...
}
[Authorize(Roles="Admin")]
public abstract class AdminControllerBase : MyControllerBase
{
....
}
Now any controllers that require authentication can derive from AdminControllerBase while my public controllers can derive from MyControllerBase. OO to the rescue.

Related

How to redirect all pages in my mvc asp.net web app except for one role?

I work with asp.net c# mvc framework. I need a way to 'turn-off' my web app for all users except administrator (i. e. all pages should return to something like "The application is closed" for all the roles except Admin).
I already create a button in order to save the status of the web app (ON/OFF) in a DB.
Do I have to check on each page the status of the application ?
Is-it possible to have a global redirection except for one role ?
I don't know how to properly do this global closure. Any suggestions are welcomed.
I can think of three approaches to check and do a redircet
An HttpModule hooked into the appropriate, post-authorisation event. Presumably PostAuthorizeRequest of HttpApplication.
In your "global" (Global.aspx.cs) subscribe to that same event.
An MVC Action filter, overriding OnActionExecuting. (Ensure you make it global, to avoid needing to apply to every controller: add to GlobalFilters.Filters in your Application_Start.)
Of these 3 is part of MVC, but is much later in the pipeline (much more work will have been done, to be thrown away when the filter fails).
Use of a module is controlled by configuration which would make is easier to switch on and off.
option 2 is likely easiest to implement, but I would tend to prefer the modularity that 1 gives.
You can accomplish your requirement with the help of custom filters shown below :-
[CheckUserRole]
public class YourController : Controller
{
public ActionResult YourAction()
{
}
}
public class CheckUserRoleAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Get the User Id from the session
// Get Role associated with the user (probably from database)
// Get the permission associated with the role (like Read, write etc)
// if user is not authenticated then do as :
filterContext.Result = new RedirectToRouteResult(new
RouteValueDictionary(new { controller = "Error", action = "AccessDenied" }));
}
}
Did you tryActionFilterAttribute ?
Here is a basic example:
Your controller:
[IsAdmin]
public class YourController
{
}
Your attribute
public class IsAdminAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if () // Check that your user is not an Admin and that your application is "turn-off"
{
filterContext.Result = new HttpStatusCodeResult(403); // or whatever you want
}
}
}
Add [IsAdmin] on top of all your controllers.
You can write in all other Controllers which are used as follows..
public class HomeController : Controller
{
public ActionResult Index()
{
if (User.IsInRole("Administrator"))
return RedirectToAction("PagetoRedirect");
else
return RedirectToAction("CommonPagetoShowApplicationAsClosed");
}
}
Or
Action Filter, you can create on your own and look for named action like IndexRolename

How can I redirect to different views on authorization failure?

While using mvc intranet template with domain authorization, how can I redirect to some certain error page on authorization failure depending on the controller?
So, I have a controller class:
[AuthorizeWithRedirect(Users = #"user")]
public class MyController : Controller
{
...
}
By default I'm not redirected anywhere. I see only a blank page, if I open the page under another user. The issue is that I want the request to be redirected to different pages for different controllers if the authorization fails. That is, one page for MyController, another page for other controller, etc.
I know I could derive from AuthorizeAttribute and override HandleUnauthorizedRequest method. But I can't make it work:
public class AuthorizeWithRedirect : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext context)
{
UrlHelper urlHelper = new UrlHelper(context.RequestContext);
context.Result = new RedirectResult(urlHelper.Action("MyErrorPage"));
}
}
I get an error, saying that the specified url is not found, while MyErrorPage.cshtml does present in the Views\Shared folder.
EDIT
[Authorize(Users = #"user")]//should be redirected to ErrorPage1
public class MyController1 : Controller
{
...
}
while
[Authorize(Users = #"user")]//should be redirected to ErrorPage2
public class MyController2 : Controller
{
...
}
That is, different error pages for different controllers on one and the same authorization failure
The AuthorizeWithRedirect looks good.
But MVC is complaining that you do not have right action method.
The view is not an issue here. The controller and action method is.
Try something like this:
context.Result = new RedirectResult(urlHelper.Action("Test", "Redirect"));
With present TestController with an public action method Redirect.
Use the Handle Error Data Annotation on top of your login method
[HandleError]
public ActionResult Login()
{
//your action method
}

Authorize Attribute with multiple Role Providers

I am building a ASP.NET MVC App that will allow a user to connect to one of several databases (with the same schema) depending upon their selection. The user will select a database prior to login and then be authenticated against that database.
I have added added several role providers to web.config, each with a different connection string corresponding to each of the available databases.
I understand that I can access the role providers with the Roles.providers collection but I don't understand how I can choose which role provider is being user by the [Authorize] attribute in my controllers
My Controllers look something like this:
namespace MyApp.Controllers
{
[Authorize(Roles = "admin")]
public class AdminController : Controller
{
I assume that this will check the users role against the default role provider.
How can I select at run time which role provider will be used for the [Authorize] attribute?
As far as I know, there's no way to do what you're suggesting. Attributes are static metadata that can't be changed at run-time. However, even though it's less convenient, you can accomplish what you want by redirecting if the user doesn't have the appropriate role:
[Authorize]
public class AdminController : Controller {
public ActionResult ActionRequiringRoleFoo() {
if( !User.IsInRole( "foo" ) ) return RedirectToAction( "InsufficientPrivileges" );
return View();
}
If you want to have this behavior for all actions in your controller, you can override the OnActionExecuting method:
protected override void OnActionExecuting( ActionExecutingContext filterContext ) {
base.OnActionExecuting( filterContext );
if( filterContext.ActionDescriptor.ActionName != "InsufficientPrivileges" &&
!User.IsInRole( "anon" ) ) filterContext.Result = new RedirectResult( "InsufficientPrivileges" );
}
I hope that helps.

If all my .Net Controllers/Action need Authorize Attr, Why don't have a attribute to use only those who do not need?

I have a App that need authorization to access all Controllers/Actions. Except the Login and Error Controllers/Actions.
With this scenario, working in a defensive manner is better to keep default restrict access to all Controllers/Actions(without Authorize Attribute) and select with a custom Attribute only those who do not.
Have you guys done something like this?
I have a MVC Filter that execute before all Actions if the Logged User have access to them:
public class ValidatePermissionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
bool isAuthorized = false;
//Logic that verify if logged user have permission to access the requested Controller/Action
...
//Redirect to a page Error if Logged User don't have Authorization
if (!isAuthorized)
{
RouteValueDictionary redirectTargetDictionary = new RouteValueDictionary();
redirectTargetDictionary.Add("action", "Erro");
redirectTargetDictionary.Add("controller", "Index");
context.Result = new RedirectToRouteResult(redirectTargetDictionary);
}
}
}
I'm thinking the best way to do this. I can create a Blank Custom Attribute and put in the Controllers do not need authorization and check it in my Filter:
public class ValidatePermissionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
bool isAuthorized = false;
var DoNotRequiresAuthorizationAttributes = context.ActionDescriptor.GetCustomAttributes(typeof(DoNotRequiresAuthorizationAttribute), false);
if (DoNotRequiresAuthorizationAttributes.Length > 0)
isAuthorized = true;
...
//Redirect to a page Error if Logged User don't have Authorization
if (!isAuthorized)
{
RouteValueDictionary redirectTargetDictionary = new RouteValueDictionary();
redirectTargetDictionary.Add("action", "Erro");
redirectTargetDictionary.Add("controller", "Index");
context.Result = new RedirectToRouteResult(redirectTargetDictionary);
}
}
}
What you expert Guys think?
Update:
Thinking better, I can replace my Filter with a Custom Authorize Attribute and register that to act in all Controllers/Actions in Global.asax:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MyCustomAuthorizeAttribute());
}
Update 2:
Instead create a Blank Custom Attribute and put in the Controllers do not need authorization I pass in Parameters of my Custom Authorize the Controllers do not need authorization (in Global.asax):
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new ValidatePermissionAttribute("Login", "Erro"));
}
My Authorize Attribute:
public class ValidatePermissionAttribute : AuthorizeAttribute
{
...
public ValidatePermissionAttribute(params string[] optionalControllers)
{
_optionalControllers = optionalControllers;
}
...
}
Update 3:
Conditional Filters is the way to go.
Have you considered using Conditional Filters in ASP.NET MVC 3?
Several ways to handle bulk implementations of attributes:
Create a custom controller base class and implement there.
I think you can use the MVC filter's global filters collection: http://weblogs.asp.net/gunnarpeipman/archive/2010/08/15/asp-net-mvc-3-global-action-filters.aspx
I've been told before the issue with using a filter attribute is that the result can be cached via output caching, and then this wouldn't run. It's better to implement an IAuthorizationFilter interface (or AuthorizeAttribute class) and create an authorization filter instead.
If the goal is just to reduce the need to re-declare the attribute in many places, it seems one could achieve the same by creating one abstract AuthorizeController with the attribute, and any controller whose actions all require authorization can inherit that.

Disallow all pages except specific pages with ActionFilter

I have an ActionFilter that is applied to my base controller which checks certain things in the request and session and makes sure that you are allowed to view the requested URI.
This has worked fine until now when we need to disallow all pages (as it exists today) but allow a few URIs here and there.
Ideally, I would apply an attribute to the ones that we want to allow and then check for that attribute in the filter and allow if it exists, but I cannot figure out how to make that happen in the filter.
Any ideas or suggestions?
I spent over an hour trying to figure this out then I posted here on SO. Minutes later, it hit me.
First, I created an attribute
public class AlwaysAllowAnonymousAttribute : Attribute {}
Second I marked my action or controller with the above attribute
Finally, in my ActionFilter i did this
public class VerifyResourceAccess : ActionFilterAttribute, IActionFilter
{
void IActionFilter.OnActionExecuting(ActionExecutingContext filterContext)
{
var actionAttributes = filterContext.ActionDescriptor.GetCustomAttributes(typeof(AlwaysAllowAnonymousAttribute), false);
var controllerAttributes = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(AlwaysAllowAnonymousAttribute), false);
bool alwaysAllow = (actionAttributes.Length > 0) || (controllerAttributes.Length > 0);
if (!alwaysAllow) {
/* ... Some logic for checking if this user is allowed to access this resource ... */
}
base.OnActionExecuting(filterContext);
}
}
Any action or controller that is marked with the attribute will always have access allowed to it.

Categories