LDAP cache without network connection - c#

We have a program running on different computers in our network, and this program needs to login with an specific administrator account to execute some tasks.
Our supplier wrote the below code to check whether the user is a local admin or not.
The question is:
If the admin user has logged in the computer before, will the credentials be saved in the cache so the below code would work even without a network connection?
If so, how can we force those credentials to be saved?
Else, is there any alternative so we can login with the credentials from the cache?
static bool UserExists(string domain, string username, string password)
{
if (System.Environment.GetEnvironmentVariable("UserDomain") != null)
{
if (string.Compare(System.Environment.GetEnvironmentVariable("UserDomain"), domain, true) != 0)
{
return false;
}
}
string filter = string.Format("(&(ObjectClass={0})(sAMAccountName={1}))", "person", username);
string[] properties = new string[] { "fullname" };
using (DirectoryEntry adRoot = new DirectoryEntry("LDAP://" + domain, null, null, AuthenticationTypes.Secure))
using (DirectorySearcher searcher = new DirectorySearcher(adRoot))
{
adRoot.Username = username;
adRoot.Password = password;
searcher.SearchScope = SearchScope.Subtree;
searcher.ReferralChasing = ReferralChasingOption.All;
searcher.PropertiesToLoad.AddRange(properties);
searcher.Filter = filter;
if (searcher.FindOne() != null)
{
// If you want to see the user details: searcher.FindOne().GetDirectoryEntry();
return true;
}
}
return false;
}
Thanks in advance

Related

Trying to access Properties in Active Directory to add to a Database

Working on pulling user info from an AD based on an ID that is entered. The error I get is:
Cannot implicitly convert type "string" to type "System.DirectoryServices.DirectoryEntry"
happening here in the Save method:
DirectoryEntry de = new DirectoryEntry();
de = QueryAD(objSearchRolesViewModel.NID);
Opening the connection:
private DirectoryEntry GetDirectoryObject()
{
DirectoryEntry oDE;
oDE = new DirectoryEntry("LDAP://myConnection");
return oDE;
}
Querying AD:
public string QueryAD(string userNID)
{
DirectorySearcher ds = new DirectorySearcher
{
SearchRoot = new DirectoryEntry(""),
//start searching from local domain
Filter = userNID
};
ds.PropertiesToLoad.Add("givenname");
ds.PropertiesToLoad.Add("sn");
ds.PropertiesToLoad.Add("mail");
// start searching
SearchResultCollection searchCollection = ds.FindAll();
try
{
foreach (SearchResult result in searchCollection)
{
if (result.Properties.PropertyNames != null)
foreach (string propKey in result.Properties.PropertyNames)
{
// Display each of the values for the property identified by the property name.
foreach (object prop in result.Properties[propKey])
{
if ((propKey == "userPrincipalName"))
{
return prop.ToString();
}
}
}
return "Unknown User";
}
catch (Exception ex)
{
return "Unknown User";
}
}
Save new user:
public void SaveUser(SearchRolesViewModel objSearchRolesViewModel, string userID)
{
DirectoryEntry de = new DirectoryEntry();
de = QueryAD(objSearchRolesViewModel.NID);
USERACCOUNT objUserAccount = new USERACCOUNT
{
HPID = Convert.ToInt32(objSearchRolesViewModel.NewUserHealthPlans),
DOMAIN = "Aeth",
NTUSERID = objSearchRolesViewModel.User_Id,
ROLEID = Convert.ToInt32(objSearchRolesViewModel.UserRole),
FIRSTNAME = GIVENNAME GOES HERE,
LASTNAME = SURNAME GOES HERE,
EMAIL = MAIL GOES HERE,
ACTIVE = true/*Convert.ToBoolean(objSearchRolesViewModel.ActiveStatus)*/,
DEFAULTPLANID = Convert.ToInt32(objSearchRolesViewModel.NewUserPrimaryHealthPlan),
CREATEID = userID,
CREATEDATE = DateTime.Now,
UPDATEID = userID,
UPDATEDATE = DateTime.Now
};
_context.USERACCOUNTs.Add(objUserAccount);
_context.SaveChanges();
}
I need to be able to access the properties from Active Directory and add them to what is being sent to the DB when a new user is added.
A couple nit-picky things:
Opening the connection
Creating a DirectoryEntry object doesn't actually open any connection. That is how almost all of Microsoft's libraries work: constructors do not make any I/O requests. The first network request is made when you first start using the object.
Also, new DirectoryEntry("") has exactly the same effect as new DirectoryEntry() - the empty string doesn't get you anything. But also, if you don't set the SearchRoot property, it will automatically set it to the current domain anyway. So you don't even need to set it unless you need to set it to a different domain or OU.
Now on to answering the question:
You have gotten a couple answers already, but they aren't ideal. You certainly can use the System.DirectoryServices.AccountManagement namespace if you want, which is just a wrapper around System.DirectoryServices to make things easier. But like all things that make things easier, it does so at the cost of performance. It always has worse performance over using System.DirectoryServices directly, yourself. One reason is because whenever a UserPrincipal object is created, it retrieves every attribute that has a value for the account. You probably aren't using every attribute.
If you can wrap your head around using DirectoryEntry/DirectorySearcher yourself, you will have better performing code.
Jawad's answer will also work, but it has one key issue that will slow down your code: DirectorySearcher will return all the attributes you request. You already set PropertiesToLoad, which is good - it will limit the results to only the attributes you need. But if you use GetDirectoryEntry(), that info is lost. If you then start using .Properties on the DirectoryEntry, it will make a new network request and ask for all attributes that have values. That can be a lot of data that you aren't using, apart from being a second network request that you could avoid.
I would suggest just returning a SearchResult from QueryAD, which will let you use the data that was returned in the search.
You can also use FindOne() instead of FindAll(), since you are only using the first result anyway. This will make AD stop looking after it finds one result. Just test for null in case the user wasn't found.
public SearchResult QueryAD(string userNID)
{
DirectorySearcher ds = new DirectorySearcher(userNID) {
PropertiesToLoad = {"givenname", "sn", "mail"}
};
return ds.FindOne();
}
public void SaveUser(SearchRolesViewModel objSearchRolesViewModel, string userID)
{
var result = QueryAD(objSearchRolesViewModel.NID);
if (result == null)
{
//user wasn't found!
}
USERACCOUNT objUserAccount = new USERACCOUNT
{
HPID = Convert.ToInt32(objSearchRolesViewModel.NewUserHealthPlans),
DOMAIN = "Aeth",
NTUSERID = objSearchRolesViewModel.User_Id,
ROLEID = Convert.ToInt32(objSearchRolesViewModel.UserRole),
FIRSTNAME = (string) result.Properties["givenName"]?[0],
LASTNAME = (string) result.Properties["sn"]?[0],
EMAIL = (string) result.Properties["mail"]?[0],
ACTIVE = true/*Convert.ToBoolean(objSearchRolesViewModel.ActiveStatus)*/,
DEFAULTPLANID = Convert.ToInt32(objSearchRolesViewModel.NewUserPrimaryHealthPlan),
CREATEID = userID,
CREATEDATE = DateTime.Now,
UPDATEID = userID,
UPDATEDATE = DateTime.Now
};
_context.USERACCOUNTs.Add(objUserAccount);
_context.SaveChanges();
}
The Properties of a SearchResult will always present properties as arrays, even if they are single-valued attributes in AD. This is different than DirectoryEntry. But that is the reason for the [0] in result.Properties["givenName"]?[0] as string. The ? is to test for null, because if the attribute is not set in AD, then it won't appear in the Properties collection at all.
I wrote an article about getting better performance when programming with AD, with a focus on C#. You might enjoy reading it.
In your code, QueryAD(objSearchRolesViewModel.NID); returns a string but you are assigning it to a DirectoryEntity. This wont work.
public void SaveUser(SearchRolesViewModel objSearchRolesViewModel, string userID)
{
DirectoryEntry de = new DirectoryEntry();
de = QueryAD(objSearchRolesViewModel.NID); // <--- This is the issue.
...
Look up DirectoryEntry from the QueryAD function and return that object to make your call work.
public string QueryAD(string userNID) // You will need to return DirectoryEntry to make your code work.
{
DirectorySearcher ds = new DirectorySearcher
EDIT:
I find using UserPrincipal with PrincipalContext to be much simpler. Look up PrincipalContext by using your domain name and provide creds if not running with domain account. Then, simply, lookup user by SamAccountName, Name/ID or DistinguishedName.
You will need, 'System.DirectoryServices.AccountManagement' nuget package for Principal usage.
public static UserPrincipal QueryAD(string UserName)
{
PrincipalContext context = new PrincipalContext(ContextType.Domain, "Aeth", "user", "password");
// Without creds if the account running the code is already a domain account
//PrincipalContext context = new PrincipalContext(ContextType.Domain, "Aeth");
// You can search the account by SamAccountName, DistinguishedName, UserPrincipalName or SID
return UserPrincipal.FindByIdentity(context, IdentityType.Name, UserName);
}
public void SaveUser(SearchRolesViewModel objSearchRolesViewModel, string userID)
{
UserPrincipal user = QueryAD(objSearchRolesViewModel.User_Id);
USERACCOUNT objUserAccount = new USERACCOUNT
{
HPID = Convert.ToInt32(objSearchRolesViewModel.NewUserHealthPlans),
DOMAIN = "Aeth",
NTUSERID = objSearchRolesViewModel.User_Id,
ROLEID = Convert.ToInt32(objSearchRolesViewModel.UserRole),
FIRSTNAME = user.GivenName, // Get FirstName
LASTNAME = user.Surname, // Get LastName
EMAIL = user.EmailAddress, // Get Email Address
ACTIVE = user.Enabled, // Get User Status
DEFAULTPLANID = Convert.ToInt32(objSearchRolesViewModel.NewUserPrimaryHealthPlan),
CREATEID = userID,
CREATEDATE = DateTime.Now,
UPDATEID = userID,
UPDATEDATE = DateTime.Now
};
_context.USERACCOUNTs.Add(objUserAccount);
_context.SaveChanges();
}
Following is your code that uses DirectoryEntry method. Make sure you add credentials if you are running this code with account that does not have access to AD.
Method to search AD
public DirectoryEntry QueryAD(string UserName)
{
try
{
DirectorySearcher ds = new DirectorySearcher
{
SearchRoot = new DirectoryEntry(),
//start searching from local domain
Filter = "(&" +
"(objectClass=user)" +
"(name=" + UserName + "))" // This is Username
};
// start searching
return ds.FindOne().GetDirectoryEntry();
}
catch (Exception ex)
{
throw new ApplicationException("error occured while querying AD");
}
}
Method to Check if Account is Active
private bool IsActive(DirectoryEntry de)
{
if (de.NativeGuid == null) return false;
int flags = (int)de.Properties["userAccountControl"].Value;
return !Convert.ToBoolean(flags & 0x0002);
}
Method to Save the User to your DB
public void SaveUser(SearchRolesViewModel objSearchRolesViewModel, string userID)
{
DirectoryEntry userEntry = QueryAD(objSearchRolesViewModel.User_Id);
if (userEntry == null)
{
//Handle error where No User was Found.
throw new ApplicationException("User Not Found");
}
USERACCOUNT objUserAccount = new USERACCOUNT
{
HPID = Convert.ToInt32(objSearchRolesViewModel.NewUserHealthPlans),
DOMAIN = "Aeth",
NTUSERID = objSearchRolesViewModel.User_Id,
ROLEID = Convert.ToInt32(objSearchRolesViewModel.UserRole),
FIRSTNAME = userEntry.Properties["givenname"].Value.ToString(),
LASTNAME = userEntry.Properties["sn"].Value.ToString(),
EMAIL = userEntry.Properties["mail"].Value.ToString(),
ACTIVE = IsActive(userEntry),
DEFAULTPLANID = Convert.ToInt32(objSearchRolesViewModel.NewUserPrimaryHealthPlan),
CREATEID = userID,
CREATEDATE = DateTime.Now,
UPDATEID = userID,
UPDATEDATE = DateTime.Now
};
_context.USERACCOUNTs.Add(objUserAccount);
_context.SaveChanges();
}

Check if the User is valid user on Domain - Active Directory

A lot of people posted about this but could not get anything to work. I am trying to get the user's username and password on an Asp.net form (the same username and password which the user uses to login to their computer on a domain).
I am using the PrincipalContext to validate the user.
Although I provide valid username and password, but pc.ValidateCredentials always returns false.
This is the first time I am doing User Authentication through Active Directory and have no idea what else do I require to successfully validate a user from Active Directory.
Do I need to provide information in the Container and Name properties on the PrincipalContext Object as it appears to be null.
Currently I am running this code from local machine which is on domain.
Do you have the correct domain? Maybe it is called different than 'DOMAIN', try this one:
private bool Authenticate(string user, string password)
{
using ( var context = new PrincipalContext(ContextType.Domain, Environment.UserDomainName) ) {
return context.ValidateCredentials(user.Trim(), password.Trim());
}
}
Please use below function
private bool AuthenticateAD(string userName, string password, string domain, out string message)
{
message = "";
DirectoryEntry entry = new
DirectoryEntry("LDAP://" + domain, userName, password);
try
{
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;
}
}
catch (Exception ex)
{
message = ex.Message;
return false;
//throw new Exception("Error authenticating user. " + ex.Message);
}
return true;
}

How the PrincipalContext works

I need to let users bind there accounts to the Active Directory. This means that admin needs a GUI where he/she can write a Active Directory account like this : MyDomain\MyName and then get a validation if the users exists before save.
Im using this code to validate the name :
public static bool CheckActiveDirectoryAccount(string account)
{
string ADServer = null;
string ADDomain = null;
string ADUserName = null;
string ADUserPassword = null;
SetADSettings(out ADServer, out ADDomain, out ADUserName, out ADUserPassword);
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, ADServer, ADUserName, ADUserPassword))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(context, account))
{
if(user != null)
return true;
else
return false;
}
}
}
The problem with this code is that there seems to be no way to check the user for a specific domain? Instead I have to input the server, if I try to input the domain instead there will be exception(Server not found).
How do I let the admin enter domain and username of a AD account and then check it against the AD?
I am able to pass the domain into the principalcontext without issue, I'm not passing in the server. I would expect this to work for you.
public static bool CheckActiveDirectoryAccount(string account, string domain)
{
using (var pc = new PrincipalContext(ContextType.Domain, domain))
{
// Find a user
UserPrincipal user = UserPrincipal.FindByIdentity(pc, account);
if (user == null)
return false;
return true;
}
}
I have noticed poor performance when passing in the NetBIOS domain name, though it does work. As a result I pass in the DNS domain name whenever possible.
I ended up with this :
public static string CheckActiveDirectoryAccount(string account)
{
UserPrincipal user;
PrincipalContext context;
List<string> userPrincipalNameList;
string ADServer = null;
string ADUserName = null;
string ADUserPassword = null;
string userAccount;
account = account.ToLower();
GetADSettings(out ADServer, out ADUserName, out ADUserPassword);
if (ADUserName.Length > 0)
context = new PrincipalContext(ContextType.Domain, ADServer, null, ADUserName, ADUserPassword);
else
context = new PrincipalContext(ContextType.Domain, ADServer);
using (context)
{
if((user = UserPrincipal.FindByIdentity(context, account)) == null)
{
if(account.Contains("\\"))
{
userPrincipalNameList = user.UserPrincipalName.Split('\\').ToList();
if (userPrincipalNameList.Count > 0)
user = UserPrincipal.FindByIdentity(context, userPrincipalNameList[0]);
}
}
if (user != null)
{
using (user)
{
userPrincipalNameList = user.UserPrincipalName.Split('#').ToList();
userAccount = userPrincipalNameList.First();
if (userPrincipalNameList.Count > 1)
userAccount = userPrincipalNameList.Last() + "\\" + userAccount;
if (user != null)
return userAccount.ToLower();
}
}
}
return string.Empty;
}

create AD-User in c#

I am trying to create a new AD-User with this code:
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "Domain", "ou=some_ou, dc=Mydomain");
UserPrincipal user = new UserPrincipal(ctx, account, passwd, true);
user.GivenName = Givenname;
user.Surname = Surname;
user.DisplayName = Displayname;
user.UserPrincipalName = account + "#Domain";
user.Save();
The User is created without error. But I also have to set properties like Address etc, so the code continues with:
string distname = user.DistinguishedName;
DirectoryEntry duser = new DirectoryEntry(distname);
try
{
duser.Properties["company"].Value = "Company";
}
catch (Exception e)
{
}
Now I am getting
Error: System.Exception._COMPlusExceptionCode -532459699
The string distname shows the correct distinguished name.
I am not 100% sure what is causing your problem but one thing that may make things easier on you and may clear up some errors due to you improperly using both DirectoryServices and DirectoryServices.AccountManagement at the same time is creating a new class that includes the company attribute.
Its actually not that hard to do.
[DirectoryObjectClass("user")]
[DirectoryRdnPrefix("CN")]
public class UserPrincipalEx : UserPrincipal
{
public UserPrincipalEx(PrincipalContext context) : base(context) { }
public UserPrincipalEx(PrincipalContext context, string samAccountName, string password, bool enabled)
: base(context, samAccountName, password, enabled)
{
}
[DirectoryProperty("company")]
public string Company
{
get
{
if (ExtensionGet("company").Length != 1)
return null;
return (string)ExtensionGet("company")[0];
}
set { this.ExtensionSet("company", value); }
}
}
You can then just modify your code to
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "Domain", "ou=some_ou, dc=Mydomain");
UserPrincipalEx user = new UserPrincipalEx(ctx, account, passwd, true);
user.GivenName = Givenname;
user.Surname = Surname;
user.DisplayName = Displayname;
user.UserPrincipalName = account + "#Domain";
user.Company = "Company";
user.Save();
My hunch is you are having some kind of interaction with the two methods of interfacing with active directory, if you switch to a single interface your problem may just go away.
For DirectoryEntry, you have to specify the protocol (LDAP, GC, WinNT, ...). So you would have to do:
DirectoryEntry duser = new DirectoryEntry("LDAP://" + distname);
Note that the protocol is case sensitive, LDAP has to be all caps.
I see you are using credentials in the UserPrincipal,
Did you forgot to use them when creating your DirectoryEntry?
Also, you need to add "LDAP://" before you server name
Try something like :
DirectoryEntry duser = new DirectoryEntry("LDAP://" + distname);
duser.Username = account;
duser.Password = passwd;
duser.AuthenticationType = AuthenticationTypes.Secure;

c# check if the user member of a group?

I have a code that I use to check if the user is member of the AD, worked perfectly,
now I want to add the possibility to check if the user also a member of a group!
what do I need to modify to achieve that, I did some work, but it fails!
so here is my code:
//Authenticate a User Against the Directory
private bool Authenticate(string userName,string password, string domain)
{
if (userName == "" || password == "")
{
return false;
}
bool authentic = false;
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain,userName, password);
object nativeObject = entry.NativeObject;
authentic = true;
}
catch (DirectoryServicesCOMException) { }
return authentic;
}
I want to make it like this:
private bool Authenticate(string userName,string password, string domain, string group)
This is not available on Windows XP or earlier.
Anyway, in order to check for group membership, you can use this code:
bool IsInGroup(string user, string group)
{
using (var identity = new WindowsIdentity(user))
{
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(group);
}
}
In ASP.Net you will use Page.User.IsInRole("RoleName") or in Windows you can use System.Threading.Thread.CurrentPrincipal.IsInRole("RoleName")
I solve it with this code
public bool AuthenticateGroup(string userName, string password, string domain, string group)
{
if (userName == "" || password == "")
{
return false;
}
try
{
DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain, userName, password);
DirectorySearcher mySearcher = new DirectorySearcher(entry);
mySearcher.Filter = "(&(objectClass=user)(|(cn=" + userName + ")(sAMAccountName=" + userName + ")))";
SearchResult result = mySearcher.FindOne();
foreach (string GroupPath in result.Properties["memberOf"])
{
if (GroupPath.Contains(group))
{
return true;
}
}
}
catch (DirectoryServicesCOMException)
{
}
return false;
}
it works fine for me, and it can be use with a machine not part of the Domain Controller / Active Directory
Thank you all for the help

Categories

Resources