C# LDAP authentication strange issue - c#

I have a VMWare machine with Windows Server 2012 and Active Directory installed. The domain name is "cpx.local" and I have created a new user "testad".
I have a C# Winform application so I can test the connection to the LDAP server and then get all the users or groups in the Active Directory.
This is the code that works fine:
string server = "192.168.238.129";
string port = "389";
System.DirectoryServices.Protocols.LdapConnection ldapConnection =
new System.DirectoryServices.Protocols.LdapConnection(new LdapDirectoryIdentifier(server + ":" + port));
TimeSpan mytimeout = new TimeSpan(0, 0, 0, 1);
try
{
ldapConnection.AuthType = AuthType.Anonymous;
ldapConnection.AutoBind = false;
ldapConnection.Timeout = mytimeout;
ldapConnection.Bind();
Console.WriteLine(("Successfully authenticated to ldap server "));
ldapConnection.Dispose();
}
catch (LdapException ex)
{
Console.WriteLine(("Error with ldap server "));
Console.WriteLine((ex.GetType().ToString() + (":" + ex.Message)));
}
The problem is that if I want to authenticate with the new user "testad" it doesn't work.
I change the AuthType to be Basic and set the credentials.
ldapConnection.AuthType = AuthType.Basic;
ldapConnection.Credential = new NetworkCredential(#"cpx\testad", "test#D12345", "cpx.local");
ldapConnection.AutoBind = false;
ldapConnection.Timeout = mytimeout;
ldapConnection.Bind();
I get the following error:
I have tried to Login the Windows Server 2012 with this user and I can login perfect.
The interesting thing is that the following code is working fine:
var dirEntry = new DirectoryEntry(string.Format("LDAP://{0}/{1}", "192.168.238.129:389", "DC=cpx,DC=local"), "testad", "test#D12345");
var searcher = new DirectorySearcher(dirEntry)
{
Filter = "(&(&(objectClass=user)(objectClass=person)))"
};
var resultCollection = searcher.FindAll();
Am I doing something wrong with the NetworkCredentials?

maybe doubleccheck credentials.in NetworkCredential support username without 'cpx/' in front. as domain is provided
ldapConnection.Credential = new NetworkCredential(#"testad", "test#D12345", "cpx.local");

If you set the AuthType to Negotiate, does it work ?
AuthType details here
change:
ldapConnection.AuthType = AuthType.Basic;
to:
ldapConnection.AuthType = AuthType.Negotiate;
Regarding the domain name - cpx vs cpx.local - you can take a look at this article about some recommended practices
http://www.mdmarra.com/2012/11/why-you-shouldnt-use-local-in-your.html
The correct way to name an Active Directory domain is to create a subdomain that is the delegation of a parent domain that you have registered and have control over. As an example, if I ever started a consulting business and used the Internet-facing website mdmarra.com as my company's site, I should name my Active Directory domain ad.mdmarra.com or internal.mdmarra.com, or something similar. You want to avoid making up a TLD like .local and you also want to avoid the headache of using mdmarra.com for the Internet-facing zone and the internal zone.

Change: ldapConnection.AutoBind= false;
to: ldapConnection.AuthType = true;

Related

Different result with DirectorySearcher

I wrote a small app to check AD group members. When I execute the following code on my pc, It works well, the SearchResult contains the "member" property, however when I run the same exe on the server or on an another computer the "member" property is missing. The usnchanged and usncreated will be different also. I run the exe with the same user on every pc. What can cause this?
...
using (DirectorySearcher searcher = new DirectorySearcher())
{
searcher.CacheResults = false;
searcher.Filter = "(&(objectClass=group)(cn=" + ADName + "))";
searcher.SizeLimit = int.MaxValue;
searcher.PageSize = int.MaxValue;
if (!DirectoryEntry.Exists(ADPath))
{
return null;
}
searcher.SearchRoot = new DirectoryEntry(ADPath);
using (SearchResultCollection collection = searcher.FindAll())
{
if (collection.Count == 1)
{
return collection[0];
}
}
}
...
The group membership data is not replicated to the global catalog. The query might work sometimes, if you happen to connect to the domain controller with the actual membership data. On other machines, you probably connect to other domain controllers, of different domains, where the information is not available.
You might want to connect to a domain controller in the actual domain, not to the global catalog.

Active Directory not working for offsite

Technology used: asp.net(C#), MVC, LINQ, Entity
I use the Active Directory for our company website. This system works for all of our employees on site and when we take our laptop offsite and use VPN.
However we recently hired some developers(offsite) who we have given VPN access to. But they are unable to run our code and I am unsure why. We have active directory users set up for them and they can connect to us through VPN.
They are running into two errors.
A username/password error.
The Server is not Operational error.
Does the VPN username/password have to match the active directory account?
If they log into their development machine does that username/password have to match the active directory account?
Is there some restriction on Active Directory and offsite I am unaware of as to why this would work for us but not our off site developers?
Below is the section of code that is giving the error: It errors on the line SearchResult rs = ds.FindOne();
public AD_CurrentUserProfile GetUserProfile(string userName)
{
using (DirectoryEntry de = new DirectoryEntry("LDAP://server.com"))
using (DirectorySearcher ds = new DirectorySearcher(de))
{
ds.SearchScope = SearchScope.Subtree;
ds.Filter = ("(&(objectCategory=person)(objectClass=User)(samaccountname=" + userName + "))");
ds.PropertiesToLoad.Add("distinguishedName");
ds.PropertiesToLoad.Add("manager");
ds.PropertiesToLoad.Add("directreports");
SearchResult rs = ds.FindOne();
AD_CurrentUserProfile userProfile = new AD_CurrentUserProfile();
userProfile.currentUser = GetProfileFromDN(rs.Properties["distinguishedName"][0].ToString());
userProfile.managerProfile = GetProfileFromDN(rs.Properties["manager"][0].ToString(), true);
int departmentID = db.IPACS_Department.First(v => (v.name == userProfile.currentUser.department)).departmentID;
userProfile.ipacs_department = db.IPACS_Department.Find(departmentID);
if (userProfile.currentUser.userName == userProfile.ipacs_department.owner)
{
userProfile.currentUser.isManager = true;
}
// Override for Provana and upper management
if (userProfile.currentUser.department == "Provana" || userProfile.currentUser.department == "JM")
{
userProfile.currentUser.isManager = true;
}
if (rs.Properties["DirectReports"].Count > 0)
{
if (!userProfile.currentUser.isManager)
{
userProfile.currentUser.isSupervisor = true;
}
userProfile.directReports = new HashSet<AD_User>();
foreach (string value in rs.Properties["DirectReports"])
{
userProfile.directReports.Add(GetProfileFromDN(value));
}
}
return userProfile;
}
}
I never pass 'password' anywhere in any of my code, so is this something Active Directory does by default?
A connection to AD will always require windows credentials. Your code, as posted, does not supply any credentials to AD. (You pass in a user name that you are looking up, but that is not the same as supplying credentials for the connection). This will work for users whose machines are attached to the domain...because your network credentials are passed in implicitly. For the external devs, when they VPN in, they supply credentials to the VPN protocol, which allows their machines to access your network, but that doesn't mean their machines are 'joined' to the domain...so AD will still require explicit credentials from them, including a personal password or a service account password that has permissions to access AD.
This line:
using (DirectoryEntry de = new DirectoryEntry("LDAP://server.com"))
essentially needs to allow a user name and password:
using (DirectoryEntry de = new DirectoryEntry("LDAP://server.com"), userName, pwd)
{
de.AuthenticationType = AuthenticationTypes.None | AuthenticationTypes.ReadonlyServer;
}
...you'll have to experiment with the AuthenticationTypes flags depending on how your network admins have it set up.

Connect to Active Directory via LDAP

I want to connect to our local Active Directory with C#.
I've found this good documentation.
But I really don't get how to connect via LDAP.
Can somebody of you explain how to use the asked parameters?
Sample Code:
static DirectoryEntry createDirectoryEntry()
{
// create and return new LDAP connection with desired settings
DirectoryEntry ldapConnection = new DirectoryEntry("rizzo.leeds-art.ac.uk");
ldapConnection.Path = "LDAP://OU=staffusers,DC=leeds-art,DC=ac,DC=uk";
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
return ldapConnection;
}
I just have the Hostname and the IP Address of our Active Directory Server. What does DC=xxx,DC=xx and so on mean?
DC is your domain. If you want to connect to the domain example.com than your dc's are: DC=example,DC=com
You actually don't need any hostname or ip address of your domain controller (There could be plenty of them).
Just imagine that you're connecting to the domain itself. So for connecting to the domain example.com you can simply write
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://example.com");
And you're done.
You can also specify a user and a password used to connect:
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://example.com", "username", "password");
Also be sure to always write LDAP in upper case. I had some trouble and strange exceptions until I read somewhere that I should try to write it in upper case and that solved my problems.
The directoryEntry.Path Property allows you to dive deeper into your domain. So if you want to search a user in a specific OU (Organizational Unit) you can set it there.
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://example.com");
directoryEntry.Path = "LDAP://OU=Specific Users,OU=All Users,OU=Users,DC=example,DC=com";
This would match the following AD hierarchy:
com
example
Users
All Users
Specific Users
Simply write the hierarchy from deepest to highest.
Now you can do plenty of things
For example search a user by account name and get the user's surname:
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://example.com");
DirectorySearcher searcher = new DirectorySearcher(directoryEntry) {
PageSize = int.MaxValue,
Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=AnAccountName))"
};
searcher.PropertiesToLoad.Add("sn");
var result = searcher.FindOne();
if (result == null) {
return; // Or whatever you need to do in this case
}
string surname;
if (result.Properties.Contains("sn")) {
surname = result.Properties["sn"][0].ToString();
}
ldapConnection is the server adres: ldap.example.com
Ldap.Connection.Path is the path inside the ADS that you like to use insert in LDAP format.
OU=Your_OU,OU=other_ou,dc=example,dc=com
You start at the deepest OU working back to the root of the AD, then add dc=X for every domain section until you have everything including the top level domain
Now i miss a parameter to authenticate, this works the same as the path for the username
CN=username,OU=users,DC=example,DC=com
Introduction to LDAP
If your email address is 'myname#mydomain.com', try changing the createDirectoryEntry() as below.
XYZ is an optional parameter if it exists in mydomain directory
static DirectoryEntry createDirectoryEntry()
{
// create and return new LDAP connection with desired settings
DirectoryEntry ldapConnection = new DirectoryEntry("myname.mydomain.com");
ldapConnection.Path = "LDAP://OU=Users, OU=XYZ,DC=mydomain,DC=com";
ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
return ldapConnection;
}
This will basically check for com -> mydomain -> XYZ -> Users -> abcd
The main function looks as below:
try
{
username = "Firstname LastName"
DirectoryEntry myLdapConnection = createDirectoryEntry();
DirectorySearcher search = new DirectorySearcher(myLdapConnection);
search.Filter = "(cn=" + username + ")";
....

ActiveDirectory Local Machine Account management - C#

I posted a question re LDAP account management, but after exploring this, it's not what i'm after. I've managed to find two ways of creating users on a machine, and i find one is much neater than the other, however, i am uncertain how to convert the first option over to the second option entirely.
This was my first solution:
Process MyProc = new Process();
MyProc.StartInfo.WorkingDirectory = System.Environment.SystemDirectory;
MyProc.StartInfo.FileName = "net.exe";
MyProc.StartInfo.UseShellExecute = false;
MyProc.StartInfo.RedirectStandardError = true;
MyProc.StartInfo.RedirectStandardInput = true;
MyProc.StartInfo.RedirectStandardOutput = true;
MyProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
MyProc.StartInfo.Arguments = string.Format(#" user {0} {1} /ADD /ACTIVE:YES /EXPIRES:NEVER /FULLNAME:{0}"" /PASSWORDCHG:NO /PASSWORDREQ:YES", username, password);
MyProc.Start();
MyProc.WaitForExit();
int exit = MyProc.ExitCode;
MyProc.Close();
return exit == 0;
And this was my second (preffered) solution:
DirectoryEntry root = GetDELocalRoot();
DirectoryEntry user = root.Children.Add(username, "user");
//TODO: Always Active
//TODO: Never Expires
//TODO: No Password Change
//TODO: Password Required
user.Properties["description"].Value = "Account for running the MicaService and handling updates.";
user.Invoke("SetPassword", new object[] { password });
user.CommitChanges();
user.Close();
I would like to map all the settings in my TODO: from the first solution into my second neater solution.
I have tried the following line as well:
user.Properties["userAccountControl"].Value = ADS_USER_FLAG.ADS_UF_NORMAL_ACCOUNT | ADS_USER_FLAG.ADS_UF_PASSWD_CANT_CHANGE | ADS_USER_FLAG.ADS_UF_DONT_EXPIRE_PASSWD;
But this does not work as the property does not exist in cache.
NOTE: the GetDELocalRoot() = return new DirectoryEntry("WinNT://" + Environment.MachineName);
Thanks for any input!
Regards
Tris
Check out my friend Richard Mueller's web site which has lots of useful information and reference material on what those two providers - WinNT for local machine accounts vs. LDAP for network accounts - have to offer.
There's also a Excel sheeet with all attributes that the WinNT provider exposes - it's a lot less than what the LDAP provider has, so I'm not sure if you'll be able to set all the properties you're looking for.
Marc

Generic Authentication Call to Active Directory in C#

I would like to have a clean C# class that authenticates from Active Directory.
It should be pretty simple, it just has to ask for credentials and check if it matches what AD is expecting.
I am responsible for a number of C# applications, and I would like all of them to use the same class.
Could someone please provide a clean code sample of such a class? It should have good error handling, be well commented, and specifically ask for credentials rather than try to read if a user is already logged in to AD for another application. (This is a security requirement because some applications are used in areas with shared computers: People with multiple roles and different permission levels may use the same computer and forget to log out between sessions)
http://support.microsoft.com/kb/316748
public bool IsAuthenticated(String domain, String username, String pwd)
{
String domainAndUsername = domain + "\\" + username;
DirectoryEntry entry = new DirectoryEntry(_path, domainAndUsername, pwd);
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();
if(null == result)
{
return false;
}
//Update the new path to the user in the directory.
_path = result.Path;
_filterAttribute = (String)result.Properties["cn"][0];
}
catch (Exception ex)
{
throw new Exception("Error authenticating user. " + ex.Message);
}
return true;
}
Admittedly I have no experience programming against AD, but the link below seems it might address your problem.
http://www.codeproject.com/KB/system/everythingInAD.aspx#35
There's some reason you can't use Windows integrated authentication, and not bother users with entering their names and passwords? That's simultaneously the most usable and secure solution when possible.

Categories