I have created the following method in a custom Active Directory RoleProvider:
public override string[] GetRolesForUser(string username)
{
ArrayList results = new ArrayList();
using (var principalContext = new PrincipalContext(
ContextType.Domain, null, domainContainer))
{
var user = UserPrincipal.FindByIdentity(
principalContext, IdentityType.SamAccountName, username);
foreach (string acceptibleGroup in GroupsToInclude)
{
GroupPrincipal adGroup = GroupPrincipal.FindByIdentity(
principalContext, acceptibleGroup);
if (user.IsMemberOf(adGroup))
results.Add(acceptibleGroup);
}
}
return results.ToArray(typeof(string)) as string[];
}
It only checks against a white list of roles which are used in my application. The problem is that if the user is not a member of one of the roles, I get a PrincipalOperationException when the
if (user.IsMemberOf(adGroup))
line is executed. I would expect this to simply return `false if the user is not in the group. What is going wrong here?
EDIT:
As and aside, if I call user.GetAuthorizationGroups() and attempt to loop through the results, I get a COMException - The specified directory service attribute or value does not exist.
Both Principal.IsMemberOf() and user.GetAuthorizationGroups() are using tokenGroups attribute to determine the group membership.
You need to make sure the account you used to run the program is added to Builtin\Windows Authorization Access Group in order to access tokenGroups attribute.
See this MSDN KB for more details.
I have managed to work around this problem with the following:
public override string[] GetRolesForUser(string username)
{
ArrayList results = new ArrayList();
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, null, domainContainer))
{
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, username);
foreach (string acceptibleGroup in GroupsToInclude)
{
GroupPrincipal p = GroupPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, acceptibleGroup);
if (p.GetMembers().Contains(user))
results.Add(acceptibleGroup);
}
}
return results.ToArray(typeof(string)) as string[];
}
However it is not exactly efficient as it pulls all the members of a group back. I am sure there is a better solution to my problem and hopefully someone will post it here!
Related
I am busy with creating a search function into my GUI application who is running on my Windows Server to add, remove, update and search users.
I am almost done building the application, but I can not solve the problem of getting details from an other property which is not given in UserPrincipal like the 'Address' property.
How can I get into that property?
I have tried many coding style to get into the given property 'Address', but it still does not work.
Here is the code:
private void ListOfUsers(String ou)
{
List<string> users = new List<string>();
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "EMRE", "OU=" + ou + ",dc=emre,dc=han");
UserPrincipal qbeUser = new UserPrincipal(ctx);
PrincipalSearcher search = new PrincipalSearcher(qbeUser);
foreach (UserPrincipal user in search.FindAll())
{
users.Add(user.UserPrincipalName);
users.Add("********");
users.Add(user.GivenName);
users.Add(user.Surname);
if (user.GetUnderlyingObjectType() == typeof(DirectoryEntry))
{
using (var entry = (DirectoryEntry)user.GetUnderlyingObject())
{
if (entry.Properties["Address"] != null)
users.Add(entry.Properties["Street"].Value.ToString());
}
}
users.Add(user.VoiceTelephoneNumber);
users.Add(user.EmailAddress);
users.Add(ou);
}
string[] row = users.ToArray();
var listViewItem = new ListViewItem(row);
lstStudents.Items.Add(listViewItem);
}
I always get a null returned even the property is not null
The attribute you want is actually called streetAddress. You can also use Properties.Contains to check if the value exists (although the effect is really the same as checking for null, just easier to read).
if (entry.Properties.Contains("streetAddress"))
users.Add(entry.Properties["streetAddress"].Value.ToString());
Personally, I like using DirectoryEntry/DirectorySearcher directly in general, rather than UserPrincipal/PrincipalSearcher because it gives me more control of what it's doing, which can translate into better performance. I wrote a bit about that here: Active Directory: Better performance
I am rather new to C#. I want to display one set of fields if the user who is logged in is a member of the Administration Board and another set of fields if the user is not a member of the Administration Board.
The manager has instructed me to use Active Directory to do this.
Is there an example someone can point me to that I can review?
Is there a way for me to write a page (just for my own review) that will display all the user groups this organization has in Active Directory?
Here is one way of doing an Active Directory query that gets all domain users, a specific user's groups, and whether a user belongs to a group:
public static List<string> DomainUsers
{
get
{
List<string> users = new List<string>();
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "domain"))
{
// find user by display name
UserPrincipal user = new UserPrincipal(ctx);
PrincipalSearcher search = new PrincipalSearcher(user);
search.FindAll().Cast<UserPrincipal>().ToList().ForEach(u => users.Add(u.SamAccountName));
}
return users;
}
}
/// <summary>
/// Gets all associated group names for current user on the current domain
/// </summary>
/// <returns></returns>
public static List<string> GetGroupNames(string username)
{
var pc = new PrincipalContext(ContextType.Domain, "domain");
var src = UserPrincipal.FindByIdentity(pc, username).GetGroups(pc);
var result = new List<string>();
src.ToList().ForEach(sr => result.Add(sr.SamAccountName));
return result;
}
public static bool UserBelongsToGroup(string group)
{
PrincipalContext pc = new PrincipalContext((Environment.UserDomainName == Environment.MachineName ? ContextType.Machine : ContextType.Domain), Environment.UserDomainName);
GroupPrincipal gp = GroupPrincipal.FindByIdentity(pc, group);
UserPrincipal up = UserPrincipal.FindByIdentity(pc, Environment.UserName);
return up.IsMemberOf(gp);
}
Please note that you will need to use the System.DirectoryServices.AccountManagement namespace which can be found in the .NET 4.0 framework assemblies when adding a reference. You will need to be targetting .NET 4.0 to use this namespace.
Use IsInRole() method. It is in System.Principle namespace - as far as I remember.
I am using System.DirectoryServices.AccountManagement.dll to deal with Active Directory
to get all the users in the "Domain Users" group.
This is returning all the users in the domain but I need to get just the enabled ones.
Here is some sample code:
List<string> users = new List<string>();
PrincipalContext pcContext = GetPrincipalContext();
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcContext,
IdentityType.Name,
"Domain Users");
foreach (Principal user in grp.GetMembers(true).OfType<UserPrincipal>())
{
if (user.Enabled != false)
{
users.Add(user.Name);
}
}
Other groups work fine, but when the group is "Domain Users", the value of the Enabled property is false for all users. This makes it impossible to distinguish between enabled and disabled users without doing a further query for each user.
UserPrinciple objects have a bool Enabled property for this.
http://msdn.microsoft.com/en-us/library/system.directoryservices.accountmanagement.userprincipal_properties.aspx
// Add this to GetUserDetails
objUserDetails.EmployeeId = UserPrinical.EmployeeId;
// Then condition the add to only add enabled
if (objUserDetails.Enabled) {
objUserDetails.Add(GetUserDetails(p.Name));
}
A method around this problem could be to first search for Enabled Users using the PrincipalSearcher class and then use the Principal's method of IsMemberOf()
List<string> users = List<string>();
PrincipalContext pcContext = GetPrincipalContext();
GroupPrincipal grp = GroupPrincipal.FindByIdentity(pcContext, IdentityType.Name, "Domain Users");
UserPrincipal searchFilter = new UserPrincipal(pcContext){ Enabled = true }
PrincipalSearcher searcher = new PrincipalSearcher(searchFilter);
PrincipalSearchResult<Principal> results = searcher.FindAll();
foreach (Principal user in results)
if (user.IsMemberOf(grp))
users.Add(user.SamAccountName);
There's a remark on the MSDN page of the Enabled property saying :
If the principal has not been persisted in the store, this property returns null. After the principal is persisted, the default enabled setting depends on the store. The AD DS and AD LDS stores disable new principals when they are persisted, whereas SAM enables new principals when they are persisted. The application can only set this property to a value after it has been persisted in the store.
Perhaps it's related if the default is false ?
Also, there's a post on the MSDN forum about UserPrincipal.Enabled returns False for accounts that are in fact enabled? and that really sound similar to your issue. According to the post there's perhaps a solution here :
I think I misunderstood. Disregard what I posted before. I think I
know what's happening. The GetMembers method apparently isn't loading
the UserPrincipal data. I don't know if there is a better solution,
but the following works (at least on my AD):
foreach (UserPrincipal user in group.GetMembers(false))
{
UserPrincipal tempUser = UserPrincipal.FindByIdentity(context, user.SamAccountName);
// use tempUser.Enabled
// other code here
}
This is not so much a question as information for anyone experiencing the same problem.
The following error occurs:
System.DirectoryServices.AccountManagement.PrincipalOperationException: An error (87) 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(List`1 sidListByteFormat, String target, NetCred credentials)
at System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.TranslateForeignMembers()
When the following code is run and a group or child group contains a ForeignSecurityPrincipal:
private static void GetUsersFromGroup()
{
var groupDistinguishedName = "CN=IIS_IUSRS,CN=Builtin,DC=Domain,DC=com";
//NB: Exception thrown during iteration of members rather than call to GetMembers.
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "Domain", "Username", "Password"))
{
using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, groupDistinguishedName))
{
using (var searchResults = groupPrincipal.GetMembers(true))//Occurs when false also.
{
foreach (UserPrincipal item in searchResults.OfType())
{
Console.WriteLine("Found user: {0}", item.SamAccountName)
}
}
}
}
}
I raised a support call with Microsoft and they have confirmed it as an issue. A bug has been raised internally but it has not been confirmed whether this will be fixed.
Microsoft suggested the following workaround code but it performs poorly on groups with a large number of users because of the repeated calls to UserPrincipal.FindByIdentity.
class Program
{
//"CN=IIS_IUSRS,CN=Builtin,DC=dev-sp-sandbox,DC=local"; //TODO MODIFY THIS LINE ACCORDING TO YOUR DC CONFIGURATION
static void Main(string[] args)
{
if (args.Length != 1)
{
Console.WriteLine("Usage: ListGroupMembers \"group's DistinguishedName\"");
Console.WriteLine("Example: ListGroupMembers \"CN=IIS_IUSRS,CN=Builtin,DC=MyDomain,DC=local\"");
return;
}
string groupDistinguishedName = args[0];
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "dev-sp-dc", "Administrator", "Corp123!");
List<UserPrincipal> users = new List<UserPrincipal>();
listGroupMembers(groupDistinguishedName, ctx, users);
foreach (UserPrincipal u in users)
{
Console.WriteLine(u.DistinguishedName);
}
}
//Recursively list the group's members which are not Foreign Security Principals
private static void listGroupMembers(string groupDistinguishedName, PrincipalContext ctx, List<UserPrincipal> users)
{
DirectoryEntry group = new DirectoryEntry("LDAP://" + groupDistinguishedName);
foreach (string dn in group.Properties["member"])
{
DirectoryEntry gpMemberEntry = new DirectoryEntry("LDAP://" + dn);
System.DirectoryServices.PropertyCollection userProps = gpMemberEntry.Properties;
object[] objCls = (userProps["objectClass"].Value) as object[];
if (objCls.Contains("group"))
listGroupMembers(userProps["distinguishedName"].Value as string, ctx, users);
if (!objCls.Contains("foreignSecurityPrincipal"))
{
UserPrincipal u = UserPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, dn);
if(u!=null) // u==null for any other types except users
users.Add(u);
}
}
}
}
The above code could be modified to find foreign security principals causing problems in groups.
Microsoft provided the following information about the foreign security principals:
This is a class of objects in AD which represents a security principal from an external source (so another forest/domain or one of the “special” accounts below).
The class is documented here: http://msdn.microsoft.com/en-us/library/cc221858(v=PROT.10).aspx
And the container is documented here : http://msdn.microsoft.com/en-us/library/cc200915(v=PROT.10).aspx
A FSP is not a real object in AD, but rather a placeholder (pointer) to an object which lives in a different, trusted domain/forest. It can also be one of the “special identities” which are a bunch of well-known accounts who are also classed as FSP’s because their SID’s are different to the domain SID.
For example the anonymous, Authenticated User, batch and several other accounts as documented here:
http://technet.microsoft.com/en-us/library/cc779144(v=WS.10).aspx
Sure this is an old thread, but might help someone. I used the below code block the solve the problem. the Principal class exposes a property called StructuralObjectClass which tells you what is the AD Class of that principal. I used this to decide whether the object is a user. The GetMembers(true) recursively searches all nested-members in the groupPrincipal in question.
Hope this helps someone.
List<UserPrincipal> members = new List<UserPrincipal>();
foreach (var principal in groupPrincipal.GetMembers(true))
{
var type = principal.StructuralObjectClass;
if (type.Contains("user"))
members.Add((UserPrincipal)principal);
}
Thanks,
R
The accountmanagement library has many saddening defects, this is just another of the many...
One thing you can do to make things slightly faster would be to adjust your LDAP query so that it checks both group membership and object type at the same time as part of the query instead of in the loop. Honestly I doubt it will make much difference though.
Most of the inspiration for the query came from How to write LDAP query to test if user is member of a group?.
Query: (&(!objectClass=foreignSecurityPrincipal)(memberof=CN=YourGroup,OU=Users,DC=YourDomain,DC=com))
Note: This is an untested query...
IF there was a way to run an LDAP query in AccountManagement (another gripe of mine) then this would be the end of your troubles as you could run the query and let AccountManagement take it from there, but this option does not exist...
Based on personal experience I don't see any other options if you stick with AccountManagement. What you could do is dump AccountManagement and use just DirectoryServices. Under the hood all AccountManagement does is wrap DirectoryEntry objects anyways, you could write a few helper classes to do similar things.
As an alternative, you can use this code to get the members:
var pth = "LDAP://ex.invalid/CN=grpName,OU=Groups,OU=whatever,DC=ex,DC=invalid";
var dirEntry = new DirectoryEntry(pth);
var members = dirEntry.Invoke("Members"); //COM object
foreach (var member in (IEnumerable)members) {
var userEntry = new DirectoryEntry(member); //member is COM object
var sid = new SecurityIdentifier((byte[]) userEntry.InvokeGet("objectSid"), 0);
var typ = typeof(System.Security.Principal.NTAccount);
var account = (NTAccount)sid.Translate(typ);
Console.WriteLine(account.Value);
}
How To Get User group of user from LDAP active directory in C# .NET for ASP. In my Scenario I want to Pass user name to method which query from LDAP Active directory and tell me my user is Member of This User Groups. Please help me in this
If you're on .NET 3.5 or newer, you can also use the new System.DirectoryServices.AccountManagement (S.DS.AM) namespaces.
With this, you can do something like:
// create context for domain
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find the user
UserPrincipal up = UserPrincipal.FindByIdentity(ctx, "YourUserName");
if(up != null)
{
// get groups for that user
var authGroups = up.GetAuthorizationGroups();
}
Read more about the new S.DS.AM namespace:
Managing Directory Security Principals in the .NET Framework 3.5
Look into using the System.DirectoryServices namespace. You can use a DirectorySearcher to find the user. Once you have the DirectoryEntry object for that user do this:
public List<string> GetMemberOf(DirectoryEntry de)
{
List<string> memberof = new List<string>();
foreach (object oMember in de.Properties["memberOf"])
{
memberof.Add(oMember.ToString());
}
return memberof;
}
This will return a list of strings which are the group names the user is a member of.
Of course you could further refine this to include the DirectorySearcher code so you can just pass the function the samAccountName.
try this...
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();
}
Use the DirectorySearcher class to preform an ldap query.
For reference:
http://www.codeproject.com/KB/system/QueryADwithDotNet.aspx
I needed a method of authenticating a user and a check to see if they were in a specific user group. I did it by pushing the username and password and loading the "memberOf" property into the 'search' instance. Example below will display all the groups for that specific user name. The 'catch' statement will trap a wrong user name or password.
DirectoryEntry entry = new DirectoryEntry("LDAP://xxxxxxxx/OU=xxxxxxx,DC=xxxxxx,DC=xxxxx,DC=xxxxxx", strLdapUserName, strLdapPassword);
try
{
//the object is needed to fire off the ldap connection
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + strLdapUserName + ")";
search.PropertiesToLoad.Add("memberOf");
SearchResult result = search.FindOne();
string filterAttribute = (String)result.Properties["cn"][0];
foreach(string groupMemberShipName in result.Properties["memberOf"])
{
Console.WriteLine("Member of - {0}", groupMemberShipName);
}
}
catch (Exception ex)
{
//failed to authenticate
throw new Exception(ex.ToString());
}
Hope this helps. (Remember to reference System.DirectoryServices)
I think most methods listed above should work, but i would suggest adding code to ensure that your code can "detect circular loops in nested group memberships", and if found, break any infinite loops that your script of choice could potentially get into.