I'm writing some kind of a mini AD tool (with VS-C#) to our organization and got into an issue.
I have a main function that searches the user (when I click on it in a listview) and some functions that manipulate the user's object.
public DirectoryEntry GetUser(string username)
{
try
{
Forest currentForest = Forest.GetCurrentForest();
GlobalCatalog gc = currentForest.FindGlobalCatalog();
using (DirectorySearcher searcher = gc.GetDirectorySearcher())
{
searcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=" + username + "*))";
SearchResult results = searcher.FindOne();
if (!(results == null))
{
DirectoryEntry de = new DirectoryEntry(results.Path, strAdminUser, strAdminPass, AuthenticationTypes.Secure);
de.RefreshCache(new string[] { "canonicalName" });
de.Path = de.Properties["canonicalName"].Value.ToString();
de.CommitChanges();
return de;
}
else
{
return null;
}
}
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return null;
}
}
and here's an example of a function that checks if the user is locked:
public bool IsUserLocked(string username)
{
try
{
DirectoryEntry de = GetUser(username);
string attName = "msDS-User-Account-Control-Computed";
de.RefreshCache(new string[] { attName });
const int UF_LOCKOUT = 0x0010;
int userFlags = /*(int)*/Convert.ToInt32(de.Properties[attName].Value);
if ((userFlags & UF_LOCKOUT) == UF_LOCKOUT)
{
return true;
}
de.Dispose();
return false;
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return false;
}
}
The function that checks the locked status of a user always fails with an error: "Unspecified error", but if I'm not changing the Directory Entry's path in the first function I get "The server is unwilling to process the request" error (I'm using proper service username and password with all the permissions needed) but still it happens.
Can someone spot the issue?
How about using System.DirectoryServices.AccountManagement namespace? If you have no issue using the new namespace, there's a simpler way to check if user account is locked and unlock if needed.
public bool IsUserLocked (string username)
{
using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "yourdomain.com")
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(ctx, username)
{
if (user != null) return user.IsAccountLockedOut();
}
}
return null;
}
And similarly, you can unlock the user account if needed.
...
if (user != null)
{
user.UnlockAccount();
user.Save();
}
Got it...
This has solved my issue:
de.Path = results.Path.Replace("GC://DCNAME.", "LDAP://");
Since I'm searcing on the Global Catalog, I had to replace a portion in the path to match it to the correct path:
public DirectoryEntry GetUser(string username)
{
try
{
Forest currentForest = Forest.GetCurrentForest();
GlobalCatalog gc = currentForest.FindGlobalCatalog();
using (DirectorySearcher searcher = gc.GetDirectorySearcher())
{
searcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=" + username + "*))";
SearchResult results = searcher.FindOne();
if (!(results == null))
{
DirectoryEntry de = new DirectoryEntry(results.Path, strAdminUser, strAdminPass, AuthenticationTypes.Secure);
de = new DirectoryEntry(results.Path);
de.Path = results.Path.Replace("GC://DCNAME.", "LDAP://");
de.CommitChanges();
//System.Windows.Forms.MessageBox.Show(de.Path);
return de;
}
else
{
return null;
}
}
}
catch (DirectoryServicesCOMException e)
{
System.Windows.Forms.MessageBox.Show(e.Message);
return null;
}
}
Now the path returns to the function called GetUser in the correct format :)
Related
I tried to change/reset password to user that has to change his password after first login using c#
My code:
var domain = WebConfigurationManager.AppSettings["ONLINE-AD"];
directoryEntry.Username = userName;
directoryEntry.Password = password;
var directorySearcher = new DirectorySearcher(directoryEntry);
SearchResult result = directorySearcher.FindOne();
if (result != null)
{
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry != null)
{
userEntry.Invoke("SetPassword", model.Resetpassword);
userEntry.CommitChanges();
}
}
But when I tried to do FindOne() i got an error 773 (means that the user has to change password for first time)
here is the error :
How to access user using LDAP ?(I successed to do login with
proper user)
I created Admin user that can manage all users then I got all users using the admin and find the wanted user and set password to this user:
public string ResetPassword(LoginDTO model) {
try {
//get context by admin user
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, WebConfigurationManager.AppSettings["ONLINE-AD"], WebConfigurationManager.AppSettings["AdminName"], WebConfigurationManager.AppSettings["AdminPassword"]);
//find the wanted user
var user = UserPrincipal.FindByIdentity(ctx, model.UserName);
if (user != null) {
try {
user.ChangePassword(model.Password, model.NewPassword);
} catch {
return "-1";
}
}
} catch (Exception ex) {
return "-1";
}
return "1"
}
private static bool ValidateUser(string userName, string password, string ldapPath)
{
DirectoryEntry directoryEntry = new DirectoryEntry(ldapPath, userName, password, AuthenticationTypes.ReadonlyServer);
try
{
object obj = directoryEntry.NativeObject;
if (obj.IsNotNullRef())
{
return true;
}
}
catch (Exception ex)
{
//error handling
}
finally
{
directoryEntry.Dispose();
}
return false;
}
I have the sample snippet above that validates an active directory username & password successfully if the domain NETBIOS and DNS match.
However, if the Domain name (NETBIOS) is not matching the DNS entry of the domain, i.e when the NETBIOS & DNS have been registered differently, the code doesn't return true even when you provide a valid UserName and Password.
How can i work around this issue?
EDIT:
The sample input is just standard, a userName, password & a domain URL
Example that returns True:
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://abcd1.xyz1.xx1.org", "abcd1\\stacktrace1", "xxxx", AuthenticationTypes.ReadonlyServer);
Example that returns false:
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://abcd2.xyz2.xx2.org", "abcd2\\stacktrace2", "xxxx", AuthenticationTypes.ReadonlyServer);
The only difference between the 2 examples is that, in example 1, the Domain name (NETBIOS) is matching with the DNS entry while in example 2, NETBIOS & DNS have been registered differently.
Refer following method:
public void AuthenticateUser(string userName, string password)
{
DirectoryUserAuthenticationResponse response = new DirectoryUserAuthenticationResponse();
try
{
// Creating Principal Context.
using (var principalContext = GetPrincipalContext())
{
try
{
// Getting user identity and validating user against active provider.
var aUser = UserPrincipal.FindByIdentity(principalContext, GetIdentitytype(), userName);
if (aUser != null)
{
// To check user account is locked out or not
if (aUser.IsAccountLockedOut())
throw new Exception("UserAccountLockedOut");
// To check user account is disabled or not.
if (aUser.Enabled == false)
throw new Exception("UserAccountDisabled");
// To check user change password on next logon.
if (aUser.LastPasswordSet == null)
throw new Exception("changePassword");
//To check password expiration
if (aUser.LastPasswordSet != null && aUser.PasswordNeverExpires == false)
{
DirectoryEntry rootDSE = new DirectoryEntry("LDAP://" + _directoryServerInfo.IPAddress, userName, password);
try
{
// Bind to the ADsobject to force authentication
object nativeobject = rootDSE.Name;
}
catch (DirectoryServicesCOMException cex)
{
string errorCode = cex.ExtendedErrorMessage.Substring(cex.ExtendedErrorMessage.IndexOf("data", 1));
errorCode = errorCode.Substring(5, 3);
//The commented code below fails to parse properly and throws an exception
// int exErrorCode = int.Parse(errorCode);
int exErrorCode = int.Parse(Regex.Match(errorCode, #"\d+").Value);
if (exErrorCode == (int)PWDFlags.Account_Expired)
throw new Exception("AccountExpired");
if (exErrorCode == (int)PWDFlags.Password_Expiration)
throw new Exception("PasswordExpired");
}
}
// validate the credentials by using principal context method.
var isAuthenticated = principalContext.ValidateCredentials(userName, password);
if (!isAuthenticated)
{
throw new Exception("Invalid your name and passowrd");
}
}
else
throw new Exception("InvalidUsernamePassword");
}
catch (DirectoryServicesCOMException cex)
{
string errorCode = cex.ExtendedErrorMessage.Substring(cex.ExtendedErrorMessage.IndexOf("data", 1));
errorCode = errorCode.Substring(5, 3);
//The commented code below fails to parse properly and throws an exception
// int exErrorCode = int.Parse(errorCode);
int exErrorCode = int.Parse(Regex.Match(errorCode, #"\d+").Value);
if (exErrorCode == (int)PWDFlags.Account_Expired)
throw new Exception("AdminACCExpire");
if (exErrorCode == (int)PWDFlags.Password_Expiration)
throw new Exception("AdminPWDExprire");
else
{
//Else any exception
}
}
}
}
catch (Exception)
{
throw;
}
}
private PrincipalContext GetPrincipalContext()
{
// Creating Principal Context.
PrincipalContext principalContext = null;
string serveraddress = _directoryServerInfo.IPAddress;//+":"+_defdirectoryport ;
if (string.IsNullOrEmpty(_directoryfilterouname))
{
principalContext = new PrincipalContext(ContextType.Domain, serveraddress, _directoryAdminUserId, _directoryAdminPassword);
//principalContext = new PrincipalContext(ContextType.Domain, _directoryServerInfo.IPAddress , _directoryAdminUserId, _directoryAdminPassword);
}
else
{
//string domainComponents = GetDomainComponents();
principalContext = new PrincipalContext(ContextType.Domain, serveraddress, _directoryfilterouname, _directoryAdminUserId, _directoryAdminPassword);
}
//// _directoryServerInfo.HostName = //((System.DirectoryServices.AccountManagement.ADStoreCtx)(principalContext.QueryCtx)).DnsDomainName;
return principalContext;
}
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;
}
I'm trying to remove a certain user from an Active Directory group using C#.
Here is my piece of code which should handle my task, even though it does not currently work.
public static bool RemoveUserFromGroup(string UserId, string GroupId)
{
using (var directory = new DirectoryEntry("LDAP://server"))
{
using (var dSearch = new DirectorySearcher(directory))
{
try
{
dSearch.Filter = "(sAMAccountName=" + UserId + ")";
SearchResult sr = dSearch.FindOne();
System.DirectoryServices.PropertyCollection UserProperties = sr.GetDirectoryEntry().Properties;
if(UserProperties == null)
return false;
foreach(object Group in UserProperties["memberOf"])
{
if(Group.ToString() == GroupId)
{
UserProperties["memberOf"].Remove(GroupId);
directory.CommitChanges();
directory.Close();
return true;
}
}
}
catch (Exception e)
{
return false;
}
}
}
return false;
}
Please excuse me if there are any typos within this code, I had to manually copy it from the machine I am developing on, which has no internet access sadly.
Use:
public void RemoveUserFromGroup(string userId, string groupName)
{
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "COMPANY"))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, groupName);
group.Members.Remove(pc, IdentityType.UserPrincipalName, userId);
group.Save();
}
}
catch (System.DirectoryServices.DirectoryServicesCOMException E)
{
//doSomething with E.Message.ToString();
}
}
public string RemoveUserFromList(string UserID, string ListName)
{
try
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "DomainName", UserName, Password))
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(pc, ListName);
group.Members.Remove(pc, IdentityType.SamAccountName, UserID);
group.Save();
}
return "Success";
}
catch (Exception ex)
{
return ex.Message;
}
}
I'd like check the the login and the password match with the AD info. I tried with this piece of coode but I get an exception on FindOne (bad username or password .. but they are correct). I know there is the PrincipalContext solution but I need to be able to set the server (Production, Dev, ...)
Thanks,
var Ad = new DirectoryEntry("LDAP://server1.domain.com", username, password);
var AdSearcher = new DirectorySearcher(Ad);
AdSearcher.Filter = String.Format("(anr={0})", username);
AdSearcher.PropertiesToLoad.Add("sAMAccountName");
AdSearcher.PropertiesToLoad.Add("displayName");
var AdSearcherResults = AdSearcher.FindOne();
var userFullName = AdSearcherResults.Properties["displayName"][0].ToString();
var userUid = AdSearcherResults.Properties["sAMAccountName"][0].ToString();
if (Membership.ValidateUser(username, userUid))
return true;
return false;
Update1 I tried this too :
using (var context = new PrincipalContext(ContextType.Domain, "server1.domain.com"))
{
var isValid = context.ValidateCredentials(username, password);
}
My computer is not connected on the domain but should be work I think.
My code for ActiveDirectory Auth.
public DirectoryEntry connDirectory(string usr, string pwd)
{
string ip = iniMan.IniRead("LDAP", "adres");
DirectoryEntry oDE;
oDE = new DirectoryEntry(ip, usr, pwd, AuthenticationTypes.Secure);
return oDE;
}
public bool AD_Login(string kullanici_adi, string sifre)
{
try
{
DirectoryEntry entLogin = connDirectory(kullanici_adi, sifre);
object loginObj = entLogin.NativeObject;
return true;
}
catch (Exception ex)
{
return false;
}
}
void TestMetod(){
if(AD_Login("ozan","ozan"){
//ok
}
}