Search code examples
asp.net-mvcauthorizationcustom-attributesauthorize-attribute

How to emulate the AllowAnonymousAttribute behavior?


I would like to implement a whitelist approach that will apply an [Authorize(Roles = "Admin")] attribute by default. Then I would like to specify [AllowAnonymous] or [AllowMember] on whitelisted actions.

So I need to create an attribute similar to AllowAnonymous, but only giving access the "Member" role. (Like AllowAnonymous, it should override any Authorize attributes that may be in effect on controllers as global filters.)

I initially tried to inherit from AllowAnonymousAttribute, but I find it is sealed. I've googled "Inherit allowanonymous" but the answers find me out of my depth.

Am I wise in my approach, and how can I create an attribute such as that?


Update

Following NightOwl888's advice and some code from this page, I have:

  1. Created two Attributes, one to allow members, the other the public

  2. Inherited the AuthorizeAttribute to create a new one that I will apply as a global filter

  3. Inserted a couple of methods into the AuthorizeCore() method that check for the attributes and return true

I hope I'm not doing anything daft in the code below... I'd appreciate a heads up (or down) if it looks ok (or not).

Thanks.


namespace FP.Codebase.Attributes
{
    public class AllowPublicAccessAttribute : Attribute
    {}



    public class AllowMemberAccessAttribute : Attribute
    {}



    public class MyAuthorizeAttribute : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            filterContext.HttpContext.Items["ActionDescriptor"] = filterContext.ActionDescriptor;
            base.OnAuthorization(filterContext);
        }



        private bool IsAllowPublicAccessAttributeAppliedToAction(ActionDescriptor actionDescriptor)
        {
            return (actionDescriptor.IsDefined(typeof(AllowPublicAccessAttribute), inherit: true)
                || actionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowPublicAccessAttribute), inherit: true));
        }



        private bool IsAllowMemberAccessAttributeAppliedToAction(ActionDescriptor actionDescriptor)
        {
            return (actionDescriptor.IsDefined(typeof(AllowMemberAccessAttribute), inherit: true)
                || actionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowMemberAccessAttribute), inherit: true));
        }



        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            var actionDescriptor = httpContext.Items["ActionDescriptor"] as ActionDescriptor;
            if (httpContext == null)
            {
                throw new ArgumentNullException("httpContext");
            }

            IPrincipal user = httpContext.User;

            if (IsAllowPublicAccessAttributeAppliedToAction(actionDescriptor))
            {
                return true;
            }
            if (IsAllowMemberAccessAttributeAppliedToAction(actionDescriptor) && user.IsInRole("Member"))
            {
                return true;
            }

            if (!user.Identity.IsAuthenticated)
            {
                return false;
            }

            var _usersSplit = SplitString(Users);
            var _rolesSplit = SplitString(Roles);

            if (_usersSplit.Length > 0 && !_usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase))
            {
                return false;
            }

            if (_rolesSplit.Length > 0 && !_rolesSplit.Any(user.IsInRole))
            {
                return false;
            }

            return true;
        }



        // copied from https://github.com/ASP-NET-MVC/aspnetwebstack/blob/master/src/System.Web.Mvc/AuthorizeAttribute.cs
        internal static string[] SplitString(string original)
        {
            if (String.IsNullOrEmpty(original))
            {
                return new string[0];
            }

            var split = from piece in original.Split(',')
                        let trimmed = piece.Trim()
                        where !String.IsNullOrEmpty(trimmed)
                        select trimmed;
            return split.ToArray();
        }
    }
}

Solution

  • All of the behavior of AllowAnonymous attribute is coded into the AuthorizeAttribute.OnAuthorize method. So, if you want to reuse this behavior, the best approach is to inherit AuthorizeAttribute and override the AuthorizeCore method instead.