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>

Tuesday, July 29, 2008

My Fancy New 3G Wireless Card

Lately I have been finding myself in more and more situations where I am giving a presentation and I need wireless connectivity and it isn't readily available.  Yesterday I finally broke down and picked up a USBConnect 881 card from AT&T, and so far, I like it.  For about $60 a month the cost isn't too bad for what you are getting, and in using it last night, the 3G speeds were pretty good for doing what I needed to do.  I orginally wanted to go with an Express PCI card for my Mac, but opted for the USB card for more flexibility.

Saturday, July 19, 2008

My Top 5 iPhone Applications

So of course I picked up a shiny new iPhone 3G on launch day.  I was lucky to be in Hillsboro Oregon at the time which meant my line was not too long and I got to take advantage of no sales tax.  It's been a while since I've posted (I'm going to correct that in the next few weeks), so I thought I'd do a round-up of my top 5 favorite 3rd party applications.  In no particular order here they are...

This is Apple's fantastic iTunes remote.  I don't have an Apple TV yet, but I have been playing with this with my iTunes library on my laptop and it works flawlessly.  I can't wait to get my condo all setup so I can take full advantage of this tool.

This has to be one of the less polished games in the iTunes store, but it is addicting. The basic goal is to fly your little ship through a minefield of cubes, if you hit a cube, you die. You pretty much just compete against yourself for high score. The game could use stages, so you don't just end up playing the same first level over and over again, but short of that, it is an awesome first showing for a casual game.

Evernote is a great desktop tool for knowledge capture.  It works on Mac and Windows, and with their latest release they have included an iPhone client.  This comes in handy so often in my life when I have meetings where lots of white boarding occurs.  All I have to do is open Evernote and take a picture of the whiteboard and the image is automatically synced with my laptop.  On top of that, Evernote has fantastic OCR technology allowing you to then search all of your content, even my aweful whiteboard chicken scratch gets properly indexed.

I always have a terrible time picking a restaurant when it comes time to go out to eat.  This tool solves that problem in a fun way.  Using location based services, Urbanspoon will zero in on your city.  You then lock in your options, be it neighborhood, cuisine or price.  Then simply shake your phone and Urbanspoon will randomly pick a place for you, if you don't like it, shake it again!

Tie: Pandora and Last.fm
Both of these applications do very similar things. Based on the same technologies of their respective web sites, you can get a customized radio station based on your music tastes. Pandora is based on the Music Genome Project, which uses a more scientific approach to matching tunes. Last.fm relies on social networks and listening patterns to do something very similar. I am a Last.fm user, so I like this app for that reason, however, I believe Pandora streams much more reliably with less waiting for buffering.

Monday, June 9, 2008

WWDC 2008 Keynote

I got to attend the Apple Worldwide Developers Conference 2008 keynote this morning, it was a lot of fun. Luckily for me I didn't have to get there at 6am like my colleges who held a spot for me. I got to see Steve Balmer speak at Mix '08 back in March, he was good, but doesn't hold a candle to watching Mr. Jobs.

On the left is a picture I took from line, so may nerds, myself included.

Sunday, June 1, 2008

TripIt - One of My Favorite Tools

I tend to fly fairly often (at least once a month). I have been using TripIt for a few months now and I'm a big fan. TripIt helps you track your travels very easily. All you do is forward your email confirmations for your flights, hotels or rental cars and TripIt will consume the information and keep all your travel information in one handy place. That combined with the ability to subscribe to your trip details via an iCal feed, as well as a new mobile site that looks great on the iPhone, make it a great tool for any frequent traveler.

Monday, May 19, 2008

ReSharper 4.0 Nightly Build Finally Stable?

My all time favorite add-on for Visual Studio, ReSharper, has finally marked one of their nightly builds for the upcoming 4.0 version as "Stable". This is good news since 4.0 brings support for Visual Studio 2008 and the new .Net 3.5 language features such as LINQ, Extension Methods and Automatic Properties. ReSharper has become a tool that I find hard to live without and I have been using the nightly builds for the last couple months. For the most part they have been good but I'm hoping this means they will have a final release soon, originally they promised the release to be in mid-Februrary.

[UPDATE]: Looks like they have finally launched the beta for 4.0, get it here.

[UPDATE 6/26/2008]: Hooray, the final version was released last week, get it here.

Sunday, May 4, 2008

Maker Faire 2008

Today I attended the Bay Area Maker Faire 2008. Over all it was an entertaining trip, except for the hour we sat on the onramp to get to the fairgrounds. I got to see Adam Savage of the Mythbusters speak, as well as enjoy a couple rounds of Battle Bots. And for your viewing pleasure, here are some Battle Bot videos taken with my new Flip Video camera.