Most of the time P&P contains a lot of good information though occasionally the code for some How-To is not really accurate.
During the late stage of an Intranet ASP.NET project, we are requested by the user to switch from Integrated Windows Authentication to Forms Authentication so as to have consistency in the login interface. However, we need to authenticate a particular user against the Active Directory and continue to make use of Windows group information for role-based security. Simple! Why not just plug in the code from "
How To Use Forms Authentication with Active Directory"
Most of the materials are proper but two of them can be improved.
(1)
The first is GetGroups(). As we all know, one nice benefit of Active Directory over the old Windows NT directory is that Active Directory supports group nesting. Nested groups give you flexibility in designing the group structure in a way similar to the organization hierarchy or delegation workflow, as well as applying suitable ACLs to resources. (Note that you need to raise an Active Directory domain to the Windows Server 2003 functional level or the Windows 2000 native domain mode to have this benifit, see also
Functional Levels Background Information).
The problem with the current GetGroups() implementation using memberOf is that the complete nested group information is not acquired. For example, if GroupRoot has two subgroup GroupA and GroupB, GroupA contains User1, calling GetGroups() for User1 would only retrieve the group GroupA, but not GroupRoot (this also depends on the administrator's setting in the memberOf property of User1 on the AD, whether having GroupRoot explicitly specified - this is usually not so). In addition, memberOf does not include the user's Primary Group, this could be bad if the Primary Group is what you are after.
Having the whole hierarchy group information is valuable in the flexibility of using AD role-based security. Thus we need to not only retrieve the primary group but also the other group being implied. If the nesting is very simple or the number of groups is a few, it would be convenient to hard code the relationship rules, for example, if User1 belongs to GroupA, he also belongs to GroupRoot. This of course is not optimal but could be good enough sometimes.
To get the complete security group membership, Joe Kaplan has proposed using TokenGroups in the adsi newsgroup (see also, google "LookupSecurityGroups").
RefreshCache(new string[]{"tokenGroups"}) would retrieve SIDs for the groups the user belongs to, which can be in turn resolved into strings using SID binding. I have converted his VB.NET code to C# and it should be just fine inserted into the MS's implementation. Key points are is highlighted.
public string GetGroupsEx(string domain, string username, string pwd)
{
string domainAndUsername = domain + @"\" + username;
DirectoryEntry entry = new DirectoryEntry("LDAP://yourCompanyName.com/DC=yourCompanyName,DC=com",
domainAndUsername, pwd);
string octetSid = string.Empty;
Object obj = entry.NativeObject;
entry.RefreshCache(new string[]{"tokenGroups"});
PropertyValueCollection groupSids = entry.Properties["tokenGroups"];
ArrayList binarySids = new ArrayList(groupSids.Count);
binarySids.AddRange(groupSids);
StringBuilder groupNames = new StringBuilder();
string groupName = string.Empty;
for (int i=0; i<binarySids.Count; i++)
{
byte[] binSid = (byte[])binarySids

;
octetSid = ToOctetString(binSid);
string groupPath = string.Format("LDAP://<SID={0}>", octetSid); // Use <>
DirectoryEntry groupEntry =
new DirectoryEntry(groupPath, domainAndUsername, pwd);
try
{
Object obj2 = groupEntry.NativeObject;
groupName = groupEntry.Properties["samAccountName"].Value.ToString();
groupNames.Append(groupName);
groupNames.Append("");
}
catch (Exception ex)
{
throw new Exception("Error obtaining group names. " + ex.Message);
}
}
return groupNames.ToString();
}
public static string ToOctetString(byte[] values)
{ return ToOctetString(values, false, false); }
public static string ToOctetString(byte[] values, bool isBackslashAdded)
{ return ToOctetString(values, isBackslashAdded, false); }
public static string ToOctetString(byte[] values, bool isBackslashAdded, bool isUpperCase)
{ string slash = string.Empty;
if (isBackslashAdded) { slash = "\\"; }
string formatcode = string.Empty;
if (isUpperCase)
{ formatcode = "X2"; }
else
{ formatcode = "x2"; }
StringBuilder sb = new StringBuilder(values.Length*2);
for (int i=0; i {
sb.Append(slash);
sb.Append(values

.ToString(formatcode));
}
return sb.ToString();
}
Another plus point of the above code is you would retrieve only security groups but not distribution groups.
(2)
The second point in MS's reference implmentation is creating the authetication ticket as follow:
FormsAuthenticationTicket authTicket =
new FormsAuthenticationTicket(1,
txtUserName.Text,
DateTime.Now,
DateTime.Now.AddMinutes(60),
false, groups);
If you use this code, you will realize this is hard wiring and completely leave the configuration in portion out of picture! Important parameters such as timeout, requireSSL or slidingExpiration are no longer configurable.
Of course, before setting the cookie as part of the outgoing response, we can have any control over how the cookie should be created. In Hernan de Lahitte's
Forms authentication and role-based security, instead a HttpCookie is created simply using FormsAuthentication.GetAuthCookie() to have all the initial attributes loaded with the web.config Forms section. Just that simple.
The point is that Microsoft is providing a reference implementation and it is just a start point (being pretty good one). As usual, a reference implementation is usually not that optimized, think twice before you cut and paste all. :) In other words, knowing why and what you are doing is more important than simply finding out how-to.
Posted
Oct 13 2004, 01:12 AM
by
blackinkbottle