views:

192

answers:

3

Updated: Thanks to the help here I've created the following solution:

public class CustomAuthorize : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        // Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs
        // If user is not logged in prompt
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            base.HandleUnauthorizedRequest(filterContext);
        }
        // Otherwise deny access
        else
        {
            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary {
                {"controller", "Account"},
                {"action", "NotAuthorized"}
            });
        }
    }
}

I started from NerdDinner and am using FormsAuthentication with ActiveDirectory as my membership provider. I've added support for roles via my db with Global.asax & AccountController (below).

So now in my controller I have my Authorize attribute set to roles of admin only (below). My logged in user is an author. When I click delete it asks me to login even though I have already done so. Where can I put the logic to return an access denied view?

Global.asax.cs

    protected void Application_AuthenticateRequest(Object sender, EventArgs e)
    {
        HttpCookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
        if (authCookie == null || authCookie.Value == "")
        {
            return;
        }

        FormsAuthenticationTicket authTicket = null;

        try
        {
            authTicket = FormsAuthentication.Decrypt(authCookie.Value);
        }
        catch
        {
            return;
        }

        if (Context.User != null)
        {
            string[] roles = authTicket.UserData.Split(new char[] { ';' });
            Context.User = new GenericPrincipal(Context.User.Identity, roles);
        }
    }

AccountController.cs

    [HttpPost]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings",
        Justification = "Needs to take same parameter type as Controller.Redirect()")]
    public ActionResult LogOn(string userName, string password, bool rememberMe, string returnUrl)
    {

        if (!ValidateLogOn(userName, password))
        {
            ViewData["rememberMe"] = rememberMe;
            return View();
        }

        // Make sure we have the username with the right capitalization
        // since we do case sensitive checks for OpenID Claimed Identifiers later.
        userName = this.MembershipService.GetCanonicalUsername(userName);

        // Lookup user's (CWID) appropriate access level
        string accessLevel = userRepository.FindUserByUserName(userName).AccessLevel.LevelName;

        FormsAuthenticationTicket authTicket = new
                        FormsAuthenticationTicket(1, //version
                        userName, // user name
                        DateTime.Now,             //creation
                        DateTime.Now.AddMinutes(30), //Expiration
                        rememberMe, //Persistent
                        accessLevel); // hacked to use roles instead

        string encTicket = FormsAuthentication.Encrypt(authTicket);
        this.Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));

        if (!String.IsNullOrEmpty(returnUrl))
        {
            return Redirect(returnUrl);
        }
        else
        {
            return RedirectToAction("Index", "Home");
        }
    }

SpotlightController.cs

    [Authorize(Roles="Admin")]
    public ActionResult Delete(int id)
+2  A: 

I suppose you could derive from AuthorizeAttribute and then override HandleUnauthorizedRequest and put your own redirect logic there.

Thanks,

Hal

Hal
I guess I'm missing why HandleUnauthorizedRequest doesn't do anything now? Or am I just not capturing that event?
ryan
Your logged in user in an Author right, not an Admin?The desired behavior is that a user with insufficient privilege would be redirected to an exception page, not a page that attempts to login with elevated credentials (which I believe is the default behavior).Am I understanding correctly?
Hal
That is correct!
ryan
I think HandleUnauthorizedRequest is doing what is supposed to do - present you with an chance to elevate your privilege. You will have to override that behavior in the custom attribute (or some other extension point). Hope that helps. Let me know if you need help actually writing the attribute.
Hal
I've updated my OP. Any comments appreciated
ryan
+1  A: 

Alternatively you can override the Authorize attribute. You would override the OnAuthorization method and get the result from the AuthorizeCore method from your base. Based on that result you can throw an exception, or a custom exception (i.e. Maybe a custom security exception that logs the state) right from within OnAuthirziation.

kmehta
+3  A: 

Here is what AuthorizeAttribute does, out-of-the-box: It checks to see if the current user is authorized for the current request, and returns HHTP 401/UNAUTHORIZED if they are not, either because they are not logged in at all, or they are not on the list of authorized users/roles for the current request.

The Web forms authentication HTTP module sees this 401 response, intercepts that, and turns it into an HTTP 302 (redirect) response to the login page, if the loginUrl attribute is configured in web.config. The general thought is that if a user is denied access to the site because they are not logged in, but the next thing they will want to do is log in.

Since what you want to do is redirect elsewhere, Hal's suggestion of overriding HandleUnauthorizedRequest and redirecting is not unreasonable. Just bear in mind that if you still want non-authenticated users to see the login page (as opposed to authenticated, but not in the list of permitted users/roles), then you will have to add logic for that. I would advise against the idea of overriding AuthorizeCore or OnAuthorization; neither of these actually solve the problem, and they are much easier to screw up than HandleUnauthorizedRequest.

Craig Stuntz
Craig is absolutely right - you would have to handle the a roles exception vs. an unauthenticated user. I don't think that would be too difficult, but I haven't tried it so YMMV.I too would advise against messing with AuthorizeCode or OnAuthorization unless you truly want to implement your own authentication scheme.
Hal
I need to read up more. In the meantime I could use some help with overriding HandleUnauthorizedRequest. I understand how to roll my own class that inherits from AuthorizeAttribute and then override HandleUnauthorizedRequest. I don't need my own attribute since this is the only thing im changing. So how to override HandleUnauthorizedRequest without deriving?
ryan
You must derive a new type and override that method. But that method is the only thing you need to change.
Craig Stuntz
Thanks Craig :)
ryan
I've updated my OP. Any comments appreciated
ryan
Seems good to me.
Craig Stuntz