I have a Windows Service (running as the Local System user) that needs to validate a user based on username and password, in addition to checking if the user belongs to the group WSMA. My current code is like this:
var pc = new PrincipalContext(ContextType.Machine);
using (pc)
{
try
{
if (pc.ValidateCredentials(username, password))
{
using (var groupEntry = new DirectoryEntry("WinNT://./WSMA,group"))
{
foreach (object member in (IEnumerable)groupEntry.Invoke("Members"))
{
using (var memberEntry = new DirectoryEntry(member))
{
if (memberEntry.Path.ToLower().EndsWith(username.ToLower()))
{
return new LoginResult{ success = true };
}
}
}
}
}
return new LoginResult{ success = false };
}
catch (PrincipalOperationException poe)
{
if (poe.ErrorCode == -2147023688)
{
return new LoginResult { Success = false, ErrorMessage = "Password expired" };
}
throw poe;
}
}
This all works as it should, as long as I'm connected to the network, but if I plug out my network cable, then the ValidateCredentials call give me the following error message:
FileNotFoundException unhandeled by user code. The network path was not found.
I guess this has something to do with AD, but I only need to check the local users, and not domain users so a network access should not be required.
Any way to do this using the PrincipalContext, or some other way that will work in a disconnected scenario?
Here's a way to logon the User (and thus check that it's a valid user/pass): MSDN Link
I guess this should work disconnected, too, if you use a local account
Related
I am trying to build a registration section for a website (internal to my dept). Now to get new users registered, I built a form where user enters his employee id i.e. AD account name and then clicks a button to fetch his details. Which are later saved in database where registration requests are queued. Once those requests are approved by admin then only those users can use the application. Now the problem is that user is not logged in, so is it possible for non logged in user to fetch details from AD server. if it is then how.? Because when I tried the below listed code I am getting bad username or password error using FindOne function.
public string getProperties(string StaffCode, string property)
{
try
{
string result = "";
using (var de = new DirectoryEntry(_path))
using (var ds = new DirectorySearcher(de))
{
ds.Filter = string.Format("(sAMAccountName={0})", StaffCode);
ds.PropertiesToLoad.AddRange(new[] {
"sn", // last name
"givenName", // first name
"mail", // email
"telephoneNumber", // phone number
// etc - add other properties you need
});
var res = ds.FindOne();
if (res == null)
{
result = "noUserFound";
}
else
{
foreach (string propName in res.Properties.PropertyNames)
{
ResultPropertyValueCollection valueCollection = res.Properties[propName];
foreach (Object propertyValue in valueCollection)
{
if (propName == property)
{
result = propertyValue.ToString();
}
}
}
}
}
return result;
}
catch (Exception ex)
{
return "someErrorOccurred";
}
Please help me in overcoming this issue.
Thanks in advance
My guess is that the identity of the application pool you run this code under doesn't have enough priviledges to query the AD without authentication.
Specifically, start with replacing this constructor
using ( var de = new DirectoryEntry( _path ) )
with the one that takes admin's username/password in an explicit way
using ( var de = new DirectoryEntry( _path, username, password ) )
and make sure the username has enough priviledges to query the catalog.
If this works, you could possibly try to go back to the original version but you'd have to make sure the identity of the asp.net application pool has enough priviledges to query the AD but also, that the asp.net server is a part of the domain (if it is not, authentication without providing username/password in an explicit way will most likely not work).
I wrote C# code to get email from Active Directory. It is working fine on my local system, but after hosting I am not getting email address. Followings are the things I already tried -
Changed application pool identity to NetworkService
Enabled Windows and Digest Authentications (both at the same time and one by one too)
Code:
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "comppany.com" , "DC=compnay,DC=com", ContextOptions.Negotiate))
// tried above and below//(ContextType.Domain, System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName))
{
// validate the credentials
bool isValid = pc.ValidateCredentials(Uid, Pwd);
if (isValid)
{
try
{
using (UserPrincipal up = UserPrincipal.FindByIdentity(pc, Uid))
{
return up != null && !String.IsNullOrEmpty(up.EmailAddress) ? up.EmailAddress : string.Empty;
}
//return "Validate successfully.";
}
catch (Exception ex)
{
return ex.Message;
}
}
}
Also tried following -
using (var connection = new DirectoryEntry())
{
using (var search = new DirectorySearcher(connection)
{
Filter = "(samaccountname=" + Uid + ")",
PropertiesToLoad = { "mail" },
})
{
return (string)search.FindOne().Properties["mail"][0];
}
}
None of them are working after hosting the app in IIS7.0
Please help.
Thanks
It will be because your user (i.e. you) will have rights to read from Active Directory, but the IIS user and Network Service won't.
Put a try catch round the using statement thing and you should see this in the exception.
There are alternative PrincipalContext constructors that allow you to specify the details of the user to connect as, or you could change the IIS app pool to run as a user with rights - I'd go with the PrincipalContext way though.
As a quick test try this version of the PrincipalContext constructor - put your username and password in the username and password paramemetrs and see if it works when hosted in IIS - if this works then you need to come up with some way of passing the user details in via config. (Generally a service account with only the rights to read, whose password does not change often is used for this)
I'm working with the Active Directory APIs, and am attempting to connect to the server using the following code:
PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, (server + ":" + port), loginUsername, loginPassword);
Whenever an invalid login username or password are passed, an exception from the whole statement is not thrown, but the following code continues executing. On debugging, I have found that the PrincipalContext class however throws an error, as shown below:
Those are two properties contained within the class. On further examining the "ConnectedServer" property, the following is displayed in the debugger:
My problem is that since an error is not thrown externally, I am unsure of how to actually check for this error. I would like to show a simple error message if the username or password are invalid - basically finding a way to check if the above errors have been thrown.
How can this be done?
The classes of System.DirectoryServices.AccountManagement are differed execution. It does not attempt to connect to the Active Directory server till it has to. The ValidateCredentials method is the way to force a check, from the MSDN:
The ValidateCredentials method binds to the server specified in the
constructor. If the username and password parameters are null, the
credentials specified in the constructor are validated. If no
credential were specified in the constructor, and the username and
password parameters are null, this method validates the default
credentials for the current principal.
So all you need to do is
using(PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, (server + ":" + port), loginUsername, loginPassword))
{
//This will force the connection to the server and validate that the credentials are good
//If the connection is good but the credentals are bad it will return "false", if the connection is bad it will throw a exception of some form.
if(principalContext.ValidateCredentials(null, null))
{
// Rest of code here.
//This is how you do the same check you where doing in your previous quesiton, notice that this is "userName", and "password" not "loginUsername" and "loginPassword"
valid = principalContext.ValidateCredentials(userName,password);
}
}
The best way to handle any exception from the principal context is by placing your code in try and then catch the exceptions as shown below.
string user = txtUsername.Text;
string pass = txtPassword.Text;
//start a try and catch method
try
{
//create a principalcontext object
var pc = new PrincipalContext(ContextType.Domain, "*****", user, pass);
{
//validate the user credentials
if (pc.ValidateCredentials(user, pass))
{
//create a user identity
UserPrincipal userp = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, user);
//check if the user is returned
if (userp != null)
{
//if user exists, return an array of authorized groups
var grps = userp.GetAuthorizationGroups();
//convert the array to a list to enable search of CIS group
List<string> strList = grps.Select(o => o == null ? String.Empty : o.ToString()).ToList();
//check for CIS group from the list
if (strList.Contains("CISS"))
{
//create a session variable to show the loggedin user and set the error panel to false
Session["username"] = user;
ErrorPanel.Visible = false;
//redirect the user to the homepage
Response.Redirect("appdesk/account.aspx");
}
else if (!strList.Contains("CISS"))
{
Label1.Text = "You Don't have the Rights to login to the platfrom";
ErrorPanel.Visible = true;
}
}
//if the user credentials are invalid
if (!pc.ValidateCredentials(user, pass))
{
Label1.Text = "Login Failed.Incorrect Username or Password";
ErrorPanel.Visible = true;
}
}
}
//catch the exceptions in the try
catch (Exception exc)
{
Label1.Text = exc.Message.ToString();
ErrorPanel.Visible = true;
}
A basic catch doesn't work? Something like:
private ADConnectResults Connect(string server, int port)
try
{
PrincipalContext principalContext = new PrincipalContext(ContextType.Domain, (server + ":" + port), loginUsername, loginPassword);
return new ADConnectResults(true, principalContext);
}
catch(DirectoryServicesCOMException dex)
{
Log(dex);
return new ADConnectResults(false);
}
}
I found that attempting to assign the PrincipalContext.ConnectedServer property to a variable allowed the exception to surface:
using(var _ctx = new PrincipalContext(ContextType.Domain, server + ":" + port))
{
try
{
var connectedServer = _ctx.ConnectedServer;
}
catch (Exception)
{
//do something with the caught exception
}
}
I have a method to retrieve a list of AD groups that a user belongs to. Here is the code:
public static List<GroupPrincipal> GetGroups(string userName)
{
List<GroupPrincipal> result = new List<GroupPrincipal>();
// establish domain context
PrincipalContext yourDomain = new PrincipalContext(ContextType.Domain);
UserPrincipal user = null;
// find your user
user = UserPrincipal.FindByIdentity(yourDomain, userName);
// if found - grab its groups
if (user != null)
{
PrincipalSearchResult<Principal> groups = user.GetGroups();
// iterate over all groups
foreach (Principal p in groups)
{
// make sure to add only group principals
if (p is GroupPrincipal)
{
result.Add((GroupPrincipal)p);
}
}
}
return result;
}
In both IE and Chrome, this can work fine, but in Firefox, it always gives me DirectoryServicesCOMException on the user = UserPrincipal.FindByIdentity(yourDomain, userName); I don't even have any idea what kind of exception that is. Can someone explain me what the error is and how to fix it? Thank you so much!
Change the call to look like this:
using (HostingEnvironment.Impersonate()){
user = UserPrincipal.FindByIdentity(yourDomain, userName);
}
You will need to make sure that your application pool has AD permissions. This will perform the underlying AD call using the credentials of the hosting environment (the web App Pool Identity) instead of the credentials of user, who may not have permissions to query the AD server.
I want to create an Application for editing user accounts on a Server.
The Server do not use AD only local accounts.
I use the following code to connect the remote server:
try
{
PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Machine, "192.168.123.110", null, ContextOptions.Negotiate, "Administrator", "password");
try
{
MessageBox.Show(oPrincipalContext.ConnectedServer);
GroupPrincipal oGroupPrincipal = GroupPrincipal.FindByIdentity(oPrincipalContext, "Goetter");
try
{
// perform operations here
}
finally
{
oGroupPrincipal.Dispose();
}
}
finally
{
oPrincipalContext.Dispose();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
Whenever I try this, I get an exception, that the user and or password is not authorized, independent of the user I use. Administrator is the build in Admin user account.
Does PrincipalContext only works with AD or also with local accounts? Is anything wrong with my code?
using(PrincipalContext oPrincipalContext = new PrincipalContext(ContextType.Machine, computer.Name, null, ContextOptions.Negotiate, Settings.UserName, Settings.UserPassword))
using(GroupPrincipal oGroupPrincipal = GroupPrincipal.FindByIdentity(oPrincipalContext, Settings.AdministratorsGroup))
{
// perform operations here
}
Change your code and wrap it around a using statement otherwise you may have some errors when trying to call the Dispose() method reason being when you try to dispose the connection may have already been closed by then.
you can use this code here and try either of the examples if you are using ActiveDirectory
example 1
If you work on .NET 3.5, you can use the System.DirectoryServices.AccountManagement namespace and easily verify your credentials:
// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}
Example 2
using System.Security;
using System.DirectoryServices.AccountManagement;
public struct Credentials
{
public string Username;
public string Password;
}
public class Domain_Authentication
{
public Credentials Credentials;
public string Domain;
public Domain_Authentication(string Username, string Password, string SDomain)
{
Credentials.Username = Username;
Credentials.Password = Password;
Domain = SDomain;
}
public bool IsValid()
{
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain))
{
// validate the credentials
return pc.ValidateCredentials(Credentials.Username, Credentials.Password);
}
}
}
The public bool IsValid() Method above should work for what you are looking for.
Have a look at PrincipalContext.ValidateCredentials
for your FindByIdentity portion you can try the following replacement code
string strName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
// This is here because of a .Net error that gets 0x80005000 on "isUser = user.IsMemberOf(groupU);"
string domainName = strName.Split('\\')[0];
var pc = new PrincipalContext(ContextType.Domain, domainName);
Additional Reference Link StackOverFlow Post
ContextType.Machine