views:

187

answers:

3

Hello,

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?

+6  A: 

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.

manu08
Any comments; how can I pass multiple parameters to custom attribute? Something like [DoNotAuthorize( role = 'A', role = 'B' )] ?
effkay
@effkay Yes, you just include those in the constructor for your custom attribute. I'll add an example above.
manu08
Nice example. using the IEnumerable, similar to my earlier suggestion of IList. That way you can have as many rejected roles as you wish.
Chris F
Thanks. I generally do use IEnumerable across class boundaries to maintain encapsulation.
manu08
+3  A: 

Besides creating a custom AuthorizeAttribute, suggested by manu, you could use PrincipalPermission, with a Deny-SecurityAction:

[PrincipalPermission(SecurityAction.Deny, Role="Administrator")]
Pbirkoff
thanks... lemme try!
effkay
That's a good point. The main reason I would do it with a custom attribute, is to make it easier to maintain later on. For example, if you decide to later reject another role, you would just have to modify your custom attribute, as opposed to modifying every individual method that uses your solution.
manu08
yes I guess... also, [PrincipalPermission(SecurityAction.Deny, Role="Administrator")] didn't work .....
effkay
I'd try harder to make this work. Using someone else's debugged, tested, supported code is usually a better choice and lets you get more sleep at night.
No Refunds No Returns
@effkay, did you substitute "Administrator" with Constants.ROLES_ADMINISTRATOR which I presume is a string that represents the name of your admin role?
Chris F
@Chris: yup. Constants.ROLES is a string constant containing role name - not the user name.
effkay
+1  A: 

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();
}
soniiic