9

I'm using WindowsTokenRoleProvider to determine Active Directory group membership in an ASP.NET web application.

My problem is that performance is not good, especially when a user is in many groups. As an example, I am in 253(!) groups, and WindowsTokenRoleProvider is taking around 150 seconds to determine what groups I am in.

I know I can use caching so that this isn't done on subsequent requests for a user, but obviously it isn't acceptable to take that long on the first hit.

What are my options? Can I force WindowsTokenRoleProvider to only consider certain groups? (I'm only interested in 5).

Cocowalla
  • 13,822
  • 6
  • 66
  • 112

1 Answers1

13

Some testing has revealed that my problem is that calling:

Roles.IsUserInRole(groupName)

is accessing the method GetRolesForUser in the RoleProvider - which is retrieving details of every role the user is a member of.

But calling:

Roles.Provider.IsUserInRole(groupName)

determines whether or not the user is in the group - without retrieving the details of every role the user is in.

Weird, but it looks like using Roles.Provider.IsUserInRole will solve my problem.

* UPDATE *

It turns out that this is just a partial workaround; if I use imperative permission checks, or 'allow' and 'deny' in web.comfig, then WindowsTokenRoleProvider still goes and slowly gets details of every group the user is a member of :o(

So my question still stands...

* UPDATE *

I solved this by creating a class that extends from WindowsTokenRoleProvider and overriding GetRolesForUser so it only checks for membership of roles specified in the configuration. It includes caching too:

/// <summary>
/// Retrieve the list of roles (Windows Groups) that a user is a member of
/// </summary>
/// <remarks>
/// Note that we are checking only against each system role because calling:
/// base.GetRolesForUser(username);
/// Is _very_ slow if the user is in a lot of AD groups
/// </remarks>
/// <param name="username">The user to check membership for</param>
/// <returns>String array containing the names of the roles the user is a member of</returns>
public override string[] GetRolesForUser(string username)
{
    // Will contain the list of roles that the user is a member of
    List<string> roles = null;

    // Create unique cache key for the user
    string key = String.Concat(username, ":", base.ApplicationName);

    // Get cache for current session
    Cache cache = HttpContext.Current.Cache;

    // Obtain cached roles for the user
    if (cache[key] != null)
    {
        roles = new List<string>(cache[key] as string[]);
    }

    // Was the list of roles for the user in the cache?
    if (roles == null)
    {
        roles = new List<string>();

        // For each system role, determine if the user is a member of that role
        foreach (SystemRoleElement role in WebConfigSection.Settings.SystemRoles)
        {
            if (base.IsUserInRole(username, role.Name))
            {
                roles.Add(role.Name);
            }
        }

        // Cache the roles for 1 hour
        cache.Insert(key, roles.ToArray(), null, DateTime.Now.AddHours(1), Cache.NoSlidingExpiration);
    }

    // Return list of roles for the user
    return roles.ToArray();
}
Cocowalla
  • 13,822
  • 6
  • 66
  • 112
  • Thanks you, Cocowalla. Exactly what I came looking for. – Pita.O May 11 '10 at 18:58
  • I am actually in the similar boat. So does it solve the first time connect problem as well? Will it take longer for the user to connect first time and then quicker subsequent times? – TheTechGuy Feb 08 '12 at 21:47
  • It will take a little longer the first time, but nothing the user will notice (because we use `IsUserInRole` to only check for membership of groups we are actually interested in) – Cocowalla Feb 08 '12 at 22:37
  • 3
    This is a great solution! This should really be part of the WindowsTokenRoleProvider class. Thank you for sharing this code. – Brian Behm Jul 06 '12 at 15:31
  • I am working on your code but I need help with SqlRoleProvider, do you have any insight what I need to change in this line `foreach (SystemRoleElement role in WebConfigSection.Settings.SystemRoles)` for SQL provider. – TheTechGuy Jul 09 '12 at 18:05
  • 1
    @Thecrocodilehunter SystemRoleElement is just a custom ConfigurationElement - you can read the list of roles you are interested in from anywhere you want. Not sure that this is really required for the SQL provider though? – Cocowalla Jul 09 '12 at 18:49
  • Thanks @Cocowalla, I think I am on track. I am using SQL to pull all the roles for that user. I will using `String` in place of `SystemRoleElement`, I am guessing that should work. – TheTechGuy Jul 09 '12 at 18:55
  • Want to test this but: SystemRoleElement WebConfigSection.Settings.SystemRoles are unknow to me. Is this SL specific? – user324365 Mar 26 '11 at 15:55
  • @user324365 SystemRoleElement is just a custom ConfigurationElement - you can read the list of roles you are interested in from anywhere you want – Cocowalla Jul 09 '12 at 18:48