Granular permissions with certain requirements for an MVC site

后端 未结 3 1337
温柔的废话
温柔的废话 2020-12-29 15:05

I don\'t like the built in membership providers. I\'ve decided to roll my own. I\'m trying to come up with a good method for performing authorization at the action level. He

3条回答
  •  执念已碎
    2020-12-29 15:27

    First of all, I have to thank you for sucking me into answering this ;)

    This is a long answer, and is only a starting point. You have to figure out how to assign roles to users and how to recreate them in the AuthenticateRequest.

    If this does not answer your question, I hope it will be an inspiration. Enjoy!

    Decorate the controller actions

    I started to decorate the two actions in the default HomeController:

        [AuthorizeRoles(Role.Read)]
        public ActionResult Index()
        {
            ViewData["Message"] = "Welcome to ASP.NET MVC!";
    
            return View();
        }
    
        [AuthorizeRoles(Role.Write)]
        public ActionResult About()
        {
            return View();
        }
    

    All users in the ReadWrite role should then be granted access. I opted here to use an enum as a type safe placeholder for the magic strings. The role of this enum is nothing else than being a placeholder. There are no composite enum values, that has to be maintained somewhere else. More on that later.

    public enum Role
    {
        Read,
        Write,
        ReadWrite
    }
    

    Implement a new authorization attribute

    Since the strings are gone, I need a new authorize attribute:

    public class AuthorizeRolesAttribute : AuthorizeAttribute
    {
        private readonly RoleSet authorizedRoles;
    
        public AuthorizeRolesAttribute(params Role[] roles)
        {
            authorizedRoles = new RoleSet(roles);
        }
    
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            return authorizedRoles.Includes(httpContext.User);
        }
    }
    

    The RoleSet wraps a set of enum values and verifies if an IPrincipal is a member of one of them:

    public class RoleSet
    {
        public RoleSet(IEnumerable roles)
        {
            Names = roles.Select(role => role.ToString());
        }
    
        public bool Includes(IPrincipal user)
        {
            return Names.Any(user.IsInRole);
        }
    
        public bool Includes(string role)
        {
            return Names.Contains(role);
        }
    
        public IEnumerable Names { get; private set; }
    }
    

    Maintain roles

    The CompositeRoleSet is where composite roles are registered and handled. CreateDefault() is where all composites are registered. Resolve() will take a list of roles (enum values) and convert the composites to their single counterparts.

    public class CompositeRoleSet
    {
        public static CompositeRoleSet CreateDefault()
        {
            var set = new CompositeRoleSet();
            set.Register(Role.ReadWrite, Role.Read, Role.Write);
            return set;
        }
    
        private readonly Dictionary compositeRoles = new Dictionary();
    
        private void Register(Role composite, params Role[] contains)
        {
            compositeRoles.Add(composite, contains);
        }
    
        public RoleSet Resolve(params Role[] roles)
        {
            return new RoleSet(roles.SelectMany(Resolve));
        }
    
        private IEnumerable Resolve(Role role)
        {
            Role[] roles;
            if (compositeRoles.TryGetValue(role, out roles) == false)
            {
                roles = new[] {role};
            }
    
            return roles;
        }
    }
    

    Wiring it up

    We need an authenticated user to work on. I cheated and hard-coded one in global.asax:

        public MvcApplication()
        {
            AuthenticateRequest += OnAuthenticateRequest;
        }
    
        private void OnAuthenticateRequest(object sender, EventArgs eventArgs)
        {
            var allRoles = CompositeRoleSet.CreateDefault();
            var roles = allRoles.Resolve(Role.ReadWrite);
            Context.User = new ApplicationUser(roles);
        }
    

    Finally, we need an IPrincipal which understand all this:

    public class ApplicationUser : IPrincipal
    {
        private readonly RoleSet roles;
    
        public ApplicationUser(RoleSet roles)
        {
            this.roles = roles;
        }
    
        public bool IsInRole(string role)
        {
            return roles.Includes(role);
        }
    
        public IIdentity Identity
        {
            get { return new GenericIdentity("User"); }
        }
    }
    

提交回复
热议问题