|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Contents
IntroductionI've written this code in order to fill a percieved gap in Microsoft's current ASP.NET role management offerings. (If you are not familiar with ASP.NET Membership and Role Management, you may want to check out the current framework documentation on Securing ASP.NET Web Sites before continuing this article. The Membership and Role summary pages should give you a good start.) Currently, if you are using forms-based authentication with Microsoft's That's where Please make certain that you read through this documentation and understand the security and performance concerns mentioned. Also, look through the code to gain an understanding of what exactly how it operates. RequirementsIn order to make use of this
Technical SummaryUsing the source code that Microsoft has released for IMPORTANT - All of the Active Directory queries in the class are currently made against the LDAP sAMAccountName attribute. Consequently, any time you are referring to a group or user name in the web.config, please be certain you are using the sAMAccountName. However, Certain built-in or common Active Directory groups are specifically excluded in the source code. For example, Exchange Enterprise Servers would never, ever be used as a role, so that is in an internal exclusion list. These system groups cannot be queried against, and will never show up in any results. You can also specify your own groups to ignore in the Web.config (see configuration below). Querying Active DirectoryAll of the heavy lifting done by this class involves querying against Active Directory, and there are as many ways to query Active Directory as there are to shoot yourself in the foot. Below is the complete code for retrieving a user's role (i.e. AD Group) membership. The SQL Caching code is ellipsed out for explanation purposes. /// <summary>
/// Retrieve listing of all roles to which a specified user belongs.
/// </summary>
/// <param name="username"></param>
/// <returns>String array of roles</returns>
public override String[] GetRolesForUser(String username)
{
...
//Create an ArrayList to store our resultant list of groups.
ArrayList results = new ArrayList();
//PrincipalContext encapsulates the server or domain against which all
//operations are performed.
using (PrincipalContext context = new PrincipalContext(ContextType.Domain,
null, _DomainDN))
{
try
{
//Create a referance to the user account we are querying
//against.
UserPrincipal p = UserPrincipal.FindByIdentity(context,
IdentityType.SamAccountName, username);
//Get the user's security groups. This is necessary to
//return nested groups, but will NOT return distribution groups.
var groups = p.GetAuthorizationGroups();
foreach (GroupPrincipal group in groups)
{
if (!_GroupsToIgnore.Contains(group.SamAccountName))
{
if (_IsAdditiveGroupMode)
{
if (
_GroupsToUse.Contains(
group.SamAccountName))
{
results.Add(
group.SamAccountName);
}
}
else
{
results.Add(group.SamAccountName);
}
}
}
}
catch (Exception ex)
{
throw new ProviderException(
"Unable to query Active Directory.", ex);
}
}
...
return results.ToArray(typeof(String)) as String[];
}
Do not let the nested Caching to SQL ServerQuerying against Active Directory can be pretty slow. To alleviate this, The manner of caching is extremely simple. I vacillated between using a more fully normalized series of tables and the simplified method I finally settled upon. Data is cached to a single table as specified below.
CacheId ApplicationId CacheType CacheKey CacheValue ExpireDT
49 MyApp L AllRoles Customer Service,IT 8/18/2008 2:49:50 PM
50 MyApp U asmith IT 8/18/2008 2:49:50 PM
51 MyApp R IT dsmith,msmith,asmith,jsmith,ssmith,bsmith 8/18/2008 2:49:50 PM
There are certain situations in which this simplified method of caching could present minor annoyances. For example: The cached results for IT is set to expire at 2:50, the results for asmith are set to expire at 3:50, and asmith is removed from the IT Active Directory group. In this situation, there could be an hour period where enumerating IT shows that asmith is not a member, but asmith still thinks he is. This could be avoided by having a group table, a user table, and a join table. However, I decided that the added complexity and overhead was not worth the trade-off, since every time a cache item is set, the integrity of every item involved would have to be verified. Included in the code zip file is a SQL file that will create the necessary table and stored procedures to implement caching. This has been tested under SQL Server 2005 Standard and Developer editions, and SQL Server 2008 Standard. SQL Caching PerformanceHonestly, the impact of enabling SQL caching is much larger than I had thought it would be. My test environment consists of only two groups and about a half dozen users. With such a small set of data to work with, I figured the performance impact would be minimal. The SQL instance I am connecting to is on a separate server from the test site. The code below basically what I used to test performance, though I actually set it to run a few hundred times. DateTime dtStart;
DateTime dtEnd;
TimeSpan executionTime;
string[] results;
dtStart = DateTime.Now;
results = Roles.GetAllRoles();
results = Roles.GetRolesForUser("dsoref");
results = Roles.GetUsersInRole("IT");
dtEnd = DateTime.Now;
executionTime = dtEnd - dtStart;
tests.InnerHtml += "Execution Time: " + executionTime.TotalMilliseconds + "<br /><br />";
Without caching enabled, this chunk of code would take from 78 to 154ms to execute, with the average hovering at about 98ms. With caching enabled, the initial request would take from 81 to 204ms to execute, with the average hovering around 102ms since the data needed to be cached to the SQL database. However, subsequent requests within the expiration time took only 14 to 16ms. I guess SQL is faster than AD after all. Using the CodeFirst, you will want to compile the provider and copy the resulting .dll into your website's bin folder. You could instead copy the .cs file into your App_Code directory, but this is somewhat less secure. Granted, you should be able to trust all of your developers, but better safe than sorry. Web.ConfigNext, you will want to enter the proper configuration settings into your web.config, as below. I will run through each of these settings. ...
<connectionStrings>
...
<add name="ActiveDirCS"
connectionString="LDAP://DC=YourDomain,DC=com"/>
</connectionStrings>
...
<roleManager enabled="true" defaultProvider="ActiveDirRP">
<providers>
<clear/>
<add applicationName="MyApp"
name="ActiveDirRP"
type="DanielPS.Roles.ADRoleProvider"
activeDirectoryConnectionString="ActiveDirCS"
groupMode="Additive"
groupsToUse="IT, Customer Service"
groupsToIgnore="Senior Management"
usersToIgnore="asmith, ksose"
enableSqlCache="True"
sqlConnectionString="SQLCacheCS"
cacheTimeInMinutes="30" />
</providers>
</roleManager>
...
Note: If a groupMode is additive, and a group is specified in both groupsToUse and groupsToIgnore, groupsToIgnore will take precedence. Note: If a group is specified in groupsToUse, but does not exist in Active Directory, it will be ignored. Groups and Users Excluded in the SourceThe following groups are excluded in the source code. Even specifying them in groupsToUse will not make them functional. Domain Guests, Domain Computers, Group Policy Creator Owners, Guests, Users, Domain Users, Pre-Windows 2000 Compatible Access, Exchange Domain Servers, Schema Admins, Enterprise Admins, Domain Admins, Cert Publishers, Backup Operators, Account Operators, Server Operators, Print Operators, Replicator, Domain Controllers, WINS Users, DnsAdmins, DnsUpdateProxy, DHCP Users, DHCP Administrators, Exchange Services, Exchange Enterprise Servers, Remote Desktop Users, Network Configuration Operators, Incoming Forest Trust Builders, Performance Monitor Users, Performance Log Users, Windows Authorization Access Group, Terminal Server License Servers, Distributed COM Users, Administrators, Everybody, RAS and IAS Servers, MTS Trusted Impersonators, MTS Impersonators The following users are excluded in the source code. They will not show up in any group membership enumeration. Administrator, TsInternetUser, Guest, krbtgt, Replicate, SERVICE, SMSService Future Enhancements
History
| ||||||||||||||||||||