3

I have created a custom role provider for my MVC4 application where I have been successfully able to override CreateRole, GetAllRoles and RoleExists methods and link them to my existing database as follows:

namespace Project.Providers
{
  public class MyProvider : System.Web.Security.SqlRoleProvider
  {
    private MyContext dbcontext = new MyContext(System.Configuration.ConfigurationManager.ConnectionStrings["MyContext"].ConnectionString);
    private Repository<MyUser> userRepository;
    private Repository<Role> roleRepository;

    public MyProvider()
    {
        this.userRepository = new Repository<MyUser>(dbcontext);
        this.roleRepository = new Repository<Role>(dbcontext);
    }

    public override string[] GetAllRoles()
    {
        IEnumerable<Role> dbRoles = roleRepository.GetAll();
        int dbRolesCount = roleRepository.GetAll().Count();
        string[] roles = new string[dbRolesCount];
        int i = 0;
        foreach(var role in dbRoles)
        {
            roles[i] = role.Name;
            i++;
        }
        return roles;
    }

    public override bool RoleExists(string roleName)
    {
        string[] roles = { "Admin", "User", "Business" };
        if(roles.Contains(roleName))
            return true;
        else
            return false;
    }

    public override void CreateRole(string roleName)
    {
        Role newRole = new Role();
        newRole.Name = roleName;
        roleRepository.Add(newRole);
        roleRepository.SaveChanges();
    }

    public override bool IsUserInRole(string userName, string roleName)
    {
        MyUser user = userRepository.Get(u => u.Username == userName).FirstOrDefault();
        Role role = roleRepository.Get(r => r.Name == roleName).FirstOrDefault();
        if (user.RoleID == role.RoleID)
            return true;
        else
            return false;
    }
  }
}

I have been unable to find a way to override the

User.IsInRole(string roleName)

What else must I do so that When I use:

[Authorize(Roles = "Admin")]

It will be based on the role provider that I have set up and not the asp default.

My user class is now as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.Collections;
using System.Security.Principal;

namespace Project.Data
{
  public class MyUser : IPrincipal
  {
    [Key]
    public int UserID { get; set; }

    [StringLength(128)]
    public string Username { get; set; }              

    .....other properties

    public IIdentity Identity { get; set; }

    public bool IsInRole(string role)
    {
        if (this.Role.Name == role)
        {
            return true;
        }
        return false;
    }

    public IIdentity Identity
    {
        get { throw new NotImplementedException(); }
    }
  }
}

My stack trace seems to be following over at:

System.Web.Security.RolePrincipal.IsInRole(String role) 

So I have tried to implement a custom RolePrincipal in the same manner I set the custom Provider any ideas how I can do this? Not sure what constructor params it takes. Here is my attempt:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Configuration.Provider;
using Project.Data;
using System.Web.Security;
using System.Security.Principal.IIdentity;

namespace Project.Principal
{
  public class MyPrincipal : System.Web.Security.RolePrincipal
  {
    private MyContext dbcontext = new MyContext(System.Configuration.ConfigurationManager.ConnectionStrings["MyContext"].ConnectionString);
    private Repository<MyUser> userRepository;
    private Repository<Role> roleRepository;        

    public MyPrincipal()
    {
        this.userRepository = new Repository<MyUser>(dbcontext);
        this.roleRepository = new Repository<Role>(dbcontext);
    }

    public override bool IsInRole(string role)
    {
        //code to be added
        return true;
    }
}

}

Jay
  • 3,012
  • 14
  • 48
  • 99

3 Answers3

7

You just need to override method GetRolesForUser in your custom role provider, instead of the more logical IsUserInRole, because that is what is called by the default implementation that does some unwanted caching.

Monoman
  • 721
  • 10
  • 12
2

You override IsInRole in your IPrincipal class, mine in EF looks like this:

public class MyUser : IPrincipal {
    //Properties
    ...
    public bool IsInRole(string role) {
        if (Roles.Any(m=>m.NameKey.ToLower().Equals(role.ToLower()))) {
            return true;
        }
        return false;
    }
}

Then once you add the appropriate sections to your webconfig for both RoleProvider and MembershipProvider you should be good for Authorize attribute.

UPDATE in response to your comments

web Config should look like:

...
<authentication mode="Forms">
  <forms loginUrl="~/Login" timeout="2880"></forms>
</authentication>
<authorization>
</authorization>

..

<membership defaultProvider="MyMembershipProvider">
  <providers>
    <add name="MyMembershipProvider" type="MyApp.Infrastructure.MyMembershipProvider" connectionStringName="connectionstring" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="true" />
  </providers>
</membership>
<roleManager defaultProvider="MyRoleProvider" enabled="true" cacheRolesInCookie="true">
  <providers>
    <clear />
    <add name="MyRoleProvider" type="MyApp.Infrastructure.MyRoleProvider" />
  </providers>
</roleManager>
...

In the provider, is the User your IPrincipal?

public MyUser User { get; private set; }

User should have and IIdentity

in MyUser.cs:

    ...
    public virtual ICollection<Role> Roles { get; set; }
    public IIdentity Identity { get; set; }

I don't have much additional advice to help troubleshoot from your comments.

UPDATE

Some examples I have been through and found helpful when setting mine up: http://www.brianlegg.com/post/2011/05/09/Implementing-your-own-RoleProvider-and-MembershipProvider-in-MVC-3.aspx

http://www.mattwrock.com/post/2009/10/14/Implementing-custom-MembershipProvider-and-Role-Provider-for-Authinticating-ASPNET-MVC-Applications.aspx

http://blogs.msdn.com/b/rickandy/archive/2012/03/23/securing-your-asp-net-mvc-4-app-and-the-new-allowanonymous-attribute.aspx?Redirected=true

I read many other articles and SO posts on my first run through, but these were things I bothered to bookmark. I took a roles/rights approach to authorization, which is why one of them is geared that way.

Matthew
  • 9,851
  • 4
  • 46
  • 77
  • I tried the above suggestion and I am receiving the following error: 'Project.Data.User' does not implement interface member 'System.Security.Principal.IPrincipal.Identity' – Jay Aug 23 '13 at 23:27
  • I implemented the interface by clicking on the end of IPrincipal and clicking ALT + SHIFT + F10. Now I am getting a Non static method requires a target error – Jay Aug 23 '13 at 23:37
  • My stack trace seems to be following over at: System.Web.Security.RolePrincipal.IsInRole(String role) I have tried to implement a custom RolePrincipal in the same manner I set the custom Provider any ideas how I can do this? Not sure what constructor params it takes – Jay Aug 23 '13 at 23:52
  • Maybe I just need to set Membership provider with this info – Jay Aug 23 '13 at 23:59
  • Any idea how I could use the membership provider to check the MyUser : IPrincipal class? – Jay Aug 24 '13 at 00:01
  • made some changes, but without my workstation in front of me. – Matthew Aug 24 '13 at 05:12
  • Hi Matthew thanks, I am unsure as to how to set the User as IPrincipal in the provider my provider is as shown above and I have now edited my user class to take on board your comments. What is the app.MyMembershipProvider that you are calling in the web.config? – Jay Aug 24 '13 at 12:11
  • In the web config you need to tell the app to use your Role and Membership providers. I just shortened the namespace on my application to app.MyProvider. was a bad choice for clarity. – Matthew Aug 24 '13 at 16:03
  • I have not created a Membership provider though, this is what I am trying to do with the override of the RolePrincipal. Is my understanding of this incorrect? – Jay Aug 24 '13 at 16:10
  • I am not sure you can do a custom user without implementing a custom provider. I have at least never done it, and a quick google didn't shed any light for me. In short I haven't seen it done that way before, but that doesn't mean you can't. – Matthew Aug 24 '13 at 16:23
  • I have implemented the custom provider. I am just not sure how to link this to the MyUser : IPrincipal user found in my database – Jay Aug 24 '13 at 16:31
  • I also dont have a membership provider defined and so this always checks the default IsInRole method – Jay Aug 24 '13 at 16:33
  • If you have a custom provider, where you set the user (public MyUser User {get; set;} is what will decide which one to use. – Matthew Aug 24 '13 at 17:08
  • I do not set MyUser { get; set;} from within the provider. Can you look at my provider which I have edited in the original post – Jay Aug 25 '13 at 00:39
  • 1
    Your custom membership provider is where you do that - not the custom role provider – Matthew Aug 25 '13 at 01:41
  • Would you have an example of a custom membership provider that you could point me to Matthew? – Jay Aug 25 '13 at 14:31
  • Added a few links I found helpful. – Matthew Aug 25 '13 at 15:40
  • Hi Matthew, I have looked at the CustomMembership creation details and I have implemented the abstract class but I cannot see how this ties in with the User.IsInRole method in which I am looking to override, have I missed something? – Jay Aug 25 '13 at 15:56
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/36203/discussion-between-matthew-and-jay) – Matthew Aug 25 '13 at 16:13
  • Hi Matthew, I notice that my user is not being set correctly in the role provider, my username parameter in the GetRolesForUser(username) is returning null. Where do I declare the MyUser class within the provider? – Jay Aug 25 '13 at 21:24
0

To fix this, you need to do 4 updates to your application.

    1. Create a class that extends RoleProvider.

    namespace MyApp
    {


     public class MyRoleProvider : RoleProvider
        {
            public override string ApplicationName
            {
                get
                {
                    throw new NotImplementedException();
                }

                set
                {
                    throw new NotImplementedException();
                }
            }

            public override void AddUsersToRoles(string[] usernames, string[] roleNames)
            {
                throw new NotImplementedException();
            }

            public override void CreateRole(string roleName)
            {
                throw new NotImplementedException();
            }

            public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
            {
                throw new NotImplementedException();
            }

            public override string[] FindUsersInRole(string roleName, string usernameToMatch)
            {
                throw new NotImplementedException();
            }

            public override string[] GetAllRoles()
            {
                throw new NotImplementedException();
            }

            public override string[] GetRolesForUser(string username)
            {
                using (ApplicationDbContext db = new ApplicationDbContext())
                {
                        // get user roles here using user name.

                }
            }



            public override string[] GetUsersInRole(string roleName)
            {
                throw new NotImplementedException();
            }

            public override bool IsUserInRole(string username, string roleName)
            {

                return GetRolesForUser(username).Contains(roleName);

            }

            public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
            {
                throw new NotImplementedException();
            }

            public override bool RoleExists(string roleName)
            {
                throw new NotImplementedException();
            }
        }

}

    2. Create a custom filter that extends AuthorizeAttribute and overwrite its methods.

      public class MyAuthFilter : AuthorizeAttribute
    {


        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            base.OnAuthorization(filterContext);
       }


        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            var routeValues = new RouteValueDictionary(new
            {
                controller = "Account",
                action = "Login",


            });

             filterContext.Result = new RedirectToRouteResult(routeValues);

            base.HandleUnauthorizedRequest(filterContext);
        }

        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            string[] roles = Roles.Split(',');

            string userName = HttpContext.Current.User.Identity.Name;

            MyRoleProvider myProvider = new MyRoleProvider();


            foreach (string role in roles)
            {
                bool success = myProvider.IsUserInRole(userName, role);

                if (success == true)
                {
                    return true;
                }

            }

            return false;
        }

    3. Configure your custom role provider in your web.config.
      <system.web>
        <roleManager defaultProvider="MyRoleProvider" enabled="true" cacheRolesInCookie="true">
          <providers>
            <clear />
            <add name="MyRoleProvider" type="MyApp.MyRoleProvider" />
          </providers>
        </roleManager>
      </system.web>

      Note: The type here uses the fully qualified namespace and your class name = MyApp.MyRoleProvider. Yours can be different

    4. Use your custom filter instead of the default Authorize attribute for your controllers and actions. E.g 

    [MyAuthFilter]
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Title = "Home Page";

            return View();
        }
    }
Ziregbe Otee
  • 534
  • 3
  • 9