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.
Related
On Windows Server 2012 R2 we want to get the Security Groups of an user from the Active Directory by C#-code. The application is an ASP.NET MVC5-project and is hosted by an IIS.
At first, we query the user account from the Active Directory by UserPrincipal.FindByIdentity(...). That works fine. At next, we use the WindowsIdentity class to query the Security Groups from the active directory. We use the class WindowsIdentity because the response is very fast.
Here is the code:
var lDomain = "MyDomain";
var lSamAccountName = "Simon";
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, lDomain))
{
// Get User Account from Active Directory
using (UserPrincipal lUserPrincipal = UserPrincipal.FindByIdentity(ctx, IdentityType.SamAccountName, lSamAccountName))
{
var lUpn = lUserPrincipal.UserPrincipalName; // get UPN
// get Security Groups of the user
using (WindowsIdentity lWindowsIdentity = new WindowsIdentity(lUpn)) // Exception: System.Security.SecurityException: The user name or password is incorrect
{
var lGroups = lWindowsIdentity.Groups;
}
}
}
The problem: When instanciating the WindowsIdentity class (and passing the UPN) then an exception occurs:
“Upn: 'simon#MyDomain' Exception: System.Security.SecurityException: The user name or password is incorrect.
at System.Security.Principal.WindowsIdentity.KerbS4ULogon(String upn, SafeAccessTokenHandle& safeTokenHandle)
at System.Security.Principal.WindowsIdentity..ctor(String sUserPrincipalName, String type)
at System.Security.Principal.WindowsIdentity..ctor(String sUserPrincipalName)
at ...
This is curious because the query with UserPrincipal.FindByIdentity(...) was successful. Some accounts are working. Some accounts are not working. I cannot find a difference between the working und not working accounts.
Question: Who knows what I am doing wrong?
Additional Notes:
The Applicationpool Identity is: LocalSystem
In the Web.config, there is the following entry: <identity impersonate="false" />
When I query the groups by the following alternative, then no exception occurs and the result is satisfying. That´s curious:
var lResult = new List<SecurityIdentifier>();
DirectorySearcher lDirectorySearcher = new DirectorySearcher();
lDirectorySearcher.Filter = string.Format(CultureInfo.InvariantCulture, "(&(objectClass=user)(distinguishedName={0}))", lUserPrincipal.DistinguishedName); // for the object lUserPrincipal, look # code above
lDirectorySearcher.SearchScope = SearchScope.Subtree;
SearchResult lSearchResult = lDirectorySearcher.FindOne();
DirectoryEntry lDirectoryEntry = lSearchResult.GetDirectoryEntry();
lDirectoryEntry.RefreshCache(new string[] { "tokenGroups" });
// get groups
for (int i = 0; i < lDirectoryEntry.Properties["tokenGroups"].Count; i++)
{
SecurityIdentifier lSid = new SecurityIdentifier((byte[])lDirectoryEntry.Properties["tokenGroups"][i], 0);
lResult.Add(lSid);
// Translate into NTAccount to get Domain and SAMAccountname etc...
[...]
}
I really do not know if this helps, but somewhere I found this:
If IIS is set to allow anonymous access you can use the userPrincipleName format (username#domain).
If anonymouse access is NOT enabled in IIS you need to use the username in the form domain\username.
Seems weird though that for some accounts the UPN works and for others it does not..
Resolution 1:
Use ADSI Edit (via MMC.exe) to manually populate the userPrincipalName attribute of the desired Windows Service Account.
The format of a userPrincipalName is
<username>#<fully_qualified_domain_name>
For example, for the example log lines above, the expected UPN value should be
goodadmin#example.com
Allow a few minutes for Active Directory replication to complete before attempting to use the service account credentials.
Resolution 2:
Ensure the Active Directory Service Account information has the necessary permissions on the local BEMS host as Local Administrator and Logon As A Service.
Ensure the Active Directory Service Account information entered is actually valid. For example, ensure the password is entered correctly and that the account is not locked out in Active Directory.
Ensure the Active Directory Service Account information has the necessary Active Directory Group Permissions.
RTCUniversalReadOnlyAdmins
Here you can find how to get groups base on the UserPrincipal
How to create WindowsIdentity/WindowsPrincipal from username in DOMAIN\user format
// find all groups the user is member of (the check is recursive).
// Guid != null check is intended to remove all built-in objects that are not really AD gorups.
// the Sid.Translate method gets the DOMAIN\Group name format.
var userIsMemberOf = p.GetAuthorizationGroups().Where(o => o.Guid != null).Select(o => o.Sid.Translate(typeof(NTAccount)).ToString());
// use a HashSet to find the group the user is member of.
var groups = new HashSet<string>(userIsMemberOf), StringComparer.OrdinalIgnoreCase);
groups.IntersectWith(groupNames);
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;
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.
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 + ")";
....
I built a test Active Directory server in Window 2008 and I also run the DNS server on it. On my client machine which runs the C# application, I can authenticate the user against the Active directory server using the function below:
public static UserPrincipal GetUserPrincipal(string usrName,string pswd,string domainName)
{
UserPrincipal usr;
PrincipalContext ad;
// Enter Active Directory settings
ad = new PrincipalContext(ContextType.Domain, domainName,usrName,pswd);
//search user
usr = new UserPrincipal(ad);
usr.SamAccountName = usrName;
PrincipalSearcher search = new PrincipalSearcher(usr);
usr = (UserPrincipal)search.FindOne();
search.Dispose();
return usr;
}
In a separate logic I tried to retrieve a user back from the server using a user name. I used the functions below:
public static DirectoryEntry CreateDirectoryEntry()
{
// create AD connection
DirectoryEntry de = new DirectoryEntry("LDAP://CN=Users,DC=rootforest,DC=com","LDAP","password");
de.AuthenticationType = AuthenticationTypes.Secure;
return de;
}
public static ResultPropertyCollection GetUserProperty(string domainName, string usrName)
{
DirectoryEntry de = CreateDirectoryEntry();
DirectorySearcher deSearch = new DirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(SamAccountName=" + usrName + ")";
SearchResult results = deSearch.FindOne();
return null;
}
However, I got no response back from the LDAP server at all, not even an exception. Am I missing certain settings on LDAP server, any of you able to see a flaw in my code (pls don't mind the hard code values, I was testing with this code).
As part of my troubleshooting, I confirmed that I can ping to the rootforest.com from the client machine. I confirmed the user with property samaccountname "LDAP" exists. My path seems to be right because when I go onto the LDAP server and type :
dsquery user -name LDAP*
I got the following:
CN=LDAP L. LDAP,CN=Users,DC=rootforest,DC=com
Any help would be greatly appreciated, I've spent most of my day troubleshooting and researching this little bugger and I think it could be something small which I overlooked.
I don't understand why you're using the new PrincipalContext / UserPrincipal stuff in your first example, but fall back to the hard to use DirectoryEntry stuff in your second example.... doesn't really make sense... also: your second function GetUserProperty seems to return null always - typo or not??
Since you're on already using the System.DirectoryServices.AccountManagement (S.DS.AM) namespace - use it for your second task, too! 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:
public static ????? GetUserProperty(string domainName, string usrName)
{
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, usrName);
if(user != null)
{
// return what you need to return from your user principal here
}
else
{
return null;
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD:
I think your code have a few problems:
Why do you return null in your GetUserProperty() function? You should return results instead.
The attribute you are using in your search filter is misspelled. Use sSAMAccountName instead. Furthermore extend your query to search only for user accounts. Here is an example: (&(objectCategory=person)(objectClass=user)(sAMAccountName=usrName))
You could also use the UserPrincipal class to search for an identity in Active Directory. The UserPrincipal class provides a static method called FindByIdentity() to search for a user identity.
Hope, this helps.