I was able to create this C# code which creates a GenericPrincipal
object initialized with a user's group information. Enjoy.
using System;
using System.Collections;
using System.DirectoryServices;
using System.Security.Principal;
using System.Text;
namespace DataAccess
{
/// <summary>
/// Retrieves a user and group information from Active Directory.
/// Adapted from:
http://groups-beta.google.com/group/microsoft.public.active.directory.interfaces/msg/4c014b52afccd2d0?output=gplain
/// Thanks to Joe Kaplan
/// </summary>
public class ActiveDirectoryDao
{
public ActiveDirectoryDao()
{
}
private GenericPrincipal GetWindowsIdentity(string ldapPath, string
domain, string username, string password)
{
GenericPrincipal principal = null;
string domainAndUsername = domain + @"\" + username;
DirectoryEntry entry = new DirectoryEntry(ldapPath,
domainAndUsername, password);
try
{
// Bind to the native AdsObject to force authentication.
Object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
SearchResult result = search.FindOne();
DirectoryEntry userEntry = new DirectoryEntry(result.Path,
domainAndUsername, password);
string[] groups = GetGroups(userEntry, ldapPath);
GenericIdentity id = new GenericIdentity(username,
"LdapAuthentication");
principal = new GenericPrincipal(id, groups);
}
catch (System.Exception ex)
{
throw new System.Exception("Error authenticating user in active
directory.", ex);
}
return principal;
}
public string[] GetGroups(DirectoryEntry userEntry, string ldapPath)
{
ArrayList allGroupNames = new ArrayList();
userEntry.RefreshCache(new string[] {"tokenGroups"});
PropertyValueCollection groupSids =
userEntry.Properties["tokenGroups"];
ArrayList binarySids = new ArrayList(groupSids.Count);
binarySids.AddRange(groupSids);
for (int i = 0; i <= binarySids.Count - 1; i++)
{
byte[] binSid = ((byte[]) (binarySids[i]));
string octetSid = ConvertToOctetString(binSid);
string groupPath = string.Format(ldapPath + "/<SID={0}>",
octetSid);
DirectoryEntry groupEntry = new DirectoryEntry(groupPath,
userEntry.Username, userEntry.Password);
string groupName =
groupEntry.Properties["samAccountName"].Value.ToString();
allGroupNames[i] = groupName;
}
return (string[])allGroupNames.ToArray(typeof(string));
}
public string ConvertToOctetString(byte[] values)
{
return ConvertToOctetString(values, false, false);
}
public string ConvertToOctetString(byte[] values, bool
isAddBackslash)
{
return ConvertToOctetString(values, isAddBackslash, false);
}
public static string ConvertToOctetString(byte[] values, bool
isAddBackslash, bool isUpperCase)
{
string slash;
if (isAddBackslash)
{
slash = "\\";
}
else
{
slash = string.Empty;
}
string formatCode;
if (isUpperCase)
{
formatCode = "X2";
}
else
{
formatCode = "x2";
}
StringBuilder builder = new StringBuilder(values.Length*2);
for (int iterator = 0; iterator <= values.Length - 1; iterator++)
{
builder.Append(slash);
builder.Append(values[iterator].ToString(formatCode));
}
return builder.ToString();
}
}
}
There are a couple of things to point out here:
- This only works for security groups, not for distribution only groups
(since they aren't in the user's token and won't be in tokenGroups)
- It won't necessarily pick up groups in a different domain
- It can be made much faster doing a search for all of the groups at once
by creating a big LDAP filter with all of the group SIDs than by binding to
each one individually
Someday I'll try to post a sample showing the latter part unless you happen
to decide to try it yourself.
Joe K.
"maggieb" <mag...@obscure.org> wrote in message
news:1108147816.9...@l41g2000cwc.googlegroups.com...
My intent with this class was to get security groups, since I use this
class specifically for security purposes.
This implementation picks up groups in the domain specified in the
ldapPath and the domain argument. I needed it for an asp.net
application that was residing in a different domain than the active
directory where the users were. By providing the full path to the
active directory domain controller in the ldapPath I was able to
retrieve info for users the different domain.
(|(objectSid=\xx\xx\xx\xx....)(objectSid=\xx\xx\xx\xx..)....)
Then, you would use the DirectorySearcher to find all of those groups at
once and get the sAMAccountName from the SearchResult. The net result is
essentially the same, but the single search seems to be about 10x faster in
most of my tests (depending a lot on how many groups are in the token).
If you are interested in looking at it, you can try it pretty easily. I had
some sample VB.NET code (sorry, no C# handy) around that you should be able
to adapt:
Dim searcher As New DirectorySearcher
searcher.SearchRoot = root
searcher.PropertiesToLoad.AddRange(New String() {"isDeleted",
"samAccountName"})
searcher.Filter = GetFilter(sids)
searcher.CacheResults = False
searcher.SearchScope = SearchScope.Subtree
Dim results As SearchResultCollection
Dim result As SearchResult
results = searcher.FindAll()
For Each result In results
If Not result.Properties.Contains("isDeleted") Then 'this should never
happen!
groups.Add(result.Properties("sAMAccountName")(0))
End If
Next
Private Function GetFilter(ByVal sids()() As Byte) As String
Dim filter As New StringBuilder((sids.Length * 90) + 10) 'stupid guess;
not accurate
filter.Append("(|")
For i As Integer = 0 To sids.Length - 1
filter.Append("(objectSid=")
filter.Append(ConvertToOctetString(sids(i), True, True))
filter.Append(")")
Next
filter.Append(")")
Return filter.ToString()
End Function
Best of luck!
Joe K.
"maggieb" <mag...@obscure.org> wrote in message
news:1108661884.9...@l41g2000cwc.googlegroups.com...
public string[] GetGroups(DirectoryEntry userEntry, string ldapPath)
{
ArrayList allGroupNames = new ArrayList();
userEntry.RefreshCache(new string[] {"memberOf"});
PropertyValueCollection groups =
userEntry.Properties["memberOf"];
ArrayList groupsArray = new ArrayList(groups.Count);
groupsArray.AddRange(groups);
for (int i = 0; i <= groupsArray.Count - 1; i++)
{
string FullgroupName = (string)groupsArray[i];
string groupPath = string.Format(ldapPath + "/{0}",FullgroupName);
DirectoryEntry groupEntry = new DirectoryEntry(groupPath,
userEntry.Username, userEntry.Password);
string groupName =
groupEntry.Properties["samAccountName"].Value.ToString();
allGroupNames.Add(groupName);
}
return (string[])allGroupNames.ToArray(typeof(string));
Joe K.
<reach...@gmail.com> wrote in message
news:1110929821.6...@f14g2000cwb.googlegroups.com...
sub DisplayGroups {
print "Called Display Groups \n";
my ($strObjectADsPath, $strSpaces, %dicSeenGroup) = @_;
#print "$strObjectADsPath \n";
my $objObject = Win32::OLE->GetObject($strObjectADsPath);
my $groupName = $objObject->Name;
$groupName =~ s/^CN=//;
print "$groupName \n";;
if ($objObject->Get("memberOf")) {
$colGroups = $objObject->Get("memberOf");
}
foreach my $strGroupDN (in $colGroups) {
# print "String DN is $strGroupDN \n" if ($DEBUG);
if (not $dicSeenGroup{$strGroupDN}) {
$dicSeenGroup{$strGroupDN} = 1;
DisplayGroups("LDAP://" . $strGroupDN, $strSpaces . " ",
%dicSeenGroup);
}
}
}
-----------------------
-Nikhil
The other potential issue with memberOf is that it includes both security
and distribution groups. Depending on what you want, this may or may not be
a good thing. If you only want security groups, then more work is needed to
filter the non-security groups out.
So, I will say that recursing over memberOf does work to expand group
membership. It is just that if you want security groups, tokenGroups seems
to me to be more more straightforward and vastly faster by the time you
verify each group's type.
I can almost read your PERL code. :)
Joe K.
"Nikhil" <mni...@gmail.com> wrote in message
news:42398BF1...@gmail.com...
Error: There is no such object on server
I dont know much about LDAP but the usernanme and LDAP paths are
correct as I've been stuffing around with this for 2 days now.
relevant section in my web.config just in case this could be a problem
<identity impersonate="true" />
<authentication mode="Forms">
<forms loginUrl="CCLogin.aspx" name="adAuthCookie" timeout="60">
<!-- <path="/" /> this gave me a syntax error-->
</forms>
</authentication>
<authorization>
<allow users="*" /> <!-- Allow all users -->
<!-- <allow users="[comma separated list of users]"
roles="[comma separated list of roles]"/>
<deny users="[comma separated list of users]"
roles="[comma separated list of roles]"/>
-->
</authorization>
Any ideas ?
"Joe Kaplan \(MVP - ADSI\)" <joseph....@removethis.accenture.com> wrote in message news:<e3YUxPwK...@TK2MSFTNGP15.phx.gbl>...
Error: There is no such object on server
I dont know much about LDAP but the usernanme and LDAP paths are
correct as I've been stuffing around with this for 2 days now.
relevant section in my web.config just in case this could be a problem
<identity impersonate="true" />
<authentication mode="Forms">
<forms loginUrl="CCLogin.aspx" name="adAuthCookie" timeout="60">
<!-- <path="/" /> this gave me a syntax error-->
</forms>
</authentication>
<authorization>
<allow users="*" /> <!-- Allow all users -->
<!-- <allow users="[comma separated list of users]"
roles="[comma separated list of roles]"/>
<deny users="[comma separated list of users]"
roles="[comma separated list of roles]"/>
-->
</authorization>
Any ideas ?
"Joe Kaplan \(MVP - ADSI\)" <joseph....@removethis.accenture.com> wrote in message news:<e3YUxPwK...@TK2MSFTNGP15.phx.gbl>...
Are you sure it died on that line? That doesn't make any sense at all as
you are just initializing a generic identity with two strings. Do you have
a stack trace that shows that? The error you are showing indicates a COM
exception you would get from an LDAP call if you passed in a distinguished
name for an object that does not exist. The code you show definitely isn't
responsible for that.
Why are you impersonating? Typically you don't do that with forms
authentication as anonymous auth is selected in IIS and impersonation will
result in impersonating the anonymous user. Sometimes you do want to do
that, but most of the time you don't.
Joe K.
"phronima" <phro...@gmail.com> wrote in message
news:79791d35.05033...@posting.google.com...
This really isn't a trivial subject as JoeK is pointing out. It is trivial only
in the case of security groups for a single domain that the user is a member of.
Even then, it can be rather involved resolving the SIDs to names. As JoeK
pointed out there is a quicker way than enumerating all objects by individually
binding to them by the SID= mechanism but you also have to take care there that
your query doesn't get too large. Chances are, it generally won't be but it is
something to keep in mind that could happen.
joe
--
Joe Richards Microsoft MVP Windows Server Directory Services
www.joeware.net
Agreed that this problem is a hard one and often times when people ask for a
solution, they aren't necessarily clear on what it is exactly that they
want. All group memberships in the forest or just the current domain? Just
security groups or DLs too? Only DLs that are mail-enabled in Exchange?
What do we do about FSPs?
Do you ever use Attribute-Scoped Query for doing recursive group unwinding?
This feature seems taylor-made for doing this kind of stuff (search within
the member attribute for additional objects that are also groups). It is
2003-only, but it is still a cool feature. There are finally nice wrappers
for this in .NET 2.0, so the functionality will finally be available to
non-C++ languages and will get more support.
The scripters haven't been invited to the party yet, but they'll likely be
missing all the cool new stuff for a while until the whole monad thing
matures. The SDK team seems to be focusing nearly 100% on improving the
managed code interfaces these days.
Joe K.
"Joe Richards [MVP]" <humore...@hotmail.com> wrote in message
news:OwjOyCFO...@tk2msftngp13.phx.gbl...
joe
--
Joe Richards Microsoft MVP Windows Server Directory Services
www.joeware.net
Joe Kaplan (MVP - ADSI) wrote:
Hi, Thanks for your reply.
Having had a closer look I realised where the problem really is:
string groupPath = string.Format(ldapPath + "/<SID={0}>", octetSid);
DirectoryEntry groupEntry = new DirectoryEntry(groupPath,
userEntry.Username, userEntry.Password);
string groupName =groupEntry.Properties["samAccountName"].Value.ToString();
It failed on the last line because there is no samAccountName in the
groupEntry properties. In fact there are no valid properties in
groupEntry (see stack trace below)
By distinguished user name do you mean CN? I simply used my windows
logon and password. Do I need to be an any specific Administration
group on the AD?
groupPath = "LDAP://machine.domain.com/<SID=010500000000000515000000160b3c6a0840cc693e4abd5fc6040000>"
##STACK TRACE####
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind() at
System.DirectoryServices.DirectoryEntry.get_AdsObject() at
System.DirectoryServices.PropertyCollection.get_Count() at
in2lifeCC.CCLogin.GetGroups(DirectoryEntry userEntry, String ldapPath)
in c:\inetpub\wwwroot\cc\cclogin.aspx.cs:line 262 at
in2lifeCC.CCLogin.GetWindowsIdentity(String ldapPath, String domain,
String username, String password) in
c:\inetpub\wwwroot\cc\cclogin.aspx.cs:line 233
Runtime values in the groupEntry
adsObject <undefined value>
AdsObject <error: an exception of type:
{System.Runtime.InteropServices.COMException} occurred>
Any ideas? I wonder how I can explain my technical difficulty against
my estimated time, which by now is well overrun..;-)
The server part isn't always used, but DN usually is. DN usually looks like
CN=someone,CN=users,DC=domain,DC=com or something like that and uniquely
specifies an object in the directory. <SID=xxxxxx> is an alternate form of
DN that Active Directory supports for referencing an object directly by its
SID. CN is just the common name and can be used to uniquely identify an
object in its current container, but does not necessarily uniquely identify
an object in the whole directory. In the above DN, CN=someone identifies
that object in the CN=users container, but there could be another CN=someone
in another part of the directory.
My guess is that the error you are getting is related to security somehow.
Is it possible that the ID and password you are using doesn't have access to
view that particular security group in the directory? If you built the SID
correctly, then that binding string should work.
I'd suggest checking this in a low level tool like LDP.exe. You can bind
with those credentials to AD and then paste that <SID=xxx> DN into the View
| Tree... dialogue and the group should come right up.
Otherwise, I'm not sure why it isn't working.
Joe K.
"phronima" <phro...@gmail.com> wrote in message
news:79791d35.05040...@posting.google.com...
The tokenGroups thing works in general, so I think we could have solved that
for you had we tried harder.
Joe K.
"phronima" <phro...@gmail.com> wrote in message
news:79791d35.05040...@posting.google.com...
My question at this stage is: if it had been a security issue how was
I able to have it working with memberOf but failed when using
tokengroups?
Joe K.
"phronima" <phro...@gmail.com> wrote in message
news:79791d35.05040...@posting.google.com...
>
Joe Kaplan (MVP - ADSI) wrote:
Also, why don't you start a new thread as this one has gotten very deep.
Joe K.
<phro...@gmail.com> wrote in message
news:1112939712.9...@f14g2000cwb.googlegroups.com...