Showing posts with label code. Show all posts
Showing posts with label code. Show all posts

Friday, August 15, 2008

Active Directory Role Provider


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>

Friday, March 14, 2008

My IDataRecord Extension Methods

I decided it was high time I did a post with some actual code samples in it, so here we go.

I am really enjoying some of the new C# 3.0 language features, one in particular is extensions methods. If you are not yet familiar with extension methods, ScottGu has a good overview. I want to highlight some specific extension methods I've created for IDataRecord that I think are extremely handy, especially when you spend a lot of time working in the data layer with DataReaders like I do. I feel like I've written variations on the same data layer 100 times. Every time it evolves, when I went from .Net 1.1 to 2.0 I took heavy advantage of generics. Now in .Net 3.5 I am simplifying it even more with extensions.

When working with DataReaders you might often have a ton of code that looks like this:


string myString = record.GetString(record.GetOrdinal("MyField"));


Where record is an IDataRecord coming from a SqlDataReader or something similar. Now that's not a terrible line of code, but it gets pretty annoying typing record.GetOrdinal all the time. So to save me that annoyance I've added the following to my IDataRecordExtensions class:


public static long GetString(this IDataRecord record, string field)
{
return record.GetString(record.GetOrdinal(field));
}


An now all I have to call in my record handling code is this:


string myString = record.GetString("MyField");


Not ground-breaking, but better, and you can write an extension for all the types you are getting data for. Here's a better example of how extension methods for your IDataRecord can be more useful. Sometimes you might be dealing with a nullable type, IDataRecord doesn't handle this too gracefully, you end up having to write code that looks something like this:


string myString = null;
int ordinal = record.GetOrdinal("MyField");

if(!record.IsDBNull(ordinal))
{
myString = record.GetString(ordinal);
}


Again, not a ton of code, but if you have to do this hundreds of times it's going to get annoying and you are going to make mistakes. Now check this out, in my IDataRecordExtensions class I add the following two methods:


private static T GetNullable<T>(IDataRecord record, string field, Func<int, T> func)
{
T result = default(T);
int ordinal = record.GetOrdinal(field);

if (!record.IsDBNull(ordinal))
{
result = func(ordinal);
}

return result;
}

public static String GetNullableString(this IDataRecord record, string field)
{
return GetNullable<string>(record, field, x => record.GetString(x));
}


OK, what the heck is going on here. Lets look at GetNullableString first. This is an extension method for an IDataRecord that takes a field name and then calls this private GetNullable method. Check out that third argument though. In GetNullable its defined as Func<int, T> func. What we are doing is taking advantage of lambda support in C# 3.0 and we are saying, pass me a function that takes an int and returns me a generic T, and in this case T is a string. x => record.GetString(x) is saying passing the record's GetString method as that parameter. Now in our data handling code we can just write:


string myString = record.GetNullableString("MyField");


Nice. What's even better, is now we can easily add other extensions for other more interesting nullable types, such as double by adding code like this to the IDataRecordExtensions class:

public static double? GetNullableDouble(this IDataRecord record, string field)
{
return GetNullable<double?>(record, field, x => record.GetDouble(x));
}


So there you go. Hopefully my experiences with extension methods and IDataRecord are helpful to others.