I'm running an ASP.NET 4.0 app which uses the user name (i.e. HttpContext.Current.Request.LogonUserIdentity.Name.ToString()) to manage access to various components.
The user name being returned is in the form "abc\jsmith" where "abc" is the domain name and "jsmith" is the login name of the user.
Part of the security module for this app accesses the Active Directory groups that the user belongs to (e.g., "Accounting", "AccountsPayable", "AdminDepartment"). I'm able to get the user's name from Active Directory using the DirectoryEntry.Properties (i.e., System.DirectoryServices.PropertyCollection") "sAMAccountName".Value.
So far, everything is fine, but I want to be able to expand the app across multiple domains, which mean I need to be able to find the domain name in Active Directory as well as the user's Login Name. I can get a "Domain" value from PrincipalContext, but it's returning "abcdc", instead of "abc". Can I assume that this property will always return "dc" (as in "Domain Controller") at the end of each domain (in which case I can use a Substring of the property), or is there somewhere else I can get the user's current domain name?
One thing I am unclear on is your question about retrieving the domain name given a directoryentry in a domain controller. I am assuming that you have a server that can see multiple trusted domains, and that a user can log into your application from any one of them such that you don't know against what domain you need to test role membership.
For controlling access to features via ADGroup membership, could you use the
HttpContext.Current.User.IsInRole("appdomain\groupname")
where User.Identity.Name=="userdomain\user". I'm not familiar with domain trust issues, but this assumes that you can add users from the trusted domain into the domain group that you control so you don't need to worry about the group domain location.
If you can't, or if you have the same group name in each different domain, then you could do something like this?
HttpContext.Current.User.IsInRole(userDomainname + "\groupname")
Some points:
unless you already have a large established AD codebase, I would recommend using objects from the System.DirectoryServices.AccountManagement namespace.
I highly recommend the ADExplorer utility from Sysinternals to get a more LDAP view of your domain(s) which helps with LDAP connection strings and directory programming in general.
If you are comfortable with interop, and need to perform any LDAP connection string parsing, check out this site.
The System.DirectoryServices.AccountManagement.PrincipalContext.Container and System.DirectoryServices.DirectoryEntry.Path properties return the LDAP connection string w/ the domain at the end of the string (i.e., DC=mycompany,DC=com)
Don't forget about trusty old Environment.UserDomainName & Environment.UserName (which grabs the WindowsPrincipal from the currently executing thread; see Table 1: Thread Exposed CurrentPrincipal Object # http://msdn.microsoft.com/en-us/library/Aa480475.aspx for a great table on what the current user is within the asp.net runtime. )
** UPDATE 6/8/2011 2:15 PM**
If I understand AD correctly, the user's domain is an integral part of the user object returned by AD. Expanding on your example of "Bob Newaccountant"...
So given the following 2 Domains with a trust between them:
1. "abcdc.com"
CN=Users
CN="Bob NewAccountant"
2. "abc.com"
CN=Users
CN="Local User1"
OU=Applications
OU=MyApplication
CN=ReportReaders (Members: abcdc\BNewAccountant, abc\luser1)
You should get the users' info given the following query:
//name parameter = domain
//container parameter = distinguished name
using(var ctx = new PrincipalContext(
ContextType.Domain,
name: "abc.com",
container: "OU=MyApplication,OU=Applications,DC=abc,DC=com",
"abc\serviceaccountname",
"Password1"))
{
var officeGroup = GroupPrincipal.FindByIdentity(ctx,
IdentityType.SamAccountName,
"ReportReaders");
foreach(Principal prin in officeGroup.GetMembers(recursive: true))
{
Console.WriteLine("DistinguishedName: " + prin.DistinguishedName
+ " UPN: " + prin.UserPrincipalName);
}
//Should result in
// DistinguishedName: CN=luser1,CN=Users,DC=abc,DC=com UPN: luser1#abc.com
// DistinguishedName: CN=BNewAccountant,CN=Users,DC=abcdc,DC=com UPN: BNewAccountant#abcdc.com
}
So you should be able to get the user's domain via distinguishedName or userPrincipalName properties of active directory. (Note: I don't have a dual domain setup handy to me so I am not able to test the above code at this time.) Is that getting closer?
Here is the WMI way to find it. I give you the PowerShell writting, but you can transform it for VBScript or C# easily
PS C:\> (Get-WmiObject Win32_NTDomain).DomainName
Be careful, the Domain part of the 'pre windows 2000 domain' can be completly different from the user Principal Name (user#domain) use to logon onto Active-Directory. the DOMAIN is the Primary Domain Controleur name or the Netbios domain name. DOMAIN is created during domain creation, by default it's part of the DNS name, but it can be completly changed during domain creation.
You can find it with nETBIOSName attribute :
ldifde -f netbios.ldf -d "CN=Partitions,CN=Configuration,DC=your-DNS-Name" -r "(netbiosname=*)"
Edited
Here is the CSharp code
ManagementObjectSearcher domainInfos1 = new ManagementObjectSearcher("select * from WIN32_NTDomain");
foreach (ManagementObject domainInfo in domainInfos1.Get())
{
Console.WriteLine("Name : {0}", domainInfo.GetPropertyValue("Name"));
Console.WriteLine("Computer/domain : {0}", domainInfo.GetPropertyValue("Caption"));
Console.WriteLine("Domain name : {0}", domainInfo.GetPropertyValue("DomainName"));
Console.WriteLine("Status : {0}", domainInfo.GetPropertyValue("Status"));
}
DC stands for domain component. A seemingly decent rundown for programming with Active Directory is here. There's a bit much over there to do a decent copy and paste here, but I did find the following which may help you:
domainname=inputbox("Enter DNS Domain Name" & vbcrlf & "(Leave blank for current domain):")
username=inputbox("Enter username:")
IF domainname = "" THEN
SET objRoot = GETOBJECT("LDAP://RootDSE")
domainname = objRoot.GET("defaultNamingContext")
END IF
IF username <> "" THEN
wscript.echo finduser(username,domainname)
END IF
FUNCTION FindUser(BYVAL UserName, BYVAL Domain)
ON ERROR RESUME NEXT
SET cn = CREATEOBJECT("ADODB.Connection")
SET cmd = CREATEOBJECT("ADODB.Command")
SET rs = CREATEOBJECT("ADODB.Recordset")
cn.open "Provider=ADsDSOObject;"
cmd.activeconnection=cn
cmd.commandtext="SELECT ADsPath FROM 'LDAP://" & Domain & _
"' WHERE sAMAccountName = '" & UserName & "'"
SET rs = cmd.EXECUTE
IF err<>0 THEN
FindUser="Error connecting to Active Directory Database:" & err.description
ELSE
IF NOT rs.BOF AND NOT rs.EOF THEN
rs.MoveFirst
FindUser = rs(0)
ELSE
FindUser = "Not Found"
END IF
END IF
cn.close
END FUNCTION
You could get the users "userprincipalname". It looks like an email address but is actually the samaccountname + # + domain.name. one thing to note is that the Active Directory domain looks like an internet Domain name, but the netbios domain name (the 'abc' from your example) does not.
If you grab the UPN, I believe it will always contain the dotted domain name.
Related
I have written code using JNDI for creating users using DirContext in AD.
After I create the user I am not able to login with those credentials. When I manually reset the password for that user in AD, I am able to login.
Here I have placed my code for your reference,
Hashtable<String, String> ldapenv = new Hashtable<>();
ldapenv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
ldapenv.put(Context.PROVIDER_URL, "ldap://10.95.144.139:389");
ldapenv.put(Context.SECURITY_AUTHENTICATION, "simple");
ldapenv.put(Context.SECURITY_PRINCIPAL, "CN=Administrator,CN=Users,dc=Merck,dc=local");
ldapenv.put(Context.SECURITY_CREDENTIALS, "Merck2017");
DirContext context = new InitialDirContext(ldapenv);
Attributes attributes = new BasicAttributes();
// Create the objectclass to add
Attribute objClasses = new BasicAttribute("objectClass");
objClasses.add("top");
objClasses.add("person");
objClasses.add("organizationalPerson");
objClasses.add("user");
// Assign the username, first name, and last name
String cnValue = new StringBuffer(user.getFirstName()).append(" ").append(user.getLastName()).toString();
Attribute cn = new BasicAttribute("cn", cnValue);
Attribute sAMAccountName = new BasicAttribute("sAMAccountName", user.getUserName());
Attribute principalName = new BasicAttribute("userPrincipalName", user.getUserName()
+ "#" + "merck.local");
Attribute givenName = new BasicAttribute("givenName", user.getFirstName());
Attribute sn = new BasicAttribute("sn", user.getLastName());
Attribute uid = new BasicAttribute("uid", user.getUserName());
// Add password
Attribute userPassword = new BasicAttribute("userPassword", user.getPassword());
Attribute pwdAge = new BasicAttribute("pwdLastSet","-1");
Attribute userAccountControl = new BasicAttribute("userAccountControl", "544");
// Add these to the container
attributes.put(objClasses);
attributes.put(sAMAccountName);
attributes.put(principalName);
attributes.put(cn);
attributes.put(sn);
attributes.put(givenName);
attributes.put(uid);
attributes.put(userPassword);
attributes.put(userAccountControl);
attributes.put(pwdAge);
// Create the entry
try {
context.createSubcontext(getUserDN(cnValue,"Merck-Users"), attributes);
System.out.println("success === ");
} catch (Exception e) {
System.out.println("Error --- "+e.getMessage());
}
Please help me resolve the following issues:
How do I set AD user password while creating the user using the above code?
How do I set userAccountControl to 66048 in the above code?
How do I create the user enabled while using the above code?
How do I disable the option "user must change the password in next login" while creating the user in the above code?
Thanks in advance.
I don't have all the answers, but this should get you started:
Passwords can only be set over a secure channel, like LDAPS (LDAP over SSL). Since you are connecting to port 389, that is not SSL and AD won't let you set the password. You must connect to the LDAPS port: 636. You may run into issues trusting the SSL certificate. I can't help much here since I'm not a Java developer, but there is an example here.
The answer to your second and third questions is the same: Accounts with no passwords are always disabled. Since you haven't set the password properly, the account will be disabled. Once you figure out how to set the password, you can also set userAccountControl to whatever you need.
You are disabling the "user must change password" option correctly: by setting pwdLastSet to -1. That's the right way to do it. But you may have to fix the other issues first.
Another important thing: I have created AD accounts in .NET, and I have found that I had to create the account first, then go back and set the password and set the userAccountControl attribute after. You may have to do the same.
We have problem like, unable to get the user full name when reading from different domain.
eg: My userName is dom1\jsmith and full name is John Smith. When we deploy the project in dom1 domain, we are able to login and get the full name of the user. When we deploy the project in another domain(dom2) where the user(dom1\jsmith) has login permission, able to access the site but not able to get the full name.
We tried different solutions but didn't work.
//output: dom1\jsmith
User.Identity.Name;
//output: dom1\jsmith
string s = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
//output: dom1\jsmith
string sUserName = System.Environment.UserName;
//output: John Smith in same domain but not able to find identity
using (var context = new PrincipalContext(ContextType.Domain))
{
var principal = UserPrincipal.FindByIdentity(context, User.Identity.Name);
if (principal != null)
var fullName = string.Format("{0} {1}", principal.GivenName, principal.Surname);
Pass the name of the logon domain into the constructor for PrincipalContext. So split the DOMAIN\username that you have, and use just the domain portion:
var split = User.Identity.Name.Split('\\');
using (var context = new PrincipalContext(ContextType.Domain, split[0])) {
...
}
Although I am surprised it doesn't work as you have it, since it works for me and I log into a different domain than what my computer is joined to. Although in my case, the two domains are in the same AD forest. Maybe that's not the case for you.
I am trying to read an msExchMailboxSecurityDescriptor, to find whether it contains a Full Access to another person. The access control entries contain the trustees in the netbios format (DOMAIN\Username).
SecurityDescriptor secDesc = (SecurityDescriptor)userDirectoryEntry.Properties["msExchMailboxSecurityDescriptor"].Value;
AccessControlList usrAcl = (AccessControlList)secDesc.DiscretionaryAcl;
foreach (AccessControlEntry ace in (IEnumerable)usrAcl)
{
var netbiosDn = ace.Trustee.Split('\\')[0];
var netbiosUser = ace.Trustee.Split('\\')[1];
// now, the problem:
UserPrincipal user = UserPrincipal.FindByIdentity(
new PrincipalContext(ContextType.Domain, netbiosDn),
netbiosUser
);
This works until the last line, where I have to connect to the correct AD server and get some user info. Obviously, this fails when there is no server available for that domain, like any of the "NT AUTHORITY" or "BUILTIN" "domains". It does not only fail, it needs quite some time until it does.
How on earth would I distinguish which ones are AD domains, where I can connect to the AD server, and which ones aren't?
Some example users I may find in the Security Descriptor, just for you to get a feel for the problem:
CONTOSO\Alex
CONTOSO\Michael
SUBDOMAIN\Kirk
TRUSTED\George
NTPD\ChiefBrown
NT AUTHORITY\SELF
NT INSTANS\INTERAKTIV
BUILTIN\Администраторы
BUILDING2\Владимир
VORDEFINERT\Administrator
Take a look at SecurityIdentifier.IsWellKnown
You can pass various values, including WellKnownSidType.NTAuthoritySid to determine what kind of SID you have.
(See also this PowerShell code on translating into readable names.)
I'm wondering what "principal" I should specify to login in to an Active Directory server. Should the principal be a user inside the AD I try to log into? Or it can be a user in the domain I specify as long as the user has privileges to access the AD?
I tried both with credentials error 49. But I can log in to the AD with ldp.exe by using the Administrator account of the server that AD is installed on.
Here is my code. Many thanks for any prompt help.
Hashtable env= new Hashtable(11);
env.put(Context.SECURITY_AUTHENTICATION,"simple"); // Also tried none w/ the same error
// What principal should I use??
env.put(Context.SECURITY_PRINCIPAL,"CN=Ross,OU=Eng,DC=RossInc");//User
//env.put(Context.SECURITY_PRINCIPAL, user + "#" + domain); // Tried w/ the same error
env.put(Context.SECURITY_CREDENTIALS, "ross");//Password
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,"ldap://myserver:389/DC=RossInc");
DirContext ctx = new InitialDirContext(env); <-- Fails with AuthenticationException: [LDAP: error code 49 - 8009030C
You either can provide:
NT-style login name
Kerberos UPN (implicit UPN)
explicit UPN (if additional UPN suffices have been defined)
More over, NEVER ever perform a simple bind! Either Digest or GSS-API.
According to the following example from Oracle site, the security Principal is a distinguished name.
Here is some code working for me from a computer inside the domain :
Hashtable<String, String> ldapEnv = new Hashtable<String, String>(11);
ldapEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
ldapEnv.put(Context.PROVIDER_URL, "ldap://societe.fr:389");
ldapEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
ldapEnv.put(Context.SECURITY_PRINCIPAL, "cn=administrateur,cn=users,dc=societe,dc=fr");
ldapEnv.put(Context.SECURITY_CREDENTIALS, "test.2011");
ldapContext = new InitialDirContext(ldapEnv);
The principal can be a user inside the AD as long as he has privileges to access the AD.
the company I work for has 2 Active Directory forests. One forest is called us where I log on in the morning with my profile (us\maflorin) and another forest is called (mail.us) which is dedicated to Exchange.
I have created an asp.net application that runs on SharePoint and gets the SPContext.Current.Web.CurrentUser.LoginName which is the us domain login name. (us\maflorin for example for me). I would like to get from the us credentials the corresponding object on the Exchange forest in order to write changes to the global address list (GAL) for user that opened the page after a line manager approval process.
I wrote the following working code to get the Exchange object, but it uses two ldap queries to find the object:
private Dictionary<string,AdRecod> FindExchangeAdProperties(string samAccountName,string description)
{
Dictionary<string,AdRecod> properties = null;
if (!string.IsNullOrEmpty(samAccountName))
{
properties = GetUserProperties(#"(&(objectCategory=person)(mailNickname=" +
samAccountName + "))");
if (properties != null) return properties;
}
if ((description == "") || (description == "0"))
throw new Exception("No matching Description, couldn't find correct Exchange AD object");
properties = GetUserProperties(#"(&(objectCategory=person)(description=" +
description + "))");
return properties;
}
Is it possible to get the exchange object with a single ldap query directly from the us samAccountName?
The mailNickname attribute on the exchange forest does not always match the sAMAccountName on the us forest. If it does not match, I use the second ldap query to see if a record is return by querying on the description field. The description field is many times the same for both forests but sometimes an administrator changed it.
Is it possible to find the corresponding Exchange Active Directory object for the us domain credentials more easily? How does Outlook find from the us credentials the corresponding mailbox / Ad object ? I was looking at the AD schema with adsiedit but could not find a clear field that is used to link the two forest objects together.
Furthermore I was looking into the Autodiscover service of the exchange web services managed api (mailbox dn attribute) but you need to pass into the GetUserSettings method an SMTP address and this field is not populated on the us domain.
Many thanks,
Mathias
I was able to find an answer to this question with a better approach than the one above which depends on the company's naming convention.
On the exchange forest I run a LDAP query with the DirectorySearcher class to obtain the attribute msExchMasterAccountSid.
The following code then provides the correct sam on the forest we use to logon:
var sid = directoryEntry.Properties["msExchMasterAccountSid"].Value as byte[];
// no mailbox
if (sid == null) continue;
var sidString = new SecurityIdentifier(sid, 0).ToString();
var samAccountName = "";
using (var context = new PrincipalContext(ContextType.Domain, "US"))
{
var principal = UserPrincipal.FindByIdentity(context, IdentityType.Sid, sidString);
if (principal == null) continue;
samAccountName = principal.SamAccountName;
}