I’m having an issue with integrating my ASP.NET web service with an Active Directory setup, and using it to authentication users and check with AD groups they are a member of and if they have permissions to use my custom application.
My custom application has its own permissions, and the administrators configure Active Directory groups that are allow to use the custom application.
The issue I’m having is when a user from a different Trusted AD forest, with full two way trust, attempts to login I can’t get a list of his groups from the AD server my ASP.NET web services communicates with. The ASP.NET web service only has access to the AD server (AD Main), not the trust AD controller (AD Secondary).
The user is a member of the (AD Secondary) domain, and I can authenticate that user against the (AD Main) domain, but I can’t get a list of groups from the (AD Main) domain when the user is in the (AD Secondary) domain.
I’ve tried this code.
StringCollection groupids = new StringCollection();
try
{
DirectoryLibrary dirLib = new DirectoryLibrary();
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://" + domain,username, password);
if (directoryEntry != null)
{
//Enum the properties so we can see what is in them
foreach (string propname in directoryEntry.Properties.PropertyNames)
{
Debug.WriteLine(propname);
}
object obGroups = directoryEntry.Invoke("Groups");
foreach (object ob in (IEnumerable)obGroups)
{
// Create object for each group.
DirectoryEntry obGpEntry = new DirectoryEntry(ob);
groupids.Add(obGpEntry.NativeGuid);
}
}
}
catch (DirectoryServicesCOMException ex) { throw ex; }
I’ve tried to move away from the DirectoryEntry object to, something like this.
List<GroupPrincipal> result = new List<GroupPrincipal>();
StringCollection groupids = new StringCollection();
PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain, domain, userName, password);
// find your user
UserPrincipal user = UserPrincipal.FindByIdentity(yourDomain, userName);
// if found - grab its groups
if (user != null)
{
PrincipalSearchResult<Principal> groups = user.GetGroups();
// iterate over all groups
foreach (Principal p in groups)
{
// make sure to add only group principals
if (p is GroupPrincipal)
{
groupids.Add(p.DisplayName);
}
}
}
But, I don’t get the user and I can’t get a list of the groups for that user in the other domain. Any help would be appreciated.
This appears to be a great use case for the AD derived attribute memberOf. With the DirectoryEntry directoryEntry object, you can enumerate what groups a user belongs to.
foreach (object group in directoryEntry.Properties["memberOf"])
{
DirectoryEntry obGpEntry = New DirectoryEntry("LDAP://" + (String)group);
groupids.Add(obGpEntry.NativeGuid);
}
It's also likely that you could use the first code segment if you prefixed ob with "LDAP://"
I think you have to connect to the remote AD and get the data you want.
I wrote a replication once, where i replicate from many AD's
Some code out of it:
Public Function GetDirectoryEntry() As Object
If InStr(1, m_sLdapPath, "DC=") > 0 Then
Dim directory_service As New PrincipalContext(ContextType.Domain, m_sDomain, m_sLdapPath)
Return directory_service
Else
Dim directory_service As New PrincipalContext(ContextType.Machine, m_sDomain, m_sLdapPath)
Return directory_service
End If
End Function
Public Function GetUserList() As PrincipalSearchResult(Of Principal)
Dim directory_service As PrincipalContext = CType(GetDirectoryEntry(), PrincipalContext)
Dim directory_user As New UserPrincipal(directory_service)
Dim directory_userlist As New PrincipalSearcher(directory_user)
directory_userlist.QueryFilter = directory_user
Return directory_userlist.FindAll
End Function
Public Function GetGroupList() As PrincipalSearchResult(Of Principal)
Dim directory_service As PrincipalContext = CType(GetDirectoryEntry(), PrincipalContext)
Dim directory_group As New GroupPrincipal(directory_service)
Dim directory_grouplist As New PrincipalSearcher(directory_group)
directory_grouplist.QueryFilter = directory_group
Return directory_grouplist.FindAll
End Function
I know this is not exactly what you need, but this shows how to connect and fetch data from any AD.
In my case I get a userlist, grouplist or whatever and then work with those collections.
Dim l_oGroupList As Object = oDirectory.GetGroupList()
For Each l_oGroup In l_oGroupList
If l_oGroup.Members.Count > 0 Then
If l_oGroup.Members.Contains(directory_service, IdentityType.UserPrincipalName, Username) Then
' he is part of the group
End If
End If
Next
I hope this helps a bit to solve the problem...
Related
How can I get a list of users from active directory? Is there a way to pull username, firstname, lastname? I saw a similar post where this was used:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN");
I have never done anything with active directory so I am completely lost. Any help would be greatly appreciated!
If you are new to Active Directory, I suggest you should understand how Active Directory stores data first.
Active Directory is actually a LDAP server. Objects stored in LDAP server are stored hierarchically. It's very similar to you store your files in your file system. That's why it got the name Directory server and Active Directory
The containers and objects on Active Directory can be specified by a distinguished name. The distinguished name is like this CN=SomeName,CN=SomeDirectory,DC=yourdomain,DC=com. Like a traditional relational database, you can run query against a LDAP server. It's called LDAP query.
There are a number of ways to run a LDAP query in .NET. You can use DirectorySearcher from System.DirectoryServices or SearchRequest from System.DirectoryServices.Protocol.
For your question, since you are asking to find user principal object specifically, I think the most intuitive way is to use PrincipalSearcher from System.DirectoryServices.AccountManagement. You can easily find a lot of different examples from google. Here is a sample that is doing exactly what you are asking for.
using (var context = new PrincipalContext(ContextType.Domain, "yourdomain.com"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
Console.WriteLine("First Name: " + de.Properties["givenName"].Value);
Console.WriteLine("Last Name : " + de.Properties["sn"].Value);
Console.WriteLine("SAM account name : " + de.Properties["samAccountName"].Value);
Console.WriteLine("User principal name: " + de.Properties["userPrincipalName"].Value);
Console.WriteLine();
}
}
}
Console.ReadLine();
Note that on the AD user object, there are a number of attributes. In particular, givenName will give you the First Name and sn will give you the Last Name. About the user name. I think you meant the user logon name. Note that there are two logon names on AD user object. One is samAccountName, which is also known as pre-Windows 2000 user logon name. userPrincipalName is generally used after Windows 2000.
If you want to filter y active accounts add this to Harvey's code:
UserPrincipal userPrin = new UserPrincipal(context);
userPrin.Enabled = true;
after the first using. Then add
searcher.QueryFilter = userPrin;
before the find all. And that should get you the active ones.
PrincipalContext for browsing the AD is ridiculously slow (only use it for .ValidateCredentials, see below), use DirectoryEntry instead and .PropertiesToLoad() so you only pay for what you need.
Filters and syntax here:
https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
Attributes here:
https://learn.microsoft.com/en-us/windows/win32/adschema/attributes-all
using (var root = new DirectoryEntry($"LDAP://{Domain}"))
{
using (var searcher = new DirectorySearcher(root))
{
// looking for a specific user
searcher.Filter = $"(&(objectCategory=person)(objectClass=user)(sAMAccountName={username}))";
// I only care about what groups the user is a memberOf
searcher.PropertiesToLoad.Add("memberOf");
// FYI, non-null results means the user was found
var results = searcher.FindOne();
var properties = results?.Properties;
if (properties?.Contains("memberOf") == true)
{
// ... iterate over all the groups the user is a member of
}
}
}
Clean, simple, fast. No magic, no half-documented calls to .RefreshCache to grab the tokenGroups or to .Bind or .NativeObject in a try/catch to validate credentials.
For authenticating the user:
using (var context = new PrincipalContext(ContextType.Domain))
{
return context.ValidateCredentials(username, password);
}
Certainly the credit goes to #Harvey Kwok here, but I just wanted to add this example because in my case I wanted to get an actual List of UserPrincipals. It's probably more efficient to filter this query upfront, but in my small environment, it's just easier to pull everything and then filter as needed later from my list.
Depending on what you need, you may not need to cast to DirectoryEntry, but some properties are not available from UserPrincipal.
using (var searcher = new PrincipalSearcher(new UserPrincipal(new PrincipalContext(ContextType.Domain, Environment.UserDomainName))))
{
List<UserPrincipal> users = searcher.FindAll().Select(u => (UserPrincipal)u).ToList();
foreach(var u in users)
{
DirectoryEntry d = (DirectoryEntry)u.GetUnderlyingObject();
Console.WriteLine(d.Properties["GivenName"]?.Value?.ToString() + d.Properties["sn"]?.Value?.ToString());
}
}
Include the System.DirectoryServices.dll, then use the code below:
DirectoryEntry directoryEntry = new DirectoryEntry("WinNT://" + Environment.MachineName);
string userNames="Users: ";
foreach (DirectoryEntry child in directoryEntry.Children)
{
if (child.SchemaClassName == "User")
{
userNames += child.Name + Environment.NewLine ;
}
}
MessageBox.Show(userNames);
Research:
Similar Issue with workaround, but not actual solution to existing problem
Similar issue pointing to Microsoft End Point update as culprit
The above links are the most suited to my problem, I have also viewed every similar question listed by Stack Overflow upon creating this post, and only the above referenced questions fit my issue.
Background:
I have been using UserPrincipal.GetAuthorizationGroups for permissions for specific page access running IIS 7.5 on Server 2008 R2 in a C#.NET 4.0 web forms site for 2 and a half years. On May 15 2013 we removed a primary Domain controller running Server 2008 (not r2) and replaced it with a Server 2012 Domain Controller. The next day we started receiving the exception listed below.
I use Principal Context for Forms Authentication. The username/pass handshake succeeds and the auth cookie is properly set, but the subsequent Principal Context call that also calls UserPrincipal.GetAuthorizationGroups fails intermittently. We've resolved a few BPA issues that appeared in the Server 2012 Domain Controller but this has yet to resolve the issue. I also instituted a cron that runs on two separate servers. The two servers will fail at Group SID resolution at different times though they are running the same code base. (A dev environment and production environment).
The issue resolves itself temporarily upon web server reboot, and also on the dev server it will resolve itself after 12 hours of not functioning. The production server will usually stop functioning properly until a reboot without resolving itself.
At this point I am trying to refine the cron targeting specific Domain Controllers in the network as well as the new DC and using the standard LDAP query that is currently failing to yield more targeted exception times. Thus far we've found on one web server that there is no pattern to the days at which it fails, but it will recover within roughly 12 hours. The latest results show Group SID resolution failure between 8AM-8PM then it recovers, several days later it will fail at 8pm and recover at 8am then run fine for another 12 hours and fail again. We are hoping to see if it is just a specific server communication issue or to see if it is the entire set of Domain Controllers.
Exception:
Exception information:
Exception type: PrincipalOperationException
Exception message: An error (1301) occurred while enumerating the groups.
The group's SID could not be resolved.
at System.DirectoryServices.AccountManagement.SidList.TranslateSids(String target, IntPtr[] pSids)
at System.DirectoryServices.AccountManagement.SidList..ctor(SID_AND_ATTR[] sidAndAttr)
at System.DirectoryServices.AccountManagement.AuthZSet..ctor(Byte[] userSid, NetCred credentials, ContextOptions contextOptions, String flatUserAuthority, StoreCtx userStoreCtx, Object userCtxBase)
at System.DirectoryServices.AccountManagement.ADStoreCtx.GetGroupsMemberOfAZ(Principal p)
at System.DirectoryServices.AccountManagement.UserPrincipal.GetAuthorizationGroups()
Question:
Given the above information, does anyone have any idea why decommissioning the Windows Server 2008 (not r2) and implementing a new Server 2012 DC would cause UserPrincipal.GetAuthorizationGroups to fail with the 1301 SID resolution error?
Ideas on eliminating possible causes would also be appreciated.
Disclaimer:
This is my first post to Stack Overflow, I often research here but have not joined in discussions until now. Forgive me if I should have posted elsewhere and feel free to point out better steps before posting.
UPDATE 13-JUN-2013:
On the 12th of June I addressed the possibility of items not disposed causing the issue.
The time frame has been too short to determine if the adjusted code has fixed the issue, but I will continue to update as we work towards a resolution such that maybe with any luck someone here can lend a hand.
Original Code
public bool isGroupMember(string userName, ArrayList groupList)
{
bool valid = false;
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domain_server + ".domain.org:636", null, ContextOptions.Negotiate | ContextOptions.SecureSocketLayer);
// find the user in the identity store
UserPrincipal user =
UserPrincipal.FindByIdentity(
ctx,
userName);
// get the groups for the user principal and
// store the results in a PrincipalSearchResult object
PrincipalSearchResult<Principal> groups =
user.GetAuthorizationGroups();
// display the names of the groups to which the
// user belongs
foreach (Principal group in groups)
{
foreach (string groupName in groupList)
{
if (group.ToString() == groupName)
{
valid = true;
}
}
}
return valid;
}
Updated Code
public bool isGroupMember(string userName, ArrayList groupList, string domain_server)
{
bool valid = false;
try
{
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domain_server + ".domain.org:636", null, ContextOptions.Negotiate | ContextOptions.SecureSocketLayer))
{
// find the user in the identity store
UserPrincipal user =
UserPrincipal.FindByIdentity(
ctx,
userName);
try
{
// get the groups for the user principal and
// store the results in a PrincipalSearchResult object
using (PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups())
{
// display the names of the groups to which the
// user belongs
foreach (Principal group in groups)
{
foreach (string groupName in groupList)
{
if (group.ToString() == groupName)
{
valid = true;
}
}
group.Dispose();
}
}//end using-2
}
catch
{
log_gen("arbitrary info");
return false;
}
}//end using-1
}
catch
{
log_gen("arbitrary info");
return false;
}
return valid;
}
I have just run into this same issue and the info I have managed to track down may be helpful; as above we have seen this problem where the domain controller is running Server 2012 - firstly with a customer deployment and then replicated on our own network.
After some experimentation we found that our code would run fine on Server 2012, but hit the 1301 error code when the client system was running Server 2008. The key information about what was happening was found here:
MS blog translated from German
The hotfix referred to in the link below has fixed the problem on our test system
SID S-1-18-1 and SID S-1-18-2 can't be mapped
Hope this is helpful for someone! As many have noted this method call seems rather fragile and we will probably look at implementing some alternative approach before we hit other issues.
Gary
Here's my solution. It seems to work consistently well. Because the problem happens when iterating over the collection, I use a different approach when iterating in order to handle the exception without blocking the actual iterating:
private string[] GetUserRoles(string Username)
{
List<string> roles = new List<string>();
try
{
string domain = Username.Contains("\\") ? Username.Substring(0, Username.IndexOf("\\")) : string.Empty;
string username = Username.Contains("\\") ? Username.Substring(Username.LastIndexOf("\\") + 1) : Username;
if (!string.IsNullOrEmpty(domain) && !string.IsNullOrEmpty(username))
{
PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, domain);
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, username);
if (user != null)
{
PrincipalSearchResult<Principal> groups = user.GetAuthorizationGroups();
int count = groups.Count();
for (int i = 0; i < count; i++)
{
IEnumerable<Principal> principalCollection = groups.Skip(i).Take(1);
Principal principal = null;
try
{
principal = principalCollection.FirstOrDefault();
}
catch (Exception e)
{
//Error handling...
//Known exception - sometimes AD can't query a particular group, requires server hotfix?
//http://support.microsoft.com/kb/2830145
}
if (principal!=null && principal is GroupPrincipal)
{
GroupPrincipal groupPrincipal = (GroupPrincipal)principal;
if (groupPrincipal != null && !string.IsNullOrEmpty(groupPrincipal.Name))
{
roles.Add(groupPrincipal.Name.Trim());
}
}
}
}
}
}
catch (Exception e)
{
//Error handling...
}
return roles.ToArray();
}
We experienced this issue when our infrastructure team brought a 2012 Domain Controller online. We also had pre-2012 DCs in place and so we experienced the issue intermittently. We came up with a fix which I wanted to share - it has 2 parts.
First of all, install the hotfix mentioned by Gary Hill. This will resolve the following issue:
An error (1301) occurred while enumerating the groups. The group's SID could not be resolved.
We thought we were home free after installing this hotfix. However, after it was installed we got a different intermittent error. Certain groups that we were interrogating had a null sAMAccountName property. The actual property was populated in Active Directory but it was incorrectly being returned with a null value by the API. I presume this is a bug somewhere in the Active Directory API but I don't know any more than that.
Fortunately we were able to work around the issue by switching to use the group Name property instead of the sAMAccountName property. This worked for us. I believe, that sAMAccountName is effectively deprecated and exists only for backwards compatibility reasons. That being the case it seemed a reasonable change to make.
I enclose a cut down version of our GetRolesForUser code to demonstrate the change in place.
using (var context = new PrincipalContext(ContextType.Domain, _domainName))
{
try
{
var p = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, username);
if (p == null) throw new NullReferenceException(string.Format("UserPrincipal.FindByIdentity returned null for user: {0}, this can indicate a problem with one or more of the AD controllers", username));
var groups = p.GetAuthorizationGroups();
var domain = username.Substring(0, username.IndexOf(#"\", StringComparison.InvariantCultureIgnoreCase)).ToLower();
foreach (GroupPrincipal group in groups)
{
if (!string.IsNullOrEmpty(group.Name))
{
var domainGroup = domain + #"\" + group.Name.ToLower();
if (_groupsToUse.Any(x => x.Equals(domainGroup, StringComparison.InvariantCultureIgnoreCase)))
{
// Go through each application role defined and check if the AD domain group is part of it
foreach (string role in roleKeys)
{
string[] roleMembers = new [] { "role1", "role2" };
foreach (string member in roleMembers)
{
// Check if the domain group is part of the role
if (member.ToLower().Contains(domainGroup))
{
// Cache the Application Role (NOT the AD role)
results.Add(role);
}
}
}
}
}
group.Dispose();
}
}
catch (Exception ex)
{
throw new ProviderException("Unable to query Active Directory.", ex);
}
}
Hope that helps.
I experienced error code 1301 with UserPrincipal.GetAuthorizationGroups while using a brand new virtual development domain which contained 2 workstations and 50 users/groups (many of which are the built in ones). We were running Windows Server 2012 R2 Essentials with two Windows 8.1 Enterprise workstations joined to the domain.
I was able to recursively obtain a list of a user's group membership using the following code:
class ADGroupSearch
{
List<String> groupNames;
public ADGroupSearch()
{
this.groupNames = new List<String>();
}
public List<String> GetGroups()
{
return this.groupNames;
}
public void AddGroupName(String groupName)
{
this.groupNames.Add(groupName);
}
public List<String> GetListOfGroupsRecursively(String samAcctName)
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, System.Environment.UserDomainName);
Principal principal = Principal.FindByIdentity(ctx, IdentityType.SamAccountName, samAcctName);
if (principal == null)
{
return GetGroups();
}
else
{
PrincipalSearchResult<Principal> searchResults = principal.GetGroups();
if (searchResults != null)
{
foreach (GroupPrincipal sr in searchResults)
{
if (!this.groupNames.Contains(sr.Name))
{
AddGroupName(sr.Name);
}
Principal p = Principal.FindByIdentity(ctx, IdentityType.SamAccountName, sr.SamAccountName);
try
{
GetMembersForGroup(p);
}
catch (Exception ex)
{
//ignore errors and continue
}
}
}
return GetGroups();
}
}
private void GetMembersForGroup(Principal group)
{
if (group != null && typeof(GroupPrincipal) == group.GetType())
{
GetListOfGroupsRecursively(group.SamAccountName);
}
}
private bool IsGroup(Principal principal)
{
return principal.StructuralObjectClass.ToLower().Equals("group");
}
}
I'm in an environment with multiple domain forests and trusts. I have pretty much this exact same code running on a web site form used to perform user security group lookups across the different domains.
I get this exact error in one of the very large domains where group membership can include 50+ different groups. It works fine in other domains forests.
In my research I found a thread that looks unrelated, but actually has the same stack trace. It is for a remote application running on SBS. The thread mentions that the error is caused by unresolvable SIDS in a group. I believe these would be what are known as "tombstoned" SIDS in active directory. See the thread here.
The thread suggests that finding the tombstoned enteries and removing them from the groups solves the problem. Is it possible the error you are receiving is because SIDS are getting tombstoned every 12 hours by a separate unrelated process? Ultimately, I believe this is a bug in the framework, and that the method should not crash because of tombstoned/unresolvable SIDS.
Good luck!
If anyone is interested this is a VB.NET version of the same code.
Few things you have to do before this code can work
1) You have to reference the assembly System.DirectoryServices
2) Make sure to pass "theusername" variable without the domain, so if your domain is "GIS" and your username is "Hussein" Windows generally authenticate you as GIS\Hussein. So you have to send in just purely the username "Hussein". I worked out the case sensitive stuff.
3) The method GetGroupsNew takes a username and returns a list of groups
4) The method isMemberofnew takes a username and a group and verifies that this user is part of that group or not, this is the one I was interested in.
Private Function getGroupsNew(theusername As String) As List(Of String)
Dim lstGroups As New List(Of String)
Try
Dim allDomains = Forest.GetCurrentForest().Domains.Cast(Of Domain)()
Dim allSearcher = allDomains.[Select](Function(domain)
Dim searcher As New DirectorySearcher(New DirectoryEntry("LDAP://" + domain.Name))
searcher.Filter = [String].Format("(&(&(objectCategory=person)(objectClass=user)(userPrincipalName=*{0}*)))", theusername)
Return searcher
End Function)
Dim directoryEntriesFound = allSearcher.SelectMany(Function(searcher) searcher.FindAll().Cast(Of SearchResult)().[Select](Function(result) result.GetDirectoryEntry()))
Dim memberOf = directoryEntriesFound.[Select](Function(entry)
Using entry
Return New With { _
Key .Name = entry.Name, _
Key .GroupName = DirectCast(entry.Properties("MemberOf").Value, Object()).[Select](Function(obj) obj.ToString()) _
}
End Using
End Function)
For Each user As Object In memberOf
For Each groupName As Object In user.GroupName
lstGroups.Add(groupName)
Next
Next
Return lstGroups
Catch ex As Exception
Throw
End Try
End Function
Private Function isMemberofGroupNew(theusername As String, thegroupname As String) As Boolean
Try
Dim lstGroups As List(Of String) = getGroupsNew(theusername)
For Each sGroup In lstGroups
If sGroup.ToLower.Contains(thegroupname.ToLower) Then Return True
Next
Return False
Catch ex As Exception
Throw
End Try
End Function
we had a similar issue after upgrading the domain controller to 2012. Suddenly my call to user.GetAuthorizationGroups() started failing; I was getting the same exception you were (error 1301). So, I changed it to user.GetGroups(). That worked for a little while, then started failing intermittently on "bad username or password". My latest workaround appears to fix it, for the moment at least. Instead of calling either of those, after constructing the user object, I also construct a group object, one for each group I want to see if the user is a member of. ie, "user.IsMemberOf(group)". That seems to work.
try
{
using (HostingEnvironment.Impersonate())
{
using (var principalContext = new PrincipalContext(ContextType.Domain, "MYDOMAIN"))
{
using (var user = UserPrincipal.FindByIdentity(principalContext, userName))
{
if (user == null)
{
Log.Debug("UserPrincipal.FindByIdentity failed for userName = " + userName + ", thus not authorized!");
isAuthorized = false;
}
if (isAuthorized)
{
firstName = user.GivenName;
lastName = user.Surname;
// so this code started failing:
// var groups = user.GetGroups();
// adGroups.AddRange(from #group in groups where
// #group.Name.ToUpper().Contains("MYSEARCHSTRING") select #group.Name);
// so the following workaround, which calls, instead,
// "user.IsMemberOf(group)",
// appears to work (for now at least). Will monitor for issues.
// test membership in SuperUsers
const string superUsersGroupName = "MyApp-SuperUsers";
using (var superUsers = GroupPrincipal.FindByIdentity(principalContext, superUsersGroupName))
{
if (superUsers != null && user.IsMemberOf(superUsers))
// add to the list of groups this user is a member of
// then do something with it later
adGroups.Add(superUsersGroupName);
}
I had same exception. If someone don't wanna used "LDAP", use this code. Cause I'm had nested groups, I'm used GetMembers(true) and it's little bit longer in time than GetMembers().
https://stackoverflow.com/a/27548271/1857271
or download fix from here: http://support.microsoft.com/kb/2830145
Facing the same problem enumerating authorization groups and the patches noted in the answer did not apply to our web server.
Manually enumerating and ignoring the trouble causing groups is working well, however:
private static bool UserIsMember(string usr, string grp)
{
usr = usr.ToLower();
grp = grp.ToLower();
using (var pc = new PrincipalContext(ContextType.Domain, "DOMAIN_NAME"))
{
using (var user = UserPrincipal.FindByIdentity(pc, usr))
{
var isMember = false;
var authGroups = user?.GetAuthorizationGroups().GetEnumerator();
while (authGroups?.MoveNext() ?? false)
{
try
{
isMember = authGroups.Current.Name.ToLower().Contains(grp);
if (isMember) break;
}
catch
{
// ignored
}
}
authGroups?.Dispose();
return isMember;
}
}
}
I had the problem that if i am connected over VPN and use groups=UserPrincipal.GetGroups() then the Exception occures when iterating over the groups.
If someone want to read all groups of a user there is following possibility (which is faster than using GetGroups())
private IList<string> GetUserGroupsLDAP(string samAccountName)
{
var groupList = new List<string>();
var domainConnection = new DirectoryEntry("LDAP://" + serverName, serverUser, serverUserPassword); // probably you don't need username and password
var samSearcher = new DirectorySearcher();
samSearcher.SearchRoot = domainConnection;
samSearcher.Filter = "(samAccountName=" + samAccountName + ")";
var samResult = samSearcher.FindOne();
if (samResult != null)
{
var theUser = samResult.GetDirectoryEntry();
theUser.RefreshCache(new string[] { "tokenGroups" });
var sidSearcher = new DirectorySearcher();
sidSearcher.SearchRoot = domainConnection;
sidSearcher.PropertiesToLoad.Add("name");
sidSearcher.Filter = CreateFilter(theUser);
foreach (SearchResult result in sidSearcher.FindAll())
{
groupList.Add((string)result.Properties["name"][0]);
}
}
return groupList;
}
private string CreateFilter(DirectoryEntry theUser)
{
string filter = "(|";
foreach (byte[] resultBytes in theUser.Properties["tokenGroups"])
{
var SID = new SecurityIdentifier(resultBytes, 0);
filter += "(objectSid=" + SID.Value + ")";
}
filter += ")";
return filter;
}
I'm trying to recive all computers in my AD and also which of them whos currently logged in. I've tryed doing this by checking the "lastLogonStamp" but that returns the wrong value, saying my server was logged into AD eight days ago. Even if I restart the server it says the same. I got the code from another question here:
How to list all computers and the last time they were logged onto in AD?
public DataTable GetListOfComputers(string domain, string userName, string password)
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain,
userName, password, AuthenticationTypes.Secure);
DirectorySearcher search = new DirectorySearcher(entry);
string query = "(objectclass=computer)";
search.Filter = query;
search.PropertiesToLoad.Add("name");
search.PropertiesToLoad.Add("lastLogonTimestamp");
SearchResultCollection mySearchResultColl = search.FindAll();
DataTable results = new DataTable();
results.Columns.Add("name");
results.Columns.Add("lastLogonTimestamp");
foreach (SearchResult sr in mySearchResultColl)
{
DataRow dr = results.NewRow();
DirectoryEntry de = sr.GetDirectoryEntry();
dr["name"] = de.Properties["Name"].Value;
dr["lastLogonTimestamp"] = DateTime.FromFileTimeUtc(long.Parse(sr.Properties["lastLogonTimestamp"][0].ToString()));
results.Rows.Add(dr);
de.Close();
}
return results;
}
If you're using .NET 3.5 and up, you can use a PrincipalSearcher and a "query-by-example" principal to do your searching:
// create your domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// define a "query-by-example" principal - here, we search for a ComputerPrincipal
ComputerPrincipal qbeComputer = new ComputerPrincipal(ctx);
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeComputer);
// find all matches
foreach(var found in srch.FindAll())
{
// do whatever here - "found" is of type "Principal" - it could be user, group, computer.....
ComputerPrincipal cp = found as ComputerPrincipal;
if(cp != null)
{
string computerName = cp.Name;
DateTime lastLogon = cp.LastLogon;
}
}
If you haven't already - absolutely read the MSDN article Managing Directory Security Principals in the .NET Framework 3.5 which shows nicely how to make the best use of the new features in System.DirectoryServices.AccountManagement. Or see the MSDN documentation on the System.DirectoryServices.AccountManagement namespace.
I access the AD properties thru the below method. It works fine in my Local VHD (where I'm the domain/local/enterprise Admin) - but the same doesn't work when I access from a Domain user(who has only local admin access).
But the same Domain user(only with local admin access) access all the AD property details using the ADExplorer(SysInternal) tools.
Is it because that is unmanaged code and have Windows APIs to access and in .Net I need domain admin or some privilege ?
Or is there another way - which I'm missing in .Net to access the AD Properties without having an extra domain-level-privilege ??
public void getCurrentUserADDetails(string UserName)
{
string ladpQueryStr = "LDAP://sp.com";
DirectoryEntry dirEntry = new DirectoryEntry(ladpQueryStr);
DirectorySearcher srch = new DirectorySearcher(dirEntry);
srch.Filter = "(cn=" + UserName.ToLowerInvariant().Trim() + ")";
srch.PropertiesToLoad.Add("name");
srch.PropertiesToLoad.Add("memberOf");
srch.PropertiesToLoad.Add("prop123");
SearchResult searcResult = srch.FindOne();
if (searcResult != null)
{
ResultPropertyCollection propertiesCollection = searcResult.Properties;
List<DisplayClass> grdDataList = new List<DisplayClass>();
foreach (string strKey in propertiesCollection.PropertyNames)
{
DisplayClass dispC = new DisplayClass();
dispC.pName = strKey;
dispC.pValue = Convert.ToString(propertiesCollection[strKey][0]);
grdDataList.Add(dispC);
}
dataGridView1.DataSource = grdDataList;
}
}
This is going to run in ASP.Net
thanks in advance :)
I assume you're using integrated authentification - in order for this to work you have to setup account delegation, unless you're running your application on a domain controller. This is a pretty tricky process, but there are a ton of info in Google.
By using Explicit authentication and changing the Search filter, i got the results.
DirectoryEntry dirEntry = new DirectoryEntry(path, username, password, AuthenticationType);
How can I get a list of users from active directory? Is there a way to pull username, firstname, lastname? I saw a similar post where this was used:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN");
I have never done anything with active directory so I am completely lost. Any help would be greatly appreciated!
If you are new to Active Directory, I suggest you should understand how Active Directory stores data first.
Active Directory is actually a LDAP server. Objects stored in LDAP server are stored hierarchically. It's very similar to you store your files in your file system. That's why it got the name Directory server and Active Directory
The containers and objects on Active Directory can be specified by a distinguished name. The distinguished name is like this CN=SomeName,CN=SomeDirectory,DC=yourdomain,DC=com. Like a traditional relational database, you can run query against a LDAP server. It's called LDAP query.
There are a number of ways to run a LDAP query in .NET. You can use DirectorySearcher from System.DirectoryServices or SearchRequest from System.DirectoryServices.Protocol.
For your question, since you are asking to find user principal object specifically, I think the most intuitive way is to use PrincipalSearcher from System.DirectoryServices.AccountManagement. You can easily find a lot of different examples from google. Here is a sample that is doing exactly what you are asking for.
using (var context = new PrincipalContext(ContextType.Domain, "yourdomain.com"))
{
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
Console.WriteLine("First Name: " + de.Properties["givenName"].Value);
Console.WriteLine("Last Name : " + de.Properties["sn"].Value);
Console.WriteLine("SAM account name : " + de.Properties["samAccountName"].Value);
Console.WriteLine("User principal name: " + de.Properties["userPrincipalName"].Value);
Console.WriteLine();
}
}
}
Console.ReadLine();
Note that on the AD user object, there are a number of attributes. In particular, givenName will give you the First Name and sn will give you the Last Name. About the user name. I think you meant the user logon name. Note that there are two logon names on AD user object. One is samAccountName, which is also known as pre-Windows 2000 user logon name. userPrincipalName is generally used after Windows 2000.
If you want to filter y active accounts add this to Harvey's code:
UserPrincipal userPrin = new UserPrincipal(context);
userPrin.Enabled = true;
after the first using. Then add
searcher.QueryFilter = userPrin;
before the find all. And that should get you the active ones.
PrincipalContext for browsing the AD is ridiculously slow (only use it for .ValidateCredentials, see below), use DirectoryEntry instead and .PropertiesToLoad() so you only pay for what you need.
Filters and syntax here:
https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
Attributes here:
https://learn.microsoft.com/en-us/windows/win32/adschema/attributes-all
using (var root = new DirectoryEntry($"LDAP://{Domain}"))
{
using (var searcher = new DirectorySearcher(root))
{
// looking for a specific user
searcher.Filter = $"(&(objectCategory=person)(objectClass=user)(sAMAccountName={username}))";
// I only care about what groups the user is a memberOf
searcher.PropertiesToLoad.Add("memberOf");
// FYI, non-null results means the user was found
var results = searcher.FindOne();
var properties = results?.Properties;
if (properties?.Contains("memberOf") == true)
{
// ... iterate over all the groups the user is a member of
}
}
}
Clean, simple, fast. No magic, no half-documented calls to .RefreshCache to grab the tokenGroups or to .Bind or .NativeObject in a try/catch to validate credentials.
For authenticating the user:
using (var context = new PrincipalContext(ContextType.Domain))
{
return context.ValidateCredentials(username, password);
}
Certainly the credit goes to #Harvey Kwok here, but I just wanted to add this example because in my case I wanted to get an actual List of UserPrincipals. It's probably more efficient to filter this query upfront, but in my small environment, it's just easier to pull everything and then filter as needed later from my list.
Depending on what you need, you may not need to cast to DirectoryEntry, but some properties are not available from UserPrincipal.
using (var searcher = new PrincipalSearcher(new UserPrincipal(new PrincipalContext(ContextType.Domain, Environment.UserDomainName))))
{
List<UserPrincipal> users = searcher.FindAll().Select(u => (UserPrincipal)u).ToList();
foreach(var u in users)
{
DirectoryEntry d = (DirectoryEntry)u.GetUnderlyingObject();
Console.WriteLine(d.Properties["GivenName"]?.Value?.ToString() + d.Properties["sn"]?.Value?.ToString());
}
}
Include the System.DirectoryServices.dll, then use the code below:
DirectoryEntry directoryEntry = new DirectoryEntry("WinNT://" + Environment.MachineName);
string userNames="Users: ";
foreach (DirectoryEntry child in directoryEntry.Children)
{
if (child.SchemaClassName == "User")
{
userNames += child.Name + Environment.NewLine ;
}
}
MessageBox.Show(userNames);