views:

335

answers:

1

Hi

I come to the conclusion I need to ditch the ASP.NET Membership (for list of reasons).

Now really the only thing I see that I need is creating a cookie(done by Form Authentication), custom methods for authentication (done) and finally validation based on if they are logged in or by role.

I am stuck on the last one.

I am trying to override the Authorize (attribute) but I have no clue how to do this. I looked at many examples and each one seems to be done differently then the next. I don't know why they do this or which one I should be using.

Some tutorials seem to do the authentication in the AuthorizeCore, Some do it in the OnAuthentication.

Some use some AuthorizationContext thing and then call this base class.

base.OnAuthorization(filterContext);

Some seem to do caching in it.

What I want is all the functionality the built in ones have but just hooked up to my custom tables. Like I going to have my own Role table. I need to tell it where that is and pull the stuff in.

Also I have no clue how to do this or how decorate the tag like this

[Authorize(Roles="test")]

References:- http://darioquintana.com.ar/blogging/tag/aspnet-mvc/ http://stackoverflow.com/questions/554094/asp-net-mvc-adding-to-the-authorize-attribute http://davidhayden.com/blog/dave/archive/2009/04/09/CustomAuthorizationASPNETMVCFrameworkAuthorizeAttribute.aspx

Edit

This is what I have now.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public sealed class AuthorizeAttributeCustom : AuthorizeAttribute
    {

        public string Roles { get; set; }


        private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
        {
            validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
        }

        public override void OnAuthorization(AuthorizationContext filterContext)
        {

            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // auth failed, redirect to login page
                filterContext.Result = new HttpUnauthorizedResult();
                return;
            }

            DataClasses1DataContext test = new DataClasses1DataContext();
            var name = filterContext.HttpContext.User.Identity.Name;
            var user = test.User2s.Where(u => u.userName == name).FirstOrDefault();
            var role = test.Roles.Where(u => u.UserId == user.userId).Select(u => u.Role1).FirstOrDefault();

            string[] split = Roles.Split(',');

            if (split.Contains(role) == true)
            {
                // is authenticated and is in the required role
                SetCachePolicy(filterContext);
                return;
            }
            filterContext.Result = new HttpUnauthorizedResult();
        }

        private 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 */);
        }
    }

Out Standing Questions

  1. Why is it sealed? If it is sealed does it not make it harder to unit test?
  2. What is filterContext?
  3. Why is no AuthorizeCore used? Only OnAuthentication?
  4. Whats the cache refering to? Like is it caching the role? Or the Page? I can't tell with the debugger it seems to run the code every single time.

  5. Is caching it safe?

  6. In general is this safe(ie no holes in it to be explioted- kinda worried I will screw something up and have some major hole in my site).

+1  A: 

Here's a custom attribute that would work just as you want it; using an Enum for role types and using cookie creation yourself, which allows for storing of roles.

usage

  [AuthorizeAttributeCustom(RoleRequired = GoodRoles.YourRoleTypeHere)]

attribute code:

//http://stackoverflow.com/questions/977071/redirecting-unauthorized-controller-in-asp-net-mvc/977112#977112
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public sealed class AuthorizeAttributeCustom : AuthorizeAttribute
    {

        /// <summary>
        /// The name of the view to render on authorization failure.  Default is "Error".
        /// </summary>
        public string ViewName { get; set; }
        public ViewDataDictionary ViewDataDictionary { get; set; }
        public DeniedAccessView DeniedAccessView { get; set; }

        private GoodRoles roleRequired = GoodRoles.None;
        public GoodRoles RoleRequired { get{ return roleRequired;} set{ roleRequired = value;} } // this may evolve into sets and intersections with an array but KISS

        public AuthorizeAttributeCustom()
        {
            ViewName = "DeniedAccess";
            DeniedAccessView = new DeniedAccessView
                                   {
                                       FriendlyName = "n/a",
                                       Message = "You do not have sufficient privileges for this operation."
                                   };
            ViewDataDictionary = new ViewDataDictionary(DeniedAccessView);
        }

        private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
        {
            validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
        }


        public override void OnAuthorization(AuthorizationContext filterContext)
        {

            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                // auth failed, redirect to login page
                filterContext.Result = new HttpUnauthorizedResult();
                return;
            }

            if (RoleRequired == GoodRoles.None || filterContext.HttpContext.User.IsInRole(RoleRequired.ToString()))
            {
                // is authenticated and is in the required role
                SetCachePolicy(filterContext);
                return;
            }

            filterContext.Result = new ViewResult { ViewName = ViewName, ViewData = ViewDataDictionary };
        }

        private 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 */);
        }


    }

you'll need to have explicitly added your roles to auth cookie and read them back in a base controller say. my implementation has other details which you might not want so maybe best to read here: http://ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html

dove
Could you explain this a bit? Like what is the chache for? this for the page or just the roles? What is filterContext for?
chobo2
I am also is passing this through by enum the only way? I can't get it to look like the built in ones? Since I am not sure how that will effect unit testing? right now I look if the tag is there and check what they Role string contains. If it contains the roles I expect then it passes. Also I notice it does not use the AuthorizeCore method either. Why not?
chobo2
also the way it is right now this line "filterContext.HttpContext.User.IsInRole(RoleRequired.ToString())" where is that pulling the role from? VS2008 Intellisense does not have even something for IsInRole. I would have figured I would have to do this with a database request.
chobo2
Oh another thing how about multiple roles? I have on some method 2 or more roles. I am not sure how to pass in multiple roles with this one.
chobo2