I have used [Authorize] attribute a lot in the past, and it allows you to also do things like this:
[Authorize(Users = "test")]
However, I would like to add another one,
[Authorize(IsPermitted= PermissionsEnum.ThePermission)]
I have the logic written out that would decide if the user was permitted for that permission, but I'm not sure how to add that overload to the authorize attribute.
I would prefer not to make a entirely separate authorize attribute if possible.
Well, as #Dave A has said on the comments, you can extend the native Authorize attribute and implement your own authorization method, for sample:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
// create your custom property
public PermissionsEnum IsPermitted { get; set; }
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
bool authorized = // create your own validation and return a bool value
if (authorized)
{
return false;
}
// if you want to have the nativa validation, call it from the base method
return base.AuthorizeCore(httpContext);
}
}
and remember to decorate your controllers/actions with your Custom Authorize Attribute, for sample:
[MyAuthorize(IsPermitted = PermissionsEnum.Sales)]
public class OrderController : Controller
{
// actions...
}
Unfortunately you can not. The only possible properties that you can set are
Users and Roles. So you have to make a separate Attribute class.
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 have two attributes :
public class AnonymousAllowedAttribute : AuthorizeAttribute { }
public class ActionAuthorizeAttribute : AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext) {
bool skipAuthorization =
filterContext.ActionDescriptor.IsDefined(typeof(AnonymousAllowedAttribute), true)
||
filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AnonymousAllowedAttribute), true);
if(!skipAuthorization)
base.OnAuthorization(filterContext);
}
bool CustomeCheck() {
bool result = //My Checks
return result;
}
}
I define ActionAuthorizeAttribute as a global attribute.
So I need this 3 items:
1- If did not log in(!User.Identity.IsAuthenticated): Go to LogIn Page Accounts/LogIn.
I must mention the LogIn action marked with AnonymousAllowedAttribute.
2- If log in (User.Identity.IsAuthenticated) and action or controller have AnonymousAllowedAttribute then authorize is true (don't need any authorization).
3- If log in (User.Identity.IsAuthenticated) and action haven't AnonymousAllowedAttribute return CustomeCheck() method
I try second one by override OnAuthorization() method as you see.
and third one by followings:
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext){
if(!httpContext.User.Identity.IsAuthenticated)
return false;
return CustomeCheck();
}
but when I did not log in always return:
IIS 7.5 Error Details:
HTTP Error 401.0 - Unauthorized
with this URL: http://myProject/Accounts/LogIn?ReturnUrl=%2f
where is the problem? how can implement ActionAuthorizeAttribute to achieve this 3 goals?
Update
I find answer : the problem is the : AnonymousAllowedAttribute need to inherit from Attribute rather than AuthorizeAttribute.
the problem is: The AnonymousAllowedAttribute need to inherit from Attribute rather than AuthorizeAttribute.
when AnonymousAllowedAttribute inherit from AuthorizeAttribute so need to authorize but I create that to reduce Authorization!!
I am building a simple CMS in which roles are set dynamically in the admin panel. The existing way of authorizing a controller method, adding [Authorize(Roles="admin")] for example, is therefore no longer sufficient. The role-action relationship must be stored in the database, so that end users can easily give/take permissions to/from others in the admin panel. How can I implement this?
If you want to take control of the authorization process, you should subclass AuthorizeAttribute and override the AuthorizeCore method. Then simply decorate your controllers with your CmsAuthorizeAttribute instead of the default.
public class CmsAuthorizeAttribute : AuthorizeAttribute
{
public override virtual bool AuthorizeCore(HttpContextBase httpContext)
{
IPrincipal user = httpContext.User;
IIdentity identity = user.Identity;
if (!identity.IsAuthenticated) {
return false;
}
bool isAuthorized = true;
// TODO: perform custom authorization against the CMS
return isAuthorized;
}
}
The downside to this is that you won't have access to ctor-injected IoC, so you'll have to request any dependencies from the container directly.
That is exactly what the ASP.NET membership / profile stuff does for you. And it works with the Authorize attribute.
If you want to roll your own you could create a custom action filter that mimics the behavior of the standard Authorize action filter does. Pseudo code below.
public MyAuthorizeAttribute : ActionFilterAttribute
{
public string MyRole { get; set; }
public void OnActionExecuting(ControllerContext context)
{
if (!(bool)Session["userIsAuthenticated"])
{
throw new AuthenticationException("Must log in.");
}
if (!Session["userRoles"].Contains(MyRole))
{
throw new AuthenticationException("Must have role " + MyRole);
}
}
}
The role - action relationship must be
stored in the database
You will have to check your security within the controller method, unless you want to subclass AuthorizeAttribute so that it looks up the roles from the database for you.
Im using asp.net mvc built in authorize filter.
My only problem with it is that I dont want it to redirect my user to a login page when they dont have permission to perform a certain action... It always takes them to the login page even though ther are already logged on (but not with admin role).. I would like to be able to decide where to take them after they tried to perform an action ther arent allowed to..anyone?
Subclass AuthorizeAttribute and override the HandleAuthorizationFailed() method. The default logic of this method is that it sets the context's result to an HttpUnauthorizedResult, but you could do anything you want from this method. Then attribute the target method with this new attribute.
As Levi said you need to create your own custom AttributeFilter by overriding AthorizeAttribute. Something like
public class CustomAuthorizeAttribute : AuthorizeAttribute {
public string Url { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext) {
if (!filterContext.HttpContext.User.Identity.IsAuthenticated) { //or custom authorization logic
filterContext.HttpContext.Response.Redirect(Url);
}
base.OnAuthorization(filterContext);
}
}
[CustomAuthorizeAttribute(Url="/Admin/AccessDenied")]
public ActionResult Admin() {
return View();
}
Taken from this similar question
I current have the following attribute decorating one of the action method.
[Authorize(Roles = "Admin")]
public ActionResult DoAdminTask()
{
//Do something
return View();
}
Currently, only users in the Admin role can invoke this method, but this will change. Is there anyway I can store a list of authorised roles in a config file, rather than hard coding it into the source?
EDIT: Roles will change over time, and more than 1 role will need access.
i.e. Users in either role A OR role B can access.
No way to do this with the standard authorize attribute, but you could extend the authorize attribute with your own custom authorize attribute and have it use a configuration file to determine the mapping between controller/action and the set of roles.
but you can use something like
public static class AppRoles
{
public const string Users = "UsersRoleName";
public const string Admin = "AdminRoleName";
}
and then Controller can have authorize attribute as
[Authorize(Roles = AppRoles.Admin)]
I felt this question deserved an answer with a code sample... Taking #tvanfosson's suggestion of extending the AuthorizeAttribute class, here's what I came up with (criticism is more than welcome).
AuthorizeFromConfiguration.cs:
public class AuthorizeFromConfiguration: AuthorizeAttribute
{
public new string Roles
{
get {
return base.Roles;
}
set {
var config = new ConfigurationBuilder()
.SetBasePath(Environment.CurrentDirectory)
.AddJsonFile("authorization.json")
.Build();
base.Roles = config[value];
}
}
}
authorization.json:
{
"Parts": {
"Create": "contoso.com\\MyWebApp_CreateNewPart",
"Edit": "contoso.com\\MyWebApp_EditPart"
}
}
Example Usage:
[AuthorizeFromConfiguration(Roles = "Parts:Create")]
public class CreateModel : PageModel
{
//...
}
Note: In my testing, the web-site had to be restarted before any changes to authorization.json file took effect, even when I tried changing the logic so that the JSON file was read on the get accessor instead of the set.