Good morning, I am developing an application with .net 6 to change and reset passwords through LDAP but it gives me an access denied exception to use "ResetPassword" I tried with the administrator user and it didn't work either, could someone help me or guide me on what may be wrong in code.
excepsion
My code is
public string cambiarPass(string usuarioAdmin, string adminPass, string usuarioC, string nuevaPass)
{
try
{
DirectoryEntry domainEntry = new DirectoryEntry(path, usuarioAdmin, adminPass);
domainEntry.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher dirSearcher = new DirectorySearcher(domainEntry);
//string filter = string.Format("(SAMAccountName={0})", usuarioC);
//dirSearcher.Filter = "(samaccountname=" + usuarioC + ")";
dirSearcher.Filter = string.Format("(sAMAccountName={0})", usuarioC);
dirSearcher.SearchScope = SearchScope.Subtree;
dirSearcher.CacheResults = false;
//dirSearcher.Filter = filter;
SearchResult result = dirSearcher.FindOne();
if (result != null)
{
DirectoryEntry userEntry = result.GetDirectoryEntry();
//Enable Account if it is disabled
// userEntry.Properties["userAccountControl"].Value = 0x200;
//Reset User Password
userEntry.Invoke("SetPassword", new object[] { nuevaPass });
//forzar la contraseƱa al cambio de contraseƱa en el siguiente inicio de sesion
userEntry.Properties["LockOutTime"].Value = 0;
userEntry.Properties["pwdlastset"][0] = 0;
userEntry.CommitChanges();
// userEntry.Close();
guardaError = "a";
}
else
{
guardaError = "b";
}
}
catch (Exception ex)
{
guardaError = ex.ToString();
}
return guardaError;
}
I hope you can help me, greetings!
Related
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 :)
I have ASP .NET MVC 4 websites which uses default membership provider, the website works fine, now I am developing a separate WCF Rest service to expose the existing DB to android app, below is the function I am using to authenticate a user in WCF service coming from android but it does not get authenticated successfully I don't know what I am doing wrong here.
public static OperationResult Authenticate(string Username, string Password)
{
SqlConnection sqlCon = new SqlConnection(Params.GetConnectionString());
SqlCommand sqlCmd = new SqlCommand("SELECT Memberships.UserId,Password,PasswordSalt From Memberships " +
"INNER JOIN Users on Memberships.UserID = Users.UserID WHERE " +
"UserName = #Username OR Email = #Username", sqlCon);
sqlCmd.Parameters.Add("#Username", SqlDbType.NVarChar, 256).Value = Username;
OperationResult OR = new OperationResult();
Guid UserID = Guid.Empty;
string OriginalHash = string.Empty;
string SaltValue = string.Empty;
try
{
sqlCon.Open();
SqlDataReader reader = sqlCmd.ExecuteReader();
while (reader.Read())
{
UserID = reader.GetGuid(0);
OriginalHash = reader.GetString(1);
SaltValue = reader.GetString(2);
break;
}
reader.Close();
// username exists
if (UserID.CompareTo(Guid.Empty) != 0)
{
// compare password hashes
byte[] bIn = Encoding.Unicode.GetBytes(Password);
byte[] bSalt = Convert.FromBase64String(SaltValue);
byte[] bAll = new byte[bSalt.Length + bIn.Length];
byte[] bRet = null;
Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
HashAlgorithm s = HashAlgorithm.Create("SHA1");
bRet = s.ComputeHash(bAll);
string newHash = Convert.ToBase64String(bRet);
// check the hash in the datbase matched the new hash we generated
if (OriginalHash != newHash)
{
OR.Success = false;
OR.Messages = new string[] { "Incorrect Username/Password combination. Please try again" };
}
else
{
OR.Success = true;
OR.Messages = new string[] { "Success" };
}
}
else
{
OR.Success = false;
OR.Messages = new string[] { "Incorrect Username/Password combination. Please try again" };
}
}
catch (Exception ex)
{
OR.Success = false;
OR.Messages = new string[] { "authentication failed: " + ex.Message };
}
finally
{
sqlCon.Close();
}
return OR;
}
I'm trying to add an user to the active directory via a C# script. I've found this script on the internet (I didn't made it myself). The problem is that I get this error when I'm trying to add an user:
The specified directory service attribute or value does not exist.
This is the code I have right now:
private void buttonCreateUser_Click(object sender, EventArgs e)
{
CreateADSUser(textboxUsername.Text, textboxPassword.Text);
}
public string CreateADSUser(string username, string password)
{
String RootDSE;
try
{
DirectorySearcher DSESearcher = new DirectorySearcher();
RootDSE = DSESearcher.SearchRoot.Path;
RootDSE = RootDSE.Insert(7, "CN=Users,");
DirectoryEntry myDE = new DirectoryEntry(RootDSE);
DirectoryEntries myEntries = myDE.Children;
DirectoryEntry myDirectoryEntry = myEntries.Add("CN=" + username, "user");
myDirectoryEntry.Properties["userPrincipalName"].Value = username;
myDirectoryEntry.Properties["name"].Value = username;
myDirectoryEntry.Properties["Password"].Value = password;
myDirectoryEntry.Properties["samAccountName"].Value = username;
myDirectoryEntry.Properties["FullName"].Value = username;
myDirectoryEntry.Properties["AccountDisabled"].Value = 0;
myDirectoryEntry.Properties["PasswordRequired"].Value = 1;
// Permanent Password?
myDirectoryEntry.Properties["permpass"].Value = 1;
myDirectoryEntry.CommitChanges();
DSESearcher.Dispose();
myDirectoryEntry.Dispose();
textboxReports.Text = "Worked!";
return "Worked!";
}
catch (Exception ex)
{
textboxReports.Text = ex.Message;
return ex.Message;
}
}
Nevermind, I've got the fix!
This is what it looks like right now:
using (var pc = new PrincipalContext(ContextType.Domain))
{
using (var up = new UserPrincipal(pc))
{
up.SamAccountName = textboxUsername.Text; // Username
up.EmailAddress = textboxEmail.Text; // Email
up.SetPassword(textboxPassword.Text); // Password
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
}
The issue here is that none of these properties actually exist:
myDirectoryEntry.Properties["Password"].Value = password;
myDirectoryEntry.Properties["FullName"].Value = username;
myDirectoryEntry.Properties["AccountDisabled"].Value = 0;
myDirectoryEntry.Properties["PasswordRequired"].Value = 1;
myDirectoryEntry.Properties["permpass"].Value = 1;
This one isn't one you write to:
myDirectoryEntry.Properties["name"].Value = username;
In order (from top to bottom) here are the actual attribute names:
Password - unicodePwd
FullName - displayName
AccountDisabled - userAccountControl
PasswordRequired - userAccountControl (actually you set the inverse - only if a password isn't required)
permPass - unicodePwd (not sure what the goal was with this one)
By System.DirectoryServices.AccountManagement ..
PrincipalContext ouContex = new PrincipalContext(ContextType.Domain, "TestDomain.local", "OU=TestOU,DC=TestDomain,DC=local");
for (int i = 0; i < 3; i++)
{
try
{
UserPrincipal up = new UserPrincipal(ouContex);
up.SamAccountName = "TestUser" + i;
up.SetPassword("password");
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
catch (Exception ex)
{
}
}
By System.DirectoryServices
To use this namespace you need to add reference System.DirectoryServices.dll
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 3; i < 6; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}
I am a new web developer and in my company all the ASP.NET applications rely on a system that pulls down the user information from the SAP system and retrieve them as a XML document. This system has only one textbox for inserting the username to retrieve his information. An example of it:
If you insert the username: johnA
the system will provide you with the following information:
John
Arneson
Elli
and so on.
Then, in the ASP.NET web-based applications, we used to use three C# classes that does the connections with this system and helpes in getting the specific user information from that system.
Anyway, Now we want to replace that system with a new system that gets the user information from the Active Directory.
I put the following code in the server and it works well, so when the employee goes to this code in our server, he will see a page that shows all of his information. What I want to do right now is utilizing this in all our developed and the next new-developed web-based applications by putting a TextBox to put the username of the user and retrieve of all of his information from this system. So how to do that?
I am a beginner and I could not be able to find a way for doing this in Google or anywhere else.
My code of the class for accessing the Active Directory is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.DirectoryServices;
/// <summary>
/// Summary description for ActiveDirectory
/// </summary>
public class ActiveDirectory
{
public static string GetPersonData(string id, string datatype)
{
//return "x xx xxxx xxx xxx"; //comment this line.
//Faster performance through Active Directory
string property = datatype;
switch (property) //backwards compatibility
{
/* case "tel":
return FindProperty(id, Property.Tel);*/
case "name":
return FindProperty(id, Property.Name);
case "dept":
return FindProperty(id, Property.Dept);
case "line":
return FindProperty(id, Property.Line);
case "email":
return FindProperty(id, Property.Email);
case "id":
return FindProperty(id, Property.Name);
default:
return "";
}
}
//ACTIVE DIRECTORY OPTION.. FOR A BETTER PERFORMANCE
const string ID = "cn";
const string NAME = "displayName";
const string TEL = "telephoneNumber";
const string DEPT = "department";
const string LINE = "extensionAttribute3";
const string UNIT = "extensionAttribute10";
const string TITLE = "title";
const string FNAME = "givenName";
const string MNAME = "initials";
const string LNAME = "sn";
const string EMAIL = "mail";
const string AREA = "extensionAttribute3";
const string MANAGER = "manager";
const string ORGCODE = "extensionAttribute10";
const string DN = "distinguishedName";
public enum Property
{
Name, Tel, Dept, Line, Unit, Title, Fname, Mname, Lname, Email, Manager, OrgCode, DistinguishedName
}
public static DirectoryEntry GetDirectoryEntry()
{
using (((System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity).Impersonate())
{
DirectoryEntry de = new DirectoryEntry(); //DirectoryEntry class encapsulates a node or object in the AD hierarchy
de.Path = "LDAP://CompanyName.COM";
de.AuthenticationType = AuthenticationTypes.Delegation;
return de;
}
}
public static bool UserExists(string username)
{
DirectoryEntry de = GetDirectoryEntry();
DirectorySearcher deSearch = new DirectorySearcher(); //Directory Searcher: It will perform queries against the active directory hierarchy
deSearch.SearchRoot = de; //SearchRoot is used to specify where the search starts
deSearch.Filter = "(&(objectClass=user) (cn=" + username + "))"; //the search retrieves all objects.
// Create a SearchResultCollection object to hold a collection of SearchResults
// returned by the FindAll method.
SearchResultCollection results = deSearch.FindAll();
return results.Count > 0;
}
public static String FindName(String userAccount)
{
DirectoryEntry entry = GetDirectoryEntry();
String account = userAccount.Replace(#"Domain\", "");
try
{
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + account + ")";
search.PropertiesToLoad.Add("displayName");
SearchResult result = search.FindOne();
if (result != null)
{
return result.Properties["displayname"][0].ToString();
}
else
{
return "Unknown User";
}
}
catch (Exception ex)
{
string debug = ex.Message;
return debug;
}
}
public static String FindProperty(String userAccount, Property p)
{
string property = "";
//proceed with LDAP search.
switch (p)
{
case Property.Dept:
property = DEPT;
break;
case Property.Email:
property = EMAIL;
break;
case Property.Fname:
property = FNAME;
break;
case Property.Line:
property = LINE;
break;
case Property.Lname:
property = LNAME;
break;
case Property.Mname:
property = MNAME;
break;
case Property.Name:
property = NAME;
break;
case Property.Tel:
property = TEL;
break;
case Property.Title:
property = TITLE;
break;
case Property.Unit:
property = UNIT;
break;
case Property.Manager:
property = MANAGER;
break;
case Property.OrgCode:
property = ORGCODE;
break;
case Property.DistinguishedName:
property = DN;
break;
default:
return "";
}
DirectoryEntry entry = GetDirectoryEntry();
String account = userAccount.Replace(#"Domain\", "");
try
{
System.Text.Encoding enc = System.Text.Encoding.ASCII;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(&(objectCategory=user)(SAMAccountName=" + account + "))";
search.PropertiesToLoad.Add(property);
SearchResult result = search.FindOne();
search.Dispose();
entry.Close();
entry.Dispose();
if (result != null)
{
object value = result.Properties[property][0];
if (value is System.Byte[])
return enc.GetString((byte[])value);
else
return value.ToString();
}
else
{
return "-";
}
}
catch (Exception ex)
{
string debug = ex.Message;
return "debug";
}
}
public static List<string> FindChildren(string userAccount)
{
DirectoryEntry entry = GetDirectoryEntry();
String account = userAccount.Replace(#"Domain\", "");
string dn = FindProperty(userAccount, Property.DistinguishedName);
dn.Replace("*","\\2a");
dn.Replace("(", "\\28");
dn.Replace(")", "\\29");
dn.Replace("\\", "\\5c");
dn.Replace("NUL", "\\00");
dn.Replace("/", "\\2f");
string property = ID;
List<string> output = new List<string>();
try
{
System.Text.Encoding enc = System.Text.Encoding.ASCII;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(&(objectCategory=user)(manager=" + dn + "))";
search.PropertiesToLoad.Add(property);
SearchResultCollection results = search.FindAll();
search.Dispose();
entry.Close();
entry.Dispose();
if (results != null)
{
foreach (SearchResult result in results)
{
object value = result.Properties[property][0];
if (value is System.Byte[])
output.Add(enc.GetString((byte[])value));
else
output.Add(value.ToString());
}
}
}
catch (Exception ex)
{
throw ex;
}
return output;
}
public static string FindOrg(string orgcode, string property)
{
DirectoryEntry entry = GetDirectoryEntry();
try
{
System.Text.Encoding enc = System.Text.Encoding.ASCII;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(&(objectCategory=user)(" + ORGCODE + "=" + orgcode + "*))";
search.PropertiesToLoad.Add(property);
SearchResult result = search.FindOne();
search.Dispose();
entry.Close();
entry.Dispose();
if (result != null)
{
object value = result.Properties[property][0];
if (value is System.Byte[])
return enc.GetString((byte[])value);
else
return value.ToString();
}
else
{
return "-";
}
}
catch (Exception ex)
{
string debug = ex.Message;
return "debug";
}
}
}
UPDATE:
For your information, the above classes are on the server. Now, I am developing a new web-based application. And in this web-based application, I have a textbox that I will use to enter the username. So how I will be able to send this username to that system and retrieve the user information for it to this application? Could you please provide me with an example?
Ok understand now what you really need. The best way for this to be accomplish is by using asp.net webservices. What this means is that you must make some edits to your code that is currently running on your server.
Please look up Asp.net Web Services and check out these links: Microsoft Web Services weblink and Create and Use ASP.NET Web Service
If you're on .NET 3.5 and up, you should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. 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:
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, "SomeUserName");
if(user != null)
{
// do something here....
}
// find the group in question
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "YourGroupNameHere");
// if found....
if (group != null)
{
// iterate over members
foreach (Principal p in group.GetMembers())
{
Console.WriteLine("{0}: {1}", p.StructuralObjectClass, p.DisplayName);
// do whatever you need to do to those members
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
And as "Nation" mentioned in his response, if you "hide" this behind a web service interface, then all sorts of applications can call into your code and get the information they need out of Active Directory!
I'm writing a web application which uses windows authentication and I can happily get the user's login name using something like:
string login = User.Identity.Name.ToString();
But I don't need their login name I want their DisplayName. I've been banging my head for a couple hours now...
Can I access my organisation's AD via a web application?
How about this:
private static string GetFullName()
{
try
{
DirectoryEntry de = new DirectoryEntry("WinNT://" + Environment.UserDomainName + "/" + Environment.UserName);
return de.Properties["displayName"].Value.ToString();
}
catch { return null; }
}
See related question: Active Directory: Retrieve User information
See also: Howto: (Almost) Everything In Active Directory via C# and more specifically section "Enumerate an object's properties".
If you have a path to connect to a group in a domain, the following snippet may be helpful:
GetUserProperty("<myaccount>", "DisplayName");
public static string GetUserProperty(string accountName, string propertyName)
{
DirectoryEntry entry = new DirectoryEntry();
// "LDAP://CN=<group name>, CN =<Users>, DC=<domain component>, DC=<domain component>,..."
entry.Path = "LDAP://...";
entry.AuthenticationType = AuthenticationTypes.Secure;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + accountName + ")";
search.PropertiesToLoad.Add(propertyName);
SearchResultCollection results = search.FindAll();
if (results != null && results.Count > 0)
{
return results[0].Properties[propertyName][0].ToString();
}
else
{
return "Unknown User";
}
}
Use this:
string displayName = UserPrincipal.Current.DisplayName;
In case anyone cares I managed to crack this one:
/// This is some imaginary code to show you how to use it
Session["USER"] = User.Identity.Name.ToString();
Session["LOGIN"] = RemoveDomainPrefix(User.Identity.Name.ToString()); // not a real function :D
string ldappath = "LDAP://your_ldap_path";
// "LDAP://CN=<group name>, CN =<Users>, DC=<domain component>, DC=<domain component>,..."
Session["cn"] = GetAttribute(ldappath, (string)Session["LOGIN"], "cn");
Session["displayName"] = GetAttribute(ldappath, (string)Session["LOGIN"], "displayName");
Session["mail"] = GetAttribute(ldappath, (string)Session["LOGIN"], "mail");
Session["givenName"] = GetAttribute(ldappath, (string)Session["LOGIN"], "givenName");
Session["sn"] = GetAttribute(ldappath, (string)Session["LOGIN"], "sn");
/// working code
public static string GetAttribute(string ldappath, string sAMAccountName, string attribute)
{
string OUT = string.Empty;
try
{
DirectoryEntry de = new DirectoryEntry(ldappath);
DirectorySearcher ds = new DirectorySearcher(de);
ds.Filter = "(&(objectClass=user)(objectCategory=person)(sAMAccountName=" + sAMAccountName + "))";
SearchResultCollection results = ds.FindAll();
foreach (SearchResult result in results)
{
OUT = GetProperty(result, attribute);
}
}
catch (Exception t)
{
// System.Diagnostics.Debug.WriteLine(t.Message);
}
return (OUT != null) ? OUT : string.Empty;
}
public static string GetProperty(SearchResult searchResult, string PropertyName)
{
if (searchResult.Properties.Contains(PropertyName))
{
return searchResult.Properties[PropertyName][0].ToString();
}
else
{
return string.Empty;
}
}
There is a CodePlex project for Linq to AD, if you're interested.
It's also covered in the book LINQ Unleashed for C# by Paul Kimmel - he uses the above project as his starting point.
not affiliated with either source - I just read the book recently