Albert D. Kallal
Albert D. Kallal

Reputation: 49049

Custom role provider IsInRole fails

I have a simple role provider I created (so forms authentication).

The standard built in asp.net logon controls when placed on a page work fine.

I am trying to determine why the standard 'tests' for role membership seen to fail.

I have this so far:

Roles.Provider.IsUserInRole(Membership.GetUser.UserName, "Portal") returns true

Roles.IsUserInRole(Membership.GetUser.UserName, "Portal") returns false

Why does the second format fail? (note that the below IsInRole code is called by BOTH of the above).

User.IsInRole("Portal") returns false.

So, out of the 3 examples, only one works.

Why does User.IsInRole() not work?

Note that User.IsInRole() for some reason does NOT call the following routine, but the above TWO codes do.

Public Overrides Function IsUserInRole(username As String, roleName As String) As Boolean

    Dim rst As DataTable
    Dim strSql As String
    MsgBox("Is user in Role test")
    strSql = "SELECT Email, RoleName FROM  dbo_ContactName " & _
             "LEFT JOIN Web_UsersInRoles ON Web_usersInRoles.User_ID = dbo_ContactName.Id " & _
             "LEFT JOIN Web_Roles on Web_Roles.ID = Web_UsersInRoles.Role_ID " & _
             "WHERE Email = '" & username & "' and RoleName = '" & roleName & "'"

    rst = Myrst(strSql)

    If rst.Rows.Count > 0 Then
        Return True
    Else
        Return False
    End If

End Function

Note that MOST confusing is this:

Roles.IsUserInRole(Membership.GetUser.UserName, "Portal") returns false

I have verified that the above causes my custom code to run but ALWAYS returns false.

In fact if I HARD CODE the above routine to return "true", then the above #2 STILL calls my code and STILL returns false!

So big question: even with hard-coding the above routine to always return true why does the this format fail?

Roles.IsUserInRole(Membership.GetUser.UserName, "Portal")

And worse, why does this fail?

User.IsInRole("Portal") again always returns false.

So what settings should I look for to enable 2 and 3 to work?

In summary:

1 - Roles.Provider.IsUserInRole(Membership.GetUser.UserName, "Portal") returns true

2 - Roles.IsUserInRole(Membership.GetUser.UserName, "Portal") returns false

3 - User.IsInRole("Portal") returns false

4 - User.Identity.Name returns the correct logged in user [email protected]

I can also verify that from above #3 NEVER calls the IsUserInRole code. This includes right after a clear browser cache.

A quick web search shows large number of people find that User.IsInRole() stops working when adopting a custom role provider. There must some be "significant" issue here that being missed when writing custom role providers that causes this to fail.

How can I get the above 2 or 3 options working?

Using Visual Studio 2013,and creating a standard asp.net web site with vb.net (frame work 4.5)

Edit

As a follow up ALL OF MY ROLE functions works.

eg: Roles.GetAllRoles() - this works (returns all roles correct)

Roles.GetRolesForUser() - this works, returns  all roles for CURRENT user

Roles.GetUserInRole("Portal") - this works, returns all users in role group "portal"

NOT working:

User.IsInRole("Portal") - as noted 1000's of posts on internet have this issue!

Roles.IsUserInRole(Membership.GetUser.UserName, "Portal")

Here are the additional subs that I had not included in original post:

Public Overrides Function GetUsersInRole(roleName As String) As String()

    Dim rst As DataTable
    Dim i As Integer
    Dim a As New List(Of String)
    Dim strSql As String
    MsgBox("get users in role code")
    strSql = "SELECT Email, RoleName FROM  dbo_ContactName " & _
             "LEFT JOIN Web_UsersInRoles ON Web_usersInRoles.User_ID = dbo_ContactName.Id " & _
             "LEFT JOIN Web_Roles on Web_Roles.ID = Web_UsersInRoles.Role_ID " & _
             "WHERE RoleName = '" & roleName & "' " & _
             "ORDER BY Web_Roles.RoleName"
    rst = Myrst(strSql)

    For i = 0 To rst.Rows.Count - 1
        a.Add(rst.Rows(i).ItemArray(0).ToString)
    Next

    Return a.ToArray

End Function

Public Overrides Function GetRolesForUser(username As String) As String()

    Dim rst As DataTable
    Dim i As Integer
    Dim a As New List(Of String)
    Dim strSql As String

    strSql = "SELECT Email, RoleName FROM  dbo_ContactName " & _
             "LEFT JOIN Web_UsersInRoles ON Web_usersInRoles.User_ID = dbo_ContactName.Id " & _
             "LEFT JOIN Web_Roles on Web_Roles.ID = Web_UsersInRoles.Role_ID " & _
             "WHERE Email = '" & username & "' " & _
             "ORDER BY Web_Roles.RoleName"

    rst = Myrst(strSql)

    For i = 0 To rst.Rows.Count - 1
        a.Add(rst.Rows(i).ItemArray(1).ToString)
    Next

    Return a.ToArray

End Function

Public Overrides Function GetAllRoles() As String()

    Dim rst As DataTable
    Dim i As Integer
    Dim a As New List(Of String)

    rst = Myrst("select RoleName from Web_Roles order by RoleName")

    For i = 0 To rst.Rows.Count - 1
        a.Add(rst.Rows(i).ItemArray(0).ToString)
    Next

    Return a.ToArray

End Function

ALL OF THE ABOVE features work.

Question still stands:

How do we wire up to ENABLE use of User.IsInRole(). This does NOT work.

In summary:

Roles.GetRolesForUser    - returns "current" roles for user - works fine

Roles.GetUsersInRole("Portal")  - works fine

Roles.GetAllRoles() - works fine.

User.IsInRole() - BROKEN!

More summary: here is some code with the resulting output:

    Debug.WriteLine(Roles.Provider.IsUserInRole(Membership.GetUser.UserName, "Portal"))

    Debug.WriteLine(Roles.IsUserInRole(Membership.GetUser.UserName, "Portal"))

    Debug.WriteLine(User.Identity.Name)

    Debug.WriteLine(User.Identity.IsAuthenticated)

    Debug.WriteLine(User.IsInRole("Portal"))

Output:

Roles.Provider.IsUserInRole  = True
Roles.IsUserInRole = False
User.Identity.Name = [email protected]
User.Identity.IsAuthenticated = True
User.IsInRole("Portal")  = False   < -- still broken!!!

Upvotes: 0

Views: 2685

Answers (3)

Terri
Terri

Reputation: 352

Thanks Joe -> "User.IsInRole" won't work until after the user has been authenticated

Solved the display-what-when issue for me! In _Layout.cshtml

 @if (HttpContext.Current.User.Identity.IsAuthenticated)
   {
    if (HttpContext.Current.User.IsInRole("Manager"))
      { 
       @Html.Partial("_ManageNav")
      }
    if (HttpContext.Current.User.IsInRole("Administrator"))
      { 
       @Html.Partial("_ManageNav")
      }
   }

Only displays "_ManageNav" if the user is assigned a Manager or Administrator role

Did not work for combined string ("Manager, Administrator")

Did not work for combined string ("Manager", "Administrator")

Upvotes: 0

Albert D. Kallal
Albert D. Kallal

Reputation: 49049

The solution to this elusive problem is outlined here:

HttpContext.Current.User.IsInRole not working

The answer and follow up by the poster is contained in the comments section.

The suggested solution is to place a trim() on the Role names. When done the CODE STARTED working!

Role name had a trailing space, and as a long shot I placed a trim on the code that loads roles for a user and sure enough the code started working. Now that the original data providing the Role has been trimmed, then the in code in question now works.

I cannot take credit for this solution but the link to this issue and problem is contained in the above.

So this code (and other code tested) was failing due to a trailing blanks on some of the Role names.

MOST interesting is that

Roles.Provider.IsUserInRole(Membership.GetUser.UserName, "Portal")

Above works! with trailing spaces

Roles.IsUserInRole(Membership.GetUser.UserName, "Portal")

Above does NOT work with trailing spaces

User.IsInRole("Portal")

Above does NOT work with trailing spaces.

So the HUGE hint here is that Roles.Provider.IsUserInRole(Membership.GetUser.UserName, "Portal") does work! If the others fail, then REST assured then you have an issue with stray spaces in your role names.

With the stray space removed, then ALL of the above examples in ALL cases NOW all work.

Upvotes: 1

to StackOverflow
to StackOverflow

Reputation: 124706

You need to implement GetRolesForUser in your custom provider.

When you call Roles.IsUserInRole in an ASP.NET application for the current user, it will call System.Web.Security.RolePrincipal.IsUserInRole.

RolePrincipal.IsUserInRole will then call the Role Provider's GetRolesForUser method on first access, and cache the list of roles internally. It never actually calls the provider's IsUserInRole method.

Confusingly, it works differently when you use ASP.NET roles in a WCF application (principalPermissionMode="UseAspNetRoles"). In this case, instead of using the System.Web.Security.RolePrincipal class, it uses an internal class System.ServiceModel.Security.RoleProviderPrincipal.

RoleProviderPrincipal.IsUserInRole will call the Role Provider's IsUserInRole method, and doesn't attempt to cache the list of roles for the current user.

The two different implementations have different performance characteristics.

  • The ASP.NET method (GetRolesForUser then cache roles internally) means that there is only one call to the provider even if IsUserInRole is called multiple times during processing a request. However for some providers (e.g. WindowsTokenRoleProvider), getting the list of all roles can be expensive, resulting in poor performance.

  • The WCF method (call the provider's IsUserInRole method every time) can result in multiple calls to the provider during the processing of a single request. But the complete list of roles for the current user is never retrieved unless explicitly requested, which can give better performance for providers such as WindowsTokenRoleProvider where this retrieval is expensive.

Upvotes: 4

Related Questions