I've been searching a lot on the web for Active Directories and windows authentications. I've succeded on getting the User information from the Domain AD but I had to pass the User name AND PASSWORD. So to put you into my context :
I have a Domain where I've set my users. Each Users will be connecting to the domain with their given credentials. So they will log into their PC and when they open a VS 2013 C# application it will check if the users Exists on the Domain if he does then return the AD information if the users doesn't exist then show a Login Page to enter the Credentials. Since I can have external users connecting to my Domain etc ...
right now I cannot access the AD with the user's windows authentication it gives me a Unkown error on the Search.FindOne();
public static void GetActiveDirectoryUser(string UserName)
{
try
{
// Create LDAP connetion object
DirectoryEntry ldapConnection = CreateDirectoryEntry();
// Create Search object which operates on LDAP connection object
// and set search object to only find the user specified
DirectorySearcher search = new DirectorySearcher(ldapConnection);
// Create results objects from search object
SearchResult result = search.FindOne();
if (result != null)
{
// User exists, cycle through LDAP fields (cn, telephonenumber, etc.)
ResultPropertyCollection fields = result.Properties;
foreach (string ldapField in fields.PropertyNames)
{
// Cycle through objects in each field e.g group membership
foreach (Object objCollection in fields[ldapField])
{
Console.WriteLine(String.Format("{0, -20} : {1}", ldapField, objCollection.ToString()));
}
}
}
}
catch (Exception e)
{
Console.WriteLine("Exception Caught:\n\n" + e.ToString());
}
}
static DirectoryEntry CreateDirectoryEntry()
{
string pathDomainName = "WinNT://MyDomain/Fred,Person";
DirectoryEntry ldapConnection = new DirectoryEntry(pathDomainName);
return ldapConnection;
}
This is the error I'm getting
System.Runtime.InteropServices.COMException (0x80005000): Unknown error (0x80005000)
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)
at System.DirectoryServices.DirectorySearcher.FindOne()
but when I use this string
string pathDomainName = "LDAP://MyDomain";
DirectoryEntry directoryEntry = new DirectoryEntry(pathDomainName, "Fred", "f12345!");
it works, it returns me all the AD for the user, but I've already logged in with the windows authentication, why would I pass the credentials again ? I just need to know that if the user exists on the domain that's it
Thanks
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:
Managing Directory Security Principals in the .NET Framework 3.5
MSDN docs on System.DirectoryServices.AccountManagement
Basically, you can define a domain context and easily find users and/or groups in AD:
// set up domain context
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// do something here....
}
// or alternatively: get the currently logged in user
UserPrincipal current = UserPrincipal.Current;
.....
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
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);
I'm trying to create an app to configure a fresh install of AD or reset an AD to default values. This means using the DirectoryServices API.
My plan is to create some OU's, then some Groups (each with their own security poperties - ForeFront and CA is also installed). Then I will create some users and add them to the groups.
I know how to create OU's, groups, and users, and I know how to add users to groups.
But I don't know how to set the security properties of a group or a user.
I found this code, but it's not working for me:
static void SecurityStuff(string groupFQDN,string user)
{
DirectoryEntry directoryEntry = new DirectoryEntry(string.Format("LDAP://{0}",dudu.test.com/cn=batata,ou=Users and Groups,ou=FIM,ou=Local,dc=dudu,dc=test,dc=com),"username","password");
ActiveDirectorySecurity adSecurity = directoryEntry.ObjectSecurity;
string sd = adSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.All);
IdentityReference newidentity = new System.Security.Principal.NTAccount("dudu.test.com",user);
ActiveDirectoryAccessRule newAccessRule = new ActiveDirectoryAccessRule(newidentity, ActiveDirectoryRights.WriteProperty, AccessControlType.Allow);
try
{
directoryEntry.ObjectSecurity.AddAccessRule(newAccessRule);
}
catch (Exception e)
{
Console.WriteLine(e.Message.ToString());
}
directoryEntry.CommitChanges();
}
I get this error from the code:
Some or all identity references could not be translated.
Please point me in the right direction.
Firstly, I am new to AD / LDAP etc so this might be something obvious (I do hope so?!) and apologise if my terminology is not right, please correct me.
We have two domains, BUSINESS (being a Global Group) and AUTH (being a Domain Local Group) with one way trust between them (AUTH trusts BUSINESS).
The following code works on MachineA sitting on BUSINESS domain when string LDAPServer = "BUSINESS".
But when run on MachineB sitting on AUTH domain with string LDAPServer = "AUTH", it shows the message 2f. User Not Found as the user returned in Step 2 is NULL. If I change string LDAPServer = "BUSINESS" then an exception is thrown in Step 1 that the domain controller cannot be found.
As a note the fact that LDAPServiceAccount can be a BUSINESS user shows that AUTH can see BUSINESS. If I change this to LDAPServiceAccount = "BUSINESS\\NotRealName" then Step 1 throws an exception with invalid credentials. This suggests it has resolved the BUSINESS domain user to authenticate the call? If I change LDAPServiceAccount = "AUTH\\ValidAccount" I get the same issue of User == NULL and so 2f. User Not Found.
namespace ConsoleApplication1
{
using System;
using System.DirectoryServices.AccountManagement;
class Program
{
static void Main(string[] args)
{
// Who are we looking for?
string userName = "BUSINESS\\User.Name";
// Where are we looking?
string LDAPServer = "AUTH";
string LDAPServiceAccount = "BUSINESS\\InternalServiceAccountName";
string LDAPServiceAccountPassword = "CorrespondingPassword";
Console.WriteLine("1. Connecting to: " + LDAPServer);
using (PrincipalContext adPrincipalContext = new PrincipalContext(ContextType.Domain, LDAPServer, LDAPServiceAccount, LDAPServiceAccountPassword))
{
Console.WriteLine("2. Finding: " + userName);
using (UserPrincipal user = UserPrincipal.FindByIdentity(adPrincipalContext, userName))
{
if (user == null)
{
Console.WriteLine("2f. User Not Found!");
Console.ReadKey();
return;
}
Console.WriteLine("3. Getting groups...");
using (var groups = user.GetGroups())
{
Console.WriteLine("4. The groups are:");
foreach (Principal group in groups)
{
Console.WriteLine("\t{0}", group.Name);
}
}
}
}
Console.WriteLine("END.");
}
}
}
I am wondering if this is an issue between the two AD Servers where the AUTH is not checking with BUSINESS but checking if the user exists on its configuration? Do I need to setup my LDAPServiceAccount user to have any special permissions? Any pointers would be much appreciated!
I guess you need to check pre-conditions while communicating with the AD server
(1) Your machine should be on the same domain to which you are communicating.
(2) Username and Password which you passed in query should exist on that AD server.
(3) In case you are updating records, then the credential you have passed to the server should have Admin rights.
Please check these points once.
Hope this helps you.
I have a function that needs to log into LDAP as a query user that has read-only access to AD LDAP.
I am able to query and find the user and enumerate most user properties except memberOf.
This only happens when I log in as the read only user. If I log in as the user in question all of the attributes can be retrieved. Any one have any ideas what I am doing wrong?
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(sAMAccountName=" + loginName + ")";
search.PropertiesToLoad.Add("CN");
search.PropertiesToLoad.Add("memberOf");
search.PropertiesToLoad.Add("SN");
search.PropertiesToLoad.Add("givenName");
if (_Attributes != null)
{
foreach (string attr in _Attributes)
{
search.PropertiesToLoad.Add(attr);
}
}
SearchResult result = search.FindOne();
if (result == null)
return null;
string usersName = "";
if (result.Properties.Count > 0)
{
if (result.Properties.Contains("CN"))
{
attributes.Add("CN", result.Properties["CN"].Cast<string>().ToList());
usersName = result.Properties["CN"].Cast<string>().FirstOrDefault();
}
if (result.Properties.Contains("SN"))
{
attributes.Add("SN", result.Properties["SN"].Cast<string>().ToList());
}
if (result.Properties.Contains("givenName"))
{
attributes.Add("givenName", result.Properties["givenName"].Cast<string>.ToList());
}
if (result.Properties.Contains("memberOf"))
ad_MemberOf = result.Properties["memberOf"].Cast<string>().ToList();
}
}
This is a security feature. Access to this attribute is restricted if you don't have the 'Pre-Windows 2000 Compatible Access' enabled.
If you enable this correctly at the root-level of the domain, you should have access to all 'memberOf' attributes of all user accounts, as well as some other attributes that are restricted when you don't have the correct privileges for access.
Alternatively, you could add the permissions to access the specific attributes that correspond to those that are provided as part of the 'Pre-Windows 2000 Compatible Access' mechanism.
If I am reading it correctly, the permissions you need to add are:
'List Contents', 'Read All Properties' and 'Read Permissions'
What is the simplest and most efficient way in C# to check if a Windows user account name exists? This is in a domain environment.
Input: user name in [domain]/[user] format (e.g. "mycompany\bob")
Output: True if the user name exists, false if not.
I did find this article but the examples there are related to authenticating and manipulating user accounts, and they assume you already have a user distinguished name, whereas I am starting with the user account name.
I'm sure I can figure this out using AD, but before I do so I was wondering if there is a simple higher level API that does what I need.
* UPDATE *
There are probably many ways to do this, Russ posted one that could work but I couldn't figure out how to tweak it to work in my environment. I did find a different approach, using the WinNT provider that did the job for me:
public static bool UserInDomain(string username, string domain)
{
string path = String.Format("WinNT://{0}/{1},user", domain, username);
try
{
DirectoryEntry.Exists(path);
return true;
}
catch (Exception)
{
// For WinNT provider DirectoryEntry.Exists throws an exception
// instead of returning false so we need to trap it.
return false;
}
}
P.S.
For those who aren't familiar with the API used above: you need to add a reference to System.DirectoryServices to use it.
The link I found that helped me with this: How Can I Get User Information Using ADSI
The examples use ADSI but can be applied to .NET DirectoryServices as well. They also demonstrate other properties of the user object that may be useful.
The System.DirectoryServices namespace in the article is exactly what you need and intended for this purpose. If I recall correctly, it is a wrapper around the Active Directory Server Interfaces COM interfaces
EDIT:
Something like the following should do it (it could probably do with some checking and handling). It will use the domain of the current security context to find a domain controller, but this could easily be amended to pass in a named server.
public bool UserInDomain(string username, string domain)
{
string LDAPString = string.Empty;
string[] domainComponents = domain.Split('.');
StringBuilder builder = new StringBuilder();
for (int i = 0; i < domainComponents.Length; i++)
{
builder.AppendFormat(",dc={0}", domainComponents[i]);
}
if (builder.Length > 0)
LDAPString = builder.ToString(1, builder.Length - 1);
DirectoryEntry entry = new DirectoryEntry("LDAP://" + LDAPString);
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "sAMAccountName=" + username;
SearchResult result = searcher.FindOne();
return result != null;
}
and tested with the following
Console.WriteLine(UserInDomain("username","MyDomain.com").ToString());
Found a simple way to do this if you're on a high enough framework version:
using System.DirectoryServices.AccountManagement;
bool UserExists(string userName, string domain) {
using (var pc = new PrincipalContext(ContextType.Domain, domain))
using (var p = Principal.FindByIdentity(pc, IdentityType.SamAccountName, userName)) {
return p != null;
}
}