Active Directory Role Provider

>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #006080; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

I’ve been using the ActiveDirectoryMembershipProvider to allow my users to login to a custom ASP.Net site with their AD credentials and it is pretty straight forward. Recently I needed to add more granularity to who can view various parts of the site. I wanted to take advantage of our existing AD groups so I assumed there would be something like an ActiveDirectoryRoleProvider as well. After a little searching, it became clear that wasn’t the case, so I decided to roll my own.

Creating a custom role provider is pretty easy, all you have to do is create a new class and inherit RoleProvider:


public class ActiveDirectoryRoleProvider : RoleProvider
{}

You will have to create stubs for all the inherited members. We only need to implement a couple of them to get basic role membership checking. We need to get our AD configuration information out of the Web.Config values, so we’ll create a few properties and override the Initialize method:


private string ConnectionStringName { get; set; }
private string ConnectionUsername { get; set; }
private string ConnectionPassword { get; set; }
private string AttributeMapUsername { get; set; }

public override void Initialize(string name, NameValueCollection config)
{
ConnectionStringName = config["connectionStringName"];
ConnectionUsername = config["connectionUsername"];
ConnectionPassword = config["connectionPassword"];
AttributeMapUsername = config["attributeMapUsername"];

base.Initialize(name, config);
}

Now we’ll override GetRolesForUser which is the bulk of our implementation. We use the DirectorySearcher class in System.DirectoryServices to query AD for the passed username. We then pull the memberOf property from that user and extract the CN component for each entry as the role:


public override string[] GetRolesForUser(string username)
{
var allRoles = new List<string>();

var root = new DirectoryEntry(WebConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString, ConnectionUsername, ConnectionPassword);

var searcher = new DirectorySearcher(root, string.Format(CultureInfo.InvariantCulture, "(&(objectClass=user)({0}={1}))", AttributeMapUsername, username));
searcher.PropertiesToLoad.Add("memberOf");

SearchResult result = searcher.FindOne();

if (result != null && !string.IsNullOrEmpty(result.Path))
{
DirectoryEntry user = result.GetDirectoryEntry();

PropertyValueCollection groups = user.Properties["memberOf"];

foreach (string path in groups)
{
string[] parts = path.Split(',');

if (parts.Length > 0)
{
foreach (string part in parts)
{
string[] p = part.Split('=');

if (p[0].Equals("cn", StringComparison.OrdinalIgnoreCase))
{
allRoles.Add(p[1]);
}
}
}
}
}

return allRoles.ToArray();
}

And finally we need to override IsUserInRole so we can easily check for role membership in code:


public override bool IsUserInRole(string username, string roleName)
{
string[] roles = GetRolesForUser(username);

foreach (string role in roles)
{
if (role.Equals(roleName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
}

Here’s the the full code (minus the unimplemented inherited methods):


public class ActiveDirectoryRoleProvider : RoleProvider
{
private string ConnectionStringName { get; set; }
private string ConnectionUsername { get; set; }
private string ConnectionPassword { get; set; }
private string AttributeMapUsername { get; set; }

public override void Initialize(string name, NameValueCollection config)
{
ConnectionStringName = config["connectionStringName"];
ConnectionUsername = config["connectionUsername"];
ConnectionPassword = config["connectionPassword"];
AttributeMapUsername = config["attributeMapUsername"];

base.Initialize(name, config);
}

public override bool IsUserInRole(string username, string roleName)
{
string[] roles = GetRolesForUser(username);

foreach (string role in roles)
{
if (role.Equals(roleName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
}

public override string[] GetRolesForUser(string username)
{
var allRoles = new List<string>();

var root = new DirectoryEntry(WebConfigurationManager.ConnectionStrings[ConnectionStringName].ConnectionString, ConnectionUsername, ConnectionPassword);

var searcher = new DirectorySearcher(root, string.Format(CultureInfo.InvariantCulture, "(&(objectClass=user)({0}={1}))", AttributeMapUsername, username));
searcher.PropertiesToLoad.Add("memberOf");

SearchResult result = searcher.FindOne();

if (result != null && !string.IsNullOrEmpty(result.Path))
{
DirectoryEntry user = result.GetDirectoryEntry();

PropertyValueCollection groups = user.Properties["memberOf"];

foreach (string path in groups)
{
string[] parts = path.Split(',');

if (parts.Length > 0)
{
foreach (string part in parts)
{
string[] p = part.Split('=');

if (p[0].Equals("cn", StringComparison.OrdinalIgnoreCase))
{
allRoles.Add(p[1]);
}
}
}
}
}

return allRoles.ToArray();
}
}

Add the role provider to your Web.Config:


<system.web>
<roleManager enabled="true" defaultProvider="ADRoleProvider" cacheRolesInCookie="true" cookieName=".ASPXROLES" cookiePath="/"
cookieTimeout="30" cookieRequireSSL="false" cookieSlidingExpiration="true" createPersistentCookie="false" cookieProtection="All">
<providers>
<clear />
<add name="ActiveDirectoryRoleProvider" connectionStringName="ADConnectionString" connectionUsername="username"
connectionPassword="password" attributeMapUsername="sAMAccountName" type="ActiveDirectoryRoleProvider" />
</providers>
</roleManager>
</system.web>

You can then check the roles of your user in code like so:


Roles.IsUserInRole("My Group")

Or control access to entire directories via the Web.Config:


<location path="RestrictedSubDirectory">
<system.web>
<authorization>
<allow roles="My Group"/>
<deny users="*" />
</authorization>
</system.web>
</location>
Advertisements
Explore posts in the same categories: .net, active directory, asp.net, code, forms authentication, microsoft

10 Comments on “Active Directory Role Provider”

  1. sara Says:

    >nice return to the uber technical. i like it when i don’t understand a single word in your posts 🙂

  2. ZzZoel Says:

    >In the config file the rolemanager property ‘defaultProvider’ should have the same value as the name of the provider which is ‘ActiveDirectoryRoleProvider’ instead of ‘ADRoleProvider’

  3. Greg Martin Says:

    >Good catch, you are correct.

  4. Mike Says:

    >…many thanks for the time you saved me!

  5. Rudolf Says:

    >Hello,Already a big thanks for having posted this. And thanks in advance for your help!first, I’m using VS2005, so I did some minor refactoring, and the class compiles without problems.I can use the class from within code to e.g. get the groups the user is in etc… (so in my default.aspx, I can write: string[] userRoles = Roles.GetRolesForUser(strLogin); and I trace it through your class, all ok!!!However, I’m using sqlsitemapprovider.cs(from the msdn site) and set the rolemanager as you wrote, but I never get the full menu, however in my DB the role field is filled in, and exactly the same as the values returned by your class function.What am I doing wrong? Any ideas, suggestions are much appreciated.thanks again, and again in advance,

  6. Rudolf Says:

    >I was wondering if it could be related to the username and password in the web.config to query LDAP, but then again, I can run Roles.GetRolesForUser anonymously or by specifying my useraccount and login inside that function. Either way it works…Except for the sqlsitemapprovider it does not seem to work.

  7. Joel Forman Says:

    >Hey Rudolf,I have used Greg’s AD role provider with a sitemap, and have had it work as expected forme. I haven’t used the SqlSiteMapProvider, just the standard XmlSiteMapProvider. There is an attribute on the XmlSiteMapProviderthat is key for this to work as I want it to, securityTrimmingEnabled. Setting the securityTrimmingEnabled attribute to “true” means that no menu item will show up in the menu if the user is not authorized to view that page. Otherwise, menu items continue to show but once a user clicks on that page, they are restricted.

  8. kwazi Says:

    >Hi,Thank you for sharing this.I already see some place are use it 🙂

  9. Anonymous Says:

    >Many thanks!!! Just saved my a$$.I did find that I needed to use the built in membership provider (AspNetActiveDirectoryMembershipProvider) as well as your role provider to get the same functionality as the default setup in asp.net mvc (removed the SQL Server store for users as mine is an internal app and AD was a better solution).Again, many many thanks!

  10. Pradeep Says:

    >Hi,While creating the "DirectoryEntry" instance, you are passing the connectionPassword. Say, I am ruuningmy WCF service in IIS under an separate APP_POOL. Is it possibel to user APP_POOL user account for "DirectoryEntry", instead of specifying UserName, Password explicitly?Regards,Pradeep.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: