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();
}
I got this register form where i get the user email and password and hash the password using SHA512
public Boolean IsRegistered(String email, String pass)
{
SHA512 shaM = new SHA512Managed();
if (pass.Length > 0 && email.Length > 0)
{
byte[] data = Encoding.UTF8.GetBytes(pass);
String encryptedpass = Encoding.UTF8.GetString(shaM.ComputeHash(data));
using (ModelContainer db = new ModelContainer())
{
//User usr = db.UserSet.Where(u => u.PasswordDigest == encryptedpass && u.Email == email).First();
int matches = (from u in bd.UserSet
where u.PasswordDigest == encryptedpass&& u.Email == email
select new
{
Id = u.Id
}
).Count();
if (matches > 0)
{
return true;
}
}
}
return false;
}
I use this method each time the user logs in and it works like a charm (i guess),
thing is when i prompt the user to change his/her password i cannot seem to be able to validate the old one here is what i try
I do the following to retrive the user data on the MyAccount form's constructor
User user;.
public MyAccount()
{
InitializeComponent();
try
{
using (ModelContainer db = new ModelContainer())
{
user = (from u in db.UserSet where u.Id == 2 select u).First();
txtName.Text = user.Name;
txtEmail.Text = user.Email;
}
}
catch (Exception x)
{
ErrorAlert error = new ErrorAlert("Error: " + x.Message);
error.Owner = getParentWindow();
error.ShowDialog();
}
}
then I validate it on the forms button_click
using (ModelContainer db = new ModelContainer())
{
SHA512 shaM = new SHA512Managed();
string oldpass = Encoding.UTF8.GetString(shaM.ComputeHash(Encoding.UTF8.GetBytes(ptxtOldPassword.Password)));
shaM.Dispose();
db.UserSet.Attach(user);
Regex rgx = new Regex(#"\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z");
if (rgx.IsMatch(txtEmail.Text))
{
if (oldpass == user.PasswordDigest)
{
if (ptxtNewPassword.Password.Equals(ptxtNewPassword2.Password))
{
string newpass = Encoding.UTF8.GetString(shaM.ComputeHash(Encoding.UTF8.GetBytes(ptxtNewPassword.Password)));
user.Name = txtName.Text;
user.Email = txtEmail.Text;
user.PasswordDigest = newpass;
db.SaveChanges();
}
else
{
ErrorAlert error = new ErrorAlert("Passwords do not match");
error.Owner = getParentWindow();
error.ShowDialog();
}
When I comapare the old password in the database with the one the user enter they do not match since they are strings I've tried using equals with no luck I thought == would work but I was wrong, i looked into other answers and found this Sha512 not returning equal in c# hash validation sadly it didn't work for me, I need to understand why my first validation work and the second doesnt
so any help is apreciated Have a nice day
You don't really need to compare the final strings, test at the bytes-level. Check this previous question.
Also, if you already validated the existence of the user (by email or any other mechanism), why don't just change / update with the new password? You could validate with the email and re-use the working function for login / signin.
Below is Code:
public ActionResult DownloadPdf(int id, int pid)
{
string userName, password = string.Empty;
userName = ConfigurationManager.AppSettings["AdminUsername"];
password = ConfigurationManager.AppSettings["AdminPassword"];
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password) && id > 0 && pid > 0)
{
var pdf = new Rotativa.ActionAsPdf("PdfPartial", new { id = id, pid = pid })
{
UserName=userName,
Password=password,
FileName = "AFE.pdf",
};
return pdf;
}
else
{
return new Rotativa.ActionAsPdf("PdfError")
{
FileName = "AFE.pdf",
};
}
}
I am using windows authentication with Active Directory. I don't want to use username and password in web.config.Is it any way to achieve this? It can be possible if there is any way to access specific mvc action anonymously while using windows authentication.
I want to create LogIn button through Active Directory.
So i have an idea to take Name logged user(Windows) from his Domain:
string Name = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
and then take Group for Login above:
string Group = System.Security.Principal.WindowsIdentity.GetCurrent().Groups.ToString(); // <---I think this is wrong ?
string allowedGroup = "Admins";
then something like:
if(Name == string.Empty)
{
MessageBox.Show("Your Name in domain doesn't exist");
}
if(Group.ToString() != allowedGroup)
{
MessageBox.Show("You don't have permissions to log in");
}
else
{
MessageBox.Show("Hello");
}
I think my 'getting group' is wrong. How can I do it? I don't know how to exactly search for one or two groups where User is assigned.
What about when user is assigned to many Groups?
Here is the point to use windows identity to authorize login.
1) Get the windows identity of user.
2) Use Windows identity object to get the other information like name and group.
use group name to validate user request.
Hope this will help you. Please write in comment in you have any questions.
System.Security.Principal.WindowsIdentity WI = System.Security.Principal.WindowsIdentity.GetCurrent();
string sUserName = WI.Name;
bool bAuthorized = false;
string allowedGroup = "Admins";
IdentityReferenceCollection irc = WI.Groups;
foreach (IdentityReference ir in irc)
{
if(ir.Translate(typeof(NTAccount)).Value == allowedGroup)
{
bAuthorized = true;
break;
}
}
if(string.IsNullOrEmpty(sUserName))
{
MessageBox.Show("Your Name in domain doesn't exist");
}
if(bAuthorized == false)
{
MessageBox.Show("You don't have permissions to log in");
}
else
{
MessageBox.Show("Hello");
}
Ok, i got this. Thanks for Pankaj.
System.Security.Principal.WindowsIdentity WI = System.Security.Principal.WindowsIdentity.GetCurrent();
string sUserName = WI.Name;
bool bAuthorized = false;
string allowedGroup = "Admins";
IdentityReferenceCollection irc = WI.Groups;
foreach (IdentityReference ir in irc)
{
NTAccount accInfo = (NTAccount)ir.Translate(typeof(NTAccount));
if (accInfo.Value == allowedGroup)
{
bAuthorized = true;
break;
}
}
if(string.IsNullOrEmpty(sUserName))
{
MessageBox.Show("Your Name in domain doesn't exist");
}
if(bAuthorized == false)
{
MessageBox.Show("You don't have permissions to log in");
}
else
{
MessageBox.Show("Hello");
}
I am trying create Activity record in Dynamics 2011 with owner name set as "test_user". Instead below code is taking credential which is used to access Dynamics API. Is there any way to impersonate "test_user" user without passing his password? Thank you.
string TargetCrmService = ConfigurationManager.AppSettings["TargetCrmService"];
string UserName = ConfigurationManager.AppSettings["UserName"];
string Domain = ConfigurationManager.AppSettings["Domain"];
string Password = ConfigurationManager.AppSettings["Password"];
Uri organizationUri = new Uri("http://CRMDEV/XRMServices/2011/Organization.svc");
Uri homeRealmUri = null;
ClientCredentials credentials = new ClientCredentials();
credentials.UserName.UserName = Domain + "\\" + UserName;
credentials.UserName.Password = Password;
OrganizationServiceProxy orgProxy = new OrganizationServiceProxy(organizationUri, homeRealmUri, credentials, null);
var _userId = (from u in orgProxy.CreateQuery<SystemUser>()
where u.FullName == "Kevin Cook"
select u.SystemUserId.Value).FirstOrDefault();
IOrganizationService _service = (IOrganizationService)orgProxy;
_service.CallerId = _userId;
try
{
//Entity activity = new Entity("activitypointer");
Entity activity = new Entity("appointment");
activity["subject"] = "Test Meeting 1";
activity["description"] = "Test Description";
activity["scheduledstart"] = DateTime.Now;
activity["scheduledend"] = DateTime.Now.AddMinutes(30);
activity["createdbyname"] = "test_user";
activity["modifiedbyname"] = "test_user";
activity["createdbyname"] = "test_user";
activity["owneridname"] = "test_user";
Guid id = _service.Create(activity);
Console.WriteLine("id: " + id);
}
catch (Exception ex)
{
//MessageBox.Show(ex.Message);
}
Modified Code
Based on example on http://msdn.microsoft.com/en-us/library/gg309629.aspx
var _userId = (from u in orgProxy.CreateQuery<SystemUser>()
where u.FullName == "Kevin Cook"
select u.SystemUserId.Value).FirstOrDefault();
You have two methods of setting the owenr id of an entity in CRM.
Use the AssignRequest to update the record after it is created.
Use impersonation with the OrganizationServiceProxy, CallerId to use the particular user you'd like to be the owner when it's created. You don't need their password to do impersonation, just their CRM SystemUserId