Active Directory PrincipalContext.ValidateCredentials domain disambiguation - c#

I'm dealing with two domains - one is a trusted domain. There may be a JohnSmith on one domain and another JohnSmith on the other. Both of these people need to log into my application.
My problem: it doesn't matter which domain I pass in - this code returns true! How do I know which JohnSmith is logging in?
static public bool CheckCredentials(
string userName, string password, string domain)
{
using (var context = new PrincipalContext(ContextType.Domain, domain))
{
return context.ValidateCredentials(userName, password);
}
}

The ValidateCredentials works with userPrincipalName you perhaps can try to build the first parameter (username) combining the login and the domain to create the username JohnSmith#dom1.com versus JohnSmith#dom2.com.

You can always retrieve the full DN of the user who has logged in using
UserPrincipal up = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, userName);
up.UserPrincipalName // shows user#domain.com
up.DistinguishedName // shows CN=Surname,OU=group,DC=domain,DC=com
up.SamAccountName // shows login name
Use the up.SamAccountName to subsequent calls to ValidateCredentials including the domain name - you can't have 2 users who log in using the same sAMAccountName after all!
The DistinguishedName will definitely show you which JohnSmith logged in.

Based on JPBlanc's answer, I've re-written my code. I've also added a try/catch in case a bogus domain is passed in.
static public bool CheckCredentials(
string userName, string password, string domain)
{
string userPrincipalName = userName + "#" + domain + ".com";
try
{
using (var context = new PrincipalContext(ContextType.Domain, domain))
{
return context.ValidateCredentials(userPrincipalName, password);
}
}
catch // a bogus domain causes an LDAP error
{
return false;
}
}

The accepted answer will fail with Domains that contain different email addresses within them. Example:
Domain = Company
User1 = employee#department1.com (under company Domain)
User2 = employee2#Department2.com (under company Domain)
The provided answer will return false using:
userName = "employee";
domain = "company";
string userPrincipalName = userName + "#" + domain + ".com";
The correct way to encompass users across domains is:
string userPrincipalName = userName + "#" + domain;
without the .com portion it searches the user AT that domain instead of searching for an email within a global domain.

Related

Getting users from another AD Domain using PrincipalContext

How would I get users from an AD Group which contains users from different domains.
For example, I have 2 domains in my active directory, Domain1.corp.com and Domain2.corp.com
I have an AD group called TestGroup which contains users from both the domains.
Domain1 users: TestUser1, TestUser2
Domain2 users: TestUser3, TestUser4, TestUser5
TestGroup users: TestUser1, TestUser2, TestUser3, TestUser5
Following could would return only Domain1 users.
string domainname = "Domain1.corp.com:3268";
string usernames = String.Empty;
using (var p_context = new PrincipalContext(ContextType.Domain, domainname))
{
using (var group = GroupPrincipal.FindByIdentity(context, "TestGroup"))
{
var users = group.GetMembers(false);
foreach (var user in users)
{
username = username + ", " + user.SamAccountName;
}
}
}
When returning the username variable, I would see users from only Domain1.Am I missing anything over here?
My IIS server is located in the Domain1.corp.com
I verified that the server had access to the other domain by running a powershell script which returned users located in both the domains.
get-adgroupmember "TestGroup" -recursive
Ref: https://stackoverflow.com/a/7073023/326315
You need to use the underlying System.DirectoryServices.DirectoryEntry for the group:
var groupEntry = (DirectoryEntry)group.GetUnderlyingObject();
(Note: according to MSDN GetUnderlyingObject() will return a DirectoryEntry, even though the return type is object.)
From there you can get the member distinguished names:
var memberDistinguishedNames = groupEntry.Properties["member"].Cast<string>();
The users from the other domain are Foreign Security Principals so the distinguished name will be in the form CN={SecurityIdentifier},CN=ForeignSecurityPrincipals. You can extract the Security Identifier and search for the user on the other domain/s in order to get the rest of their details. Unfortunately this does mean you need to connect to both domains, which can be slow.

UserPrincipal.FindByIdentity throws exception - There is no such object on the server

I'm struggling with a simple scenario: I would like to retrieve my account from Active Directory using the username and password which I use to log into my computer.
My first issue was that I was receiving a referral from the server when attempting to call UserPrincipal.FindByIdentity. I thought that this was a bit weird, given the fact that PrincipalContext.ValidateCredentials was working fine, but it turns out that my DC path was incorrect.
I wasn't sure how to properly craft my OU/DC string. As such, I found this SO post
which helpful provided the following bit of code:
private static string GetDomainControllerString()
{
string pdc;
using (var context = new PrincipalContext(ContextType.Domain))
{
string server = context.ConnectedServer; // "pdc.examle.com"
string[] splitted = server.Split('.'); // { "pdc", "example", "com" }
IEnumerable<string> formatted = splitted.Select(s => String.Format("DC={0}", s));// { "DC=pdc", "DC=example", "DC=com" }
string joined = String.Join(",", formatted); // "DC=pdc,DC=example,DC=com"
// or just in one string
pdc = String.Join(",", context.ConnectedServer.Split('.').Select(s => String.Format("DC={0}", s)));
}
return pdc;
}
After using this code to properly generate my DC string, my error message changed. Now, I am receiving the error "There is no such object on the server." I suspect the issue is either with my OU or how I am calling FindByIdentity.
Here is the location of my user account which I am trying to retrieve:
And here is how I am attempting to access said user:
private static void Main(string[] args)
{
const string Domain = "SLO1.Foo.Bar.biz";
const string DefaultOU = "OU=Users,DC=SLO1,DC=Foo,DC=Bar,DC=biz";
const string username = #"sanderso";
const string password = "**********";
var principalContext = new PrincipalContext(ContextType.Domain, Domain, DefaultOU, ContextOptions.Negotiate, username, password);
bool areCredentialsValid = principalContext.ValidateCredentials(username, password, ContextOptions.Negotiate);
if (areCredentialsValid)
{
UserPrincipal userPrincipal = UserPrincipal.FindByIdentity(principalContext, username);
}
}
I have also tried calling:
UserPrincipal.FindByIdentity(principalContext, IdentityType.Name, "Sean Anderson");
UserPrincipal.FindByIdentity(principalContext, "Sean Anderson");
these were equally unsuccessful.
I belive the object that does not exist is:
"OU=Users,DC=SLO1,DC=Foo,DC=Bar,DC=biz"
Users is a container, not an OU. So correcty you need:
"CN=Users,DC=SLO1,DC=Foo,DC=Bar,DC=biz"
This Code should work for you Sean
I work on AD for BOA currently and use this many times..
public bool UserExists(string username)
{
// create your domain context
PrincipalContext domain = new PrincipalContext(ContextType.Domain);
// find the user
UserPrincipal foundUser = UserPrincipal.FindByIdentity(domain, IdentityType.Name, username);
return foundUser != null;
}
from MSDN what each parameter is see the list below
Parameters
context
Type: System.DirectoryServices.AccountManagement.PrincipalContext
The PrincipalContex that specifies the server or domain against which operations are performed.
identityType
Type: System.DirectoryServices.AccountManagement.IdentityType
A IdentityType enumeration value that specifies the format of the identityValue parameter.
identityValue
Type: System.String
The identity of the user principal. This parameter can be any format that is contained in the IdentityType enumeration.
Return Value
Type: System.DirectoryServices.AccountManagement.UserPrincipal
A UserPrincipal object that matches the specified identity value and type, or null if no matches are found.
UserPrincipal.FindByIdentity Method()

Validating a user's credentials remotely

I currently use LogonUser() to authenticate my user's username and password on my local domain at the office and it works great for what i need it to do.
Since I developed the app I now need to make it work over my VPN. It seems LogonUser() will not work with REMOTELY validating credentials. Or will it? Is it possible to use LogonUser() to validate a user's credentials on a REMOTE domain account?
I have read in some places that using LOGON32_LOGON_NEW_CREDENTIALS for the 4th param (login type) and LOGON32_PROVIDER_WINNT50 for the 5th param (provider) would do the trick. But every time I try that I ALWAYS get success... I can supply a bogas user and pass and it will work every time :(.
Ideas?
Edit - Added Notes
Tried to use this function but I kept getting the exception telling me the user/pass was bad.
public bool Win2kCredentialsIsValid(string domain, string username, string password)
{
string adPath = "LDAP://" + domain + "/rootDSE";
DirectoryEntry adRoot = new DirectoryEntry(adPath, domain + "\\" + username, password, AuthenticationTypes.ReadonlyServer);
try
{
object o = adRoot.Properties["defaultNamingContext"];
}
catch
{
return false;
}
return true;
}
--
Edit - Added More Notes
OK so I tried yet another example just to get it to work and started down this path, and there are a few things to note...
MyServerHostName is exactly that, my server's hostname. EX: 'Server01'.
My domain name in this example is 'MyDomain.local'
So that makes my FQN for the server 'Server01.MyDomain.local'
I tried to make this work and got the following error...
The supplied context type does not match the server contacted. The server type is Domain.
This errored out at : var context = new PrincipalContext(ContextType.ApplicationDirectory, "MyServerHostName:389", "DC=MyDomain,DC=local"))
private bool CheckADCredentials()
{
bool bResults;
using (var context = new PrincipalContext(ContextType.ApplicationDirectory,
"MyServerHostName:389",
"DC=MyDomain,DC=local"))
{
var username = "firstname.lastname";
var email = "firstname.lastname#MyServerHostName";
var password = "123456";
var user = new UserPrincipal(context)
{
Name = username,
EmailAddress = email
};
user.SetPassword(password);
user.Save();
if (context.ValidateCredentials(username, password, ContextOptions.SimpleBind))
{
bResults = true;
}
else
{
bResults = false;
}
user.Dispose();
}
return bResults;
}
I ended up going with a different solution. Instead of trying to validate a user's account on a domain that my PC was not connected to I ended up caching my domain credentials in the database and just built a salted MD5 type encrypt function so it would make it hard .. er.. for someone to crack it. ;)
Now I just validate against cached credentials in the database when working remotely... It just required the user to first login on the domain but then the user can use it remotely day and night. ;)
Thanks!

Verify if password is correct

i need to verify if the password is correct for a user.
i have this code:
private bool checkOldPasswordValid(string password, string username)
{
using (DirectoryEntry entry = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer"))
{
entry.Username = username;
entry.Password = password;
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "(objectclass=user)";
try
{
searcher.FindOne();
}
catch (Exception ex)
{
return false;
}
return true;
}
}
but then directory searcher is not supported with WinNt, so i found another way to loop through all records.
foreach (DirectoryEntry dc in entry.Children)
{
// prints the name
System.Diagnostics.Debug.WriteLine(dc.Name);
}
but this just gets the name and doesnt verify the password.
please help . thanks
To autenticate against LDAP or WinNT, you need no DirectorySearcher. You only need to get the NativeObject from your DirectoryEntry instance. Here's a code sample that might guide you through the way.
public bool Authenticate(string username, string password, string domain) {
bool authenticated = false;
using (DirectoryEntry entry = new DirectoryEntry(#"WinNT://" + domain, username, password) {
try {
object nativeObject = entry.NativeObject;
authenticated = true;
} catch (DirectoryServicesCOMException ex) {
}
}
return authenticated;
}
This code will return either a user is authentic or not. Once you can get the NativeObject property using this DirectoryEntry class instance, this means that the AD (or local computer) used impersonation to get this object. If you get the object without having a thrown exception, this means that the AD (or local computer) was able to authenticate the impersonnated user.
While you can use the currently authenticated user by specifying no username and password, but only the domain (or local computer), by specifying a username and password, you say you want to use impersonnation, so the security infrastructure will use the given username and password to try to retrieve the NativeObject property from this DirectoryEntry class instance.
To authenticate against the AD, just replace the "WinNT://" for "LDAP://".
You can use DirectoryEntry itself.
See the example here: http://support.microsoft.com/kb/316748
Why are you using WinNT:// anyways?

Get UPN or email for logged in user in a .NET web application

I'm not a .NET developer, and I have a feeling this would be trivial for someone who is:
I have a C# web application that makes user of the user credentials of the logged in user. Currently it uses the SID which comes from
System.Security.Principal.WindowsIdentity.GetCurrent().User.Value
I need to get either the users UPN login or email address (as defined in active directory) instead of the SID. GetCurrent() returns an object of type WindowsIdentity; looking in the details for WindowsIdentity Members:
MSDN: WindowsIdentity Members
I can't see anything that looks like it would give me either the UPN or email in there. How can I pull up that information to use, either by feeding the SID into some other function or calling something different in the first place.
Meanwhile (.NET 3.5) this is a one-liner:
System.DirectoryServices.AccountManagement.UserPrincipal.Current.EmailAddress
for the email, or
System.DirectoryServices.AccountManagement.UserPrincipal.Current.UserPrincipalName
for the UPN.
To query active directory using a directory searcher you need to do something like this (totally untested code):
string userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
string ldapPath = "LDAP://domain.company.com";
public string GetEmail(string userName, string ldapPath)
{
using (DirectoryEntry root = new DirectoryEntry(ldapPath))
{
DirectorySearcher searcher = new DirectorySearcher(root);
searcher.Filter = string.Format(#"(&(sAMAccountName={0}))", userName);
searcher.PropertiesToLoad = "mail";
SearchResult result = searcher.FindOne();
if (result != null)
{
PropertyValueCollection property = result.Properties["mail"];
return (string)property.Value;
}
else
{
// something bad happened
}
}
}
Try:
System.Security.Principal.WindowsIdentity.GetCurrent().Name

Categories

Resources