In Sharepoint (or any ASP.NET web application) I want to have a function to create AD users. I'm using System.DirectoryServices.AccountManagement for this task, but I'm getting into trouble. Here is my code:
using (var pc = new PrincipalContext(ContextType.Domain,"DOMAIN","administrator","password"))
{
if (pc.ValidateCredentials("administrator", "password"))
{
UserPrincipal up = new UserPrincipal(pc, username, password, true);
up.Save();
}
}
The user gets created but it is disabled. I know that my administrator:password pair is correct because "if" statement is returning true. Also during creation I receive Exception:
Exception has been thrown by the target of an invocation.
I checked PrincipalContext object and it is connecting to domain controller with "administrator" account. What could be the reason of this error and up.Save() function throwing Exception ?
Can you try the following:
using (var pc = new PrincipalContext(ContextType.Domain,"DOMAIN","administrator","password"))
{
using (var up = new UserPrincipal(pc))
{
up.SamAccountName = textboxUsername.Text; // Username
up.SetPassword(textboxPassword.Text); // Password
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
}
This should at least make sure that the user is created and is enabled. Let me know if it works.
Related
I am developing my Razor.Pages web application in .Net Core 3.1 and I configured the authentication using my company AD. I can use without any problem the User.Identity.Name to get the user#domain value but I need to get the full name of the person that is logged in so that I can filter some results of a query to an SQL DB based on the user's full name.
I tried googling around but didn't find anything a solution to my problem. Thanks!
After doing some digging around I finally managed to create a method that receives the User.Identity.Name of the logged in user and returns the full name.
Bellow is a snippet of the method!
public static string GetFullName(string domainName)
{
string fullName = "";
UserPrincipal principal;
using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain))
{
principal = UserPrincipal.FindByIdentity(ctx, domainName);
}
if (principal != null)
fullName = $"{principal.GivenName} {principal.Surname}";
else
fullName = domainName;
return fullName;
}
I'm trying to connect to a CRM 2011 Online environment. I'm able to connect via a "Console Application", but when I'm trying to connect via an "ASP.net"-application with the same code, it doesn't work, it gives me the "Authentication Failure"-error ({"An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail."}).
Is there something special we need to do to make it work on an "ASP.net" environment. I tested out several solutions I found on the internet, but all gives me the same error.
A "code"-snippet of my simplified code:
private static ClientCredentials GetDeviceCredentials()
{
return Microsoft.Crm.Services.Utility.DeviceIdManager.LoadOrRegisterDevice();
}
protected void Button1_Click(object sender, EventArgs e)
{
//Authenticate using credentials of the logged in user;
string UserName = "*****"; //your Windows Live ID
string Password = "*****"; // your password
ClientCredentials Credentials = new ClientCredentials();
Credentials.UserName.UserName = UserName;
Credentials.UserName.Password = Password;
Credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
//This URL needs to be updated to match the servername and Organization for the environment.
Uri OrganizationUri = new Uri("https://*****.crm4.dynamics.com/XRMServices/2011/Organization.svc"); //this URL could copy from Setting --> Developer Source
Uri HomeRealmUri = null;
//OrganizationServiceProxy serviceProxy;
using (OrganizationServiceProxy serviceProxy = new OrganizationServiceProxy(OrganizationUri, HomeRealmUri, Credentials, GetDeviceCredentials()))
{
IOrganizationService service = (IOrganizationService)serviceProxy;
OrganizationServiceContext orgContext = new OrganizationServiceContext(service);
var theAccounts = orgContext.CreateQuery<Account>().Take(1).ToList();
Response.Write(theAccounts.First().Name);
}
}
I tried several things, like deleting the content of "LiveDeviceID"-folder an re-running the device registration tool. but is weird that it works in the "console application" but not on my "asp.net"-solution...
PS : I am able to generate the "context"-file via crmsvcutil.exe /url:https://org.crm4.dynamics.com/XRMServices/2011/Organization.svc /o:crm.cs /u:username /p:password /di:deviceUserName /dp:devicPWD
Is there any particular reason you have
Credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
You shouldn't need that line for windows live authentication.
Even with that the code seems valid so it is something to do with the Device Registration. I suggest rather than just call it directly like you have
using (OrganizationServiceProxy serviceProxy = new OrganizationServiceProxy(OrganizationUri, HomeRealmUri, Credentials, GetDeviceCredentials()))
{
You try something like the following because you only need to register once:
ClientCredentials deviceCredentials;
if ((CRMSettings.Default.DeviceID == String.Empty) || (CRMSettings.Default.DevicePassword == String.Empty))
{
deviceCredentials = Microsoft.Crm.Services.Utility.DeviceIdManager.RegisterDevice();
}
else
{
deviceCredentials = new ClientCredentials();
deviceCredentials.UserName.UserName = CRMSettings.Default.DeviceID;
deviceCredentials.UserName.Password = CRMSettings.Default.DevicePassword;
}
using (OrganizationServiceProxy serviceProxy = new OrganizationServiceProxy(OrganizationUri, HomeRealmUri, Credentials, deviceCredentials))
{
I have had issues in the past where I get an "already registered" response from the RegisterDevice call.
I would also dump out the Device ID and Password so you can see if they are being set.
Recently I moved my ASP.NET application from an old server running IIS5 to a new server running IIS7.5.
The application gives me an error:
The (&(objectCategory=person)(sAMAccountName=)) search filter is invalid.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.ArgumentException: The (&(objectCategory=person)(sAMAccountName=)) search filter is invalid.
The function that searches AD is:
public static string Get_AD_User_Email(string username)
{
try
{
DirectorySearcher searcher = new DirectorySearcher("(&(objectCategory=person)(sAMAccountName=" + username + "))");
SearchResult result = searcher.FindOne();
if (result != null)
{
DirectoryEntry employee = result.GetDirectoryEntry();
if (employee.Properties["mail"] != null)
{
return employee.Properties["mail"].Value.ToString();
}
else return "NULL";
}
else throw new Exception("ERROR: Problem searching active directory for users.");
}
catch (Exception ex) { throw ex; }
}
The weird thing is that on debug in Visual Studio the website is running, only from IIS it's crashes.
Can someone help me?
The trouble is just that your function Get_AD_User_Email(string username) is called with an empty value for username.
Since:
DirectorySearcher searcher = new DirectorySearcher("(&(objectCategory=person)(sAMAccountName=" + username + "))");
Is returning the exception:
The (&(objectCategory=person)(sAMAccountName=)) search filter is
invalid.
You are passing an empty string to Get_AD_User_Email.
How are you retrieving "username"?
You changed IIS servers, now no username is being passed in by the calling method (as several other answers point out).
I would verify that you have anonymous access disabled on that website in IIS. It's common to find both Windows authentication and anonymous access enabled. When this happens anonymous is preferred and you won't get the username.
Check the value of HttpContext.Current.User. I usually use code like the one below to verify windows authentication:
WindowsIdentity id = (WindowsIdentity)HttpContext.Current.User.Identity;
string username = id.Name;
Try:
objectClass=user
Instead of:
objectCategory=person
I'm trying to authenticate against AD using application mode (ADAM), but keep getting unknown username or bad password. If I test the login in LDP.exe it logs in no problem, on simple bind. I've trawled through all similar posts with the same issue, but have not resolved it, any suggestions what I should be checking for?
private bool ValidateActiveDirectoryLogin(string Username, string Password)
{
bool Success = false;
System.DirectoryServices.DirectoryEntry Entry = new System.DirectoryServices.DirectoryEntry("LDAP://localhost:389/OU=Users,O=TestDirectory", Username, Password);
System.DirectoryServices.DirectorySearcher Searcher = new System.DirectoryServices.DirectorySearcher(Entry);
Searcher.SearchScope = System.DirectoryServices.SearchScope.Subtree;
try
{
System.DirectoryServices.SearchResult Results = Searcher.FindOne();
Success = (Results != null);
}
catch (Exception ex)
{
Success = false;
throw;
}
return Success;
}
Determine what context your application is hitting AD with. If your ASP.NET application pool identity is one that is low privileged, it won't have enough permissions to query active directory. If you don't want to create a custom user to run the app pool as with appropriate permissions - you could use the LogonUser API to make your ValidateActiveDirectoryLogin call under the security context of that account.
Finally, you should consider using System.DirectoryServices.AccountManagement if you are using .NET 3.5 or above.
You can use code like
bool validCreds = false;
using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
{
validCreds = context.ValidateCredentials( username, password );
}
I get an error by a website, on which I use Windows Authentication.
Strange things:
Only occurs if user is not yet saved into database (new unknown user)
Appears only on live system, everything fine on local development environment
This is what I get in a logging mail:
Source : System.DirectoryServices
Message: The server is not operational.
Trace:
at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail)
at System.DirectoryServices.DirectoryEntry.Bind()
at System.DirectoryServices.DirectoryEntry.get_AdsObject()
at System.DirectoryServices.DirectorySearcher.FindAll(Boolean findMoreThanOne)
at System.DirectoryServices.DirectorySearcher.FindOne()
at Smarthouse.Labs.DataAccess.UserListManager.SaveUser(String windowsUserName)
This is how I implement DirectorySearch:
private void SaveUser(string windowsUserName)
{
string[] domainAndUser = windowsUserName.Split('\\');
string domain = domainAndUser[0];
string username = domainAndUser[1];
DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain);
DirectorySearcher search = new DirectorySearcher(entry);
try
{
// Bind to the native AdsObject to force authentication.
search.Filter = "(SAMAccountName=" + username + ")";
search.PropertiesToLoad.Add("cn");
search.PropertiesToLoad.Add("sn");
search.PropertiesToLoad.Add("givenName");
search.PropertiesToLoad.Add("mail");
SearchResult result = search.FindOne();
if (result == null)
{
throw new Exception("No results found in Windows authentication.");
}
User userToSave = new User();
userToSave.FirstName = (String) result.Properties["givenName"][0];
userToSave.LastName = (String) result.Properties["sn"][0];
userToSave.Email = (String) result.Properties["mail"][0];
userToSave.Username = windowsUserName;
userToSave.Guid = Guid.NewGuid();
SaveUser(userToSave);
}
catch (Exception ex)
{
throw new Exception("Error authenticating user. " + ex.Message, ex);
}
finally
{
//Dispose service and search to prevent leek in memory
entry.Dispose();
search.Dispose();
}
}
If more code examples are needed just tell me.
Your problem is that you're using a "plain" domain name to bind - this won't work in LDAP. Actually, if you try to bind to LDAP://MyDomain, what you're really doing is trying to bind to the server called MyDomain.
You need a valid LDAP bind string - something like LDAP://dc=yourdomain,dc=local or something.
To find out what your default LDAP binding context is, use this code snippet:
DirectoryEntry deRoot = new DirectoryEntry("LDAP://RootDSE");
if (deRoot != null)
{
string defaultNamingContext = deRoot.Properties["defaultNamingContext"].Value.ToString();
}
Once you have that string - use that as your bind string to your LDAP server.
And 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 -- no domain name needed, uses default domain
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, username);
if(user != null)
{
// do something here....
}
The new S.DS.AM makes it really easy to play around with users and groups in AD!
You can use bind strings in the format LDAP://mydomain.com:389. I kept getting "Access is Denied" when trying to use the format LDAP://DC=mydomain,DC=com. Once I switched to the LDAP://mydomain.com:389 format, and bound using the AuthenticationTypes.ServerBind flag when constructing my DirectoryEntry, it worked great. This was in Azure App Service.
To add to marc_s's answer above, I needed to search multiple domains.
So for each Domain I did the following:
DirectoryEntry deRoot = new DirectoryEntry("LDAP://" +"DomainName"+ "/RootDSE");
string defaultNamingContext = "LDAP://" + deRoot.Properties["defaultNamingContext"].Value.ToString();
DirectoryEntry mySearchRoot = new DirectoryEntry(defaultNamingContext);
DirectorySearcher myDirectorySearcher = new DirectorySearcher(mySearchRoot);
Similar Error Happened to me (though it happened all the time and not in specific cases like pointed out here) because of a wrong Active Directory connection string. i used the corp instead the prod one .
Use something that works for another app in your organization if exists.