How to provide DirectoryEntry.Exists with credentials? - c#

This morning I discovered a nice method (DirectoryEntry.Exists), that should be able to check whether an Active Directory object exists on the server. So I tried with a simple:
if (DirectoryEntry.Exists(path)) {}
Of course it lacks any overloads to provide credentials with it. Because, if credentials are not provided I get this Exception:
Logon failure: unknown user name or
bad password.
(System.DirectoryServices.DirectoryServicesCOMException)
Is there any other option that gives me the possibility to authenticate my code at the AD server? Or to check the existence of an object?

In this case you can't use the static method Exists as you said :
DirectoryEntry directoryEntry = new DirectoryEntry(path);
directoryEntry.Username = "username";
directoryEntry.Password = "password";
bool exists = false;
// Validate with Guid
try
{
var tmp = directoryEntry.Guid;
exists = true;
}
catch (COMException)
{
exists = false;
}

I know this is an old question, but the source code is now available so you can just Steal and Modify™ to make a version that accepts credentials:
public static bool Exists(string path, string username, string password)
{
DirectoryEntry entry = new DirectoryEntry(path, username, password);
try
{
_ = entry.NativeObject; // throws exceptions (possibly can break applications)
return true;
}
catch (System.Runtime.InteropServices.COMException e)
{
if (e.ErrorCode == unchecked((int)0x80072030) ||
e.ErrorCode == unchecked((int)0x80070003) || // ERROR_DS_NO_SUCH_OBJECT and path not found (not found in strict sense)
e.ErrorCode == unchecked((int)0x800708AC)) // Group name could not be found
return false;
throw;
}
finally
{
entry.Dispose();
}
}
The one change you must make is changing the use of Bind, since that's an internal method and can't be used by mere mortals like us. Instead, I just get the NativeObject property, which calls Bind() for us.
You can use that like this:
var ouExists = Exists("LDAP://hadoop.com/OU=Students,DC=hadoop,DC=com", "username", "password");

There is no way to do this and I have written a connect issue to hopefully resolve it.
DirectoryEntry.Exists Does Not Accept Credentials

Here you can read about impersonation in C#:
http://www.codeproject.com/KB/cs/zetaimpersonator.aspx
http://www.codeproject.com/KB/system/everythingInAD.aspx

So answer to the question: impossible.
Finally write an own method to get the DirectoryEntry by distinguised name, with credentials specified. In both cases of existence/inexistence I got an instance of DirectoryEntry. To check whether it's a valid object returned I do a simple try...catch to see if it results in an Exception. If so, it's invalid.
Nasty check, but it works. Too bad the default .net method DirectoryEntry.Exists doesn't provide an overload to provide credentials just like the DirectoryEntry constructor...

If the user who ran the process doesn't have permissions to call DirectoryEntry.Exists, then you can use impersonation.
This may be helpful (discusses impersonation in an AD context): http://www.codeproject.com/KB/system/everythingInAD.aspx
Btw, if you already have credentials of a user who has access to everything you need, why not just the process with that user (e.g. /runas)?

Related

GroupPrincipal.GetMembers and cross-domain members error

I have 2 domains, A and B. The Domain A has the group GroupA which contains users from Domain B.
My code:
using (var context = new PrincipalContext(ContextType.Domain, DomainName, User, Password))
{
using (var groupPrincipal = GroupPrincipal.FindByIdentity(context, IdentityType.SamAccountName,
groupName))
{
if (groupPrincipal == null) return null;
using (var principalSearchResult = groupPrincipal.GetMembers(true))
{
var changedUsersFromGroup =
principalSearchResult
.Where(member => member is UserPrincipal)
.Where(member => IsModifiedUser(member, usnChanged))
.Cast<UserPrincipal>()
.Select(adsUser => new AdsUser(adsUser)).Cast<IAdsUser>()
.ToArray();
return changedUsersFromGroup;
}
}
}
System.DirectoryServices.AccountManagement.PrincipalOperationException:
While trying to resolve a cross-store reference, the target principal
could not be found in the domain indicated by the principal's SID.
But if I add user from here
new PrincipalContext(ContextType.Domain, DomainName, User, Password)
to domain B, it works correctly.
How can I fix it?
At least in .NET 4.7, you can work around it by manually managing the enumerator. This has been tested and has been able to successfully get past the errors.
static System.Guid[] GetGroupMemberGuids(System.DirectoryServices.AccountManagement.GroupPrincipal group)
{
System.Collections.Generic.List<System.Guid> result = new List<Guid>();
if (group == null) return null;
System.DirectoryServices.AccountManagement.PrincipalCollection px = group.Members;
System.Collections.IEnumerator en = px.GetEnumerator();
bool hasMore = true;
int consecFaults = 0;
while (hasMore && consecFaults < 10)
{
System.DirectoryServices.AccountManagement.Principal csr = null;
try
{
hasMore = en.MoveNext();
if (!hasMore) break;
csr = (System.DirectoryServices.AccountManagement.Principal)en.Current;
consecFaults = 0;
}
catch (System.DirectoryServices.AccountManagement.PrincipalOperationException e)
{
Console.Error.WriteLine(" Unable to enumerate a member due to the following error: {0}", e.Message);
consecFaults++;
csr = null;
}
if (csr is System.DirectoryServices.AccountManagement.UserPrincipal)
result.Add(csr.Guid.Value);
}
if (consecFaults >= 10) throw new InvalidOperationException("Too many consecutive errors on retrieval.");
return result.ToArray();
}
Check if it behaves differently when not casting to UserPrincipal, e.g.
var changedUsersFromGroup = principalSearchResult.ToArray();
As per other threads it might be some issue there. Also as per MSDN, using GetMembers(true) returned principal collection that does not contain group objects, only leaf nodes are returned, and so maybe you don't need that casting at all. Next, is to check how many results such search would return. If your AD has many users/nested groups, it might be better to try not to use GetMembers(true) to ensure it works on small groups of users.
It seems you are defining a PrincipalContext of domain A (so you can get the group), but because the users inside the group are defined in domain B the context cannot access them (as it's a domain A context).
You might need to define a second 'PricipalContext` for domain B and run the query against it and filter the objects using maybe the list of SIDs of the users located in the domain A group (you'll need to get the list of SIDs without causing the underlying code to try and resolve them).
Hope it helps!
Unfortunately I currently cannot test it, but maybe you can try this
var contextB = new PrincipalContext(ContextType.Domain, DomainName_B, User_B, Password_B)
[..]
var changedUsersFromGroup =
principalSearchResult
.Where(member => member is UserPrincipal)
.Where(member => IsModifiedUser(member, usnChanged))
.Select(principal => Principal.FindByIdentity(contextB, principal.SamAccountName))
.Cast<UserPrincipal>()
.Select(adsUser => new AdsUser(adsUser)).Cast<IAdsUser>()
.ToArray();
This could work, but only, if all members are in Domain B of course. If they are mixed in different domains you may have to filter it before that and iterate through your domains.
Alternatively you could run your application with a domain account? Then don't pass user/pass and give the required access rights to this account to avoid the error.
Explanation: The domain context will switch for your retrieved principals (you can view this in debugging mode if you comment out the new AdsUser / IAdsUser Cast part). This is the one, that seems to cause the exception. Though exactly this is the part I cannot test, I think the creation of AdsUser creates a new ldap Bind to the target Domain. This fails with the "original" Credentials. Is AdsUser Part of ActiveDS or 3rd Party? I did not find any hint if passing credentials uses basic authentication, but I think it should. Using application credentials uses negotiate and "handling this over" to the new AdsUser(..) should fix the issue as well.
problem found and reported to MS as bug. currently impossible to do it with .net :( but it works via native c++ api via queries

Check if PasswordVault/credential manager has app data at load

Hey I'm using PasswordVault for storing user credentials in my windows 8 app.
What I want the app to do on loading is check to see if the PasswordVault/credential manager already has a stored value for my app. if it don't don't I want it to stay on the page so the user can login, but if the credentials are already there then I would like it to go straight to page 2.
I tried using the following code:
private Windows.Security.Credentials.PasswordCredential GetCredentialFromLocker()
{
Windows.Security.Credentials.PasswordCredential credential = null;
var vault = new Windows.Security.Credentials.PasswordVault();
var credentialList = vault.FindAllByResource("MYapp");
if (credentialList.Count > 0)
if (credentialList.Count == 1)
credential = credentialList[0];
else
// User selecor
return credential;
}
and then on page load I have
private void Page_Loaded(object sender, RoutedEventArgs e)
{
var loginCredential = GetCredentialFromLocker();
if (loginCredential != null)
this.Frame.Navigate(typeof(page2));
else
{
loginBigButton.Visibility = Windows.UI.Xaml.Visibility.Visible;
signUpButton.Visibility = Windows.UI.Xaml.Visibility.Visible;
signUpTextBlock.Visibility = Windows.UI.Xaml.Visibility.Visible;
}
}
The problem is that if there is no credential stored with the Resource (MYapp) the code:
var credentialList = vault.FindAllByResource("MYapp");
yields:
WinRT information: Cannot find credential in Vault
Additional information: Element not found.
Method FindAllByResource throws exception when there are no credentials for specified resource, so you need to wrap it with try catch block.
Alternatively you can use 'RetrieveAll' which doesn't throw exception if there are no credentials stored and iterate over each returned PasswordCredential and check it's Resource property.
I'll try to answer this question the best as I can:
Firstable as Tadeusz said FindAllByResource throws an exception when there are no credentials for specified resource, in order to dont crash you app, you would need to wrap you logic within a try-catch block.
the reason to do this is for you to be able to create your resource in case you dont have one. so the way I see it, you logic should look like this:
private Windows.Security.Credentials.PasswordCredential GetCredentialFromLocker()
{
Windows.Security.Credentials.PasswordCredential credential = null;
var vault = new Windows.Security.Credentials.PasswordVault();
try
{
var credential = vault.FindAllByResource("MYapp").FirstOrDefault();
return credential;
}
catch(Exception ex)
{
Debug.WriteLine($"Error retrieving Token: {ex.Message}");
}
return null;
}
now all you have to do is to store a new token in your passwordvault
you have to login first and then you store the token, so next time you try to retrieve your credentials it wont throw an exception since its already store.
by example it should be something like this:
await client.LoginAsync(provider);
then you store your token like this:
PasswordVault.Add(new PasswordCredential("MYapp",
client.CurrentUser.UserId,
client.CurrentUser.MobileServiceAuthenticationToken));
I hope this answer helps in order, sorry for the late answer but, I found myself trying to solve this problem right now and I thought i should give a more detail or complete answer to this question.
Also you should consider token expiration checks and refreshing handling.

How to get userID for username in ASMX webservice

I'm trying to store a user's post into an access database through a web method. I want to store the logged-in user's username, user's post, and the post datetime.
So far, I can store an existing user post by hard coding. But I want to store posts by any logged-in users. I was told I need to get userID for username.
Thus, I've found and tried adding the following codes:
//GetUser() returns current user information
MembershipUser user = Membership.GetUser();
//Returns the UserID and converts to a string
string UserID = user.ProviderUserKey.ToString();
When I tried debugging with breakpoints, the first one was okay. But for the second one, VS 2010 said that "object reference not set to an instance of an object." How do I fix it?
VS suggested adding "new," which didn't work. It also suggested to catch NullReferenceException, but I don't know how to use the codes they provided:
public class EHClass
{
void ReadFile(int index)
{
// To run this code, substitute a valid path from your local machine
string path = #"UsersDB_in_App_Data";
System.IO.StreamReader file = new System.IO.StreamReader(path);
char[] buffer = new char[10];
try
{
file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
Console.WriteLine("Error reading from {0}. Message = {1}", path, e.Message);
}
finally
{
if (file != null)
{
file.Close();
}
}
// Do something with buffer...
}
}
Can you give me suggestions of what I need to do, or an alternative way to go about getting userID for username?
You didn't indicate what type of MembershipUser you have, but the ProviderUserKey is totally dependent on the underlying data store.
For example, the sql membership provider stores this value as a GUID.
If there is a possibility that this property won't contain any useful data, then you need to test it for existence before accessing it:
//Returns the UserID and converts to a string
string UserID;
if ((myObject != null) && (myObject.ProviderUserKey != null)) {
UserId = myObject.ProviderUserKey.ToString();
} else {
UserId = String.Empty;
}
In addition, unless you are using WSE in a straight asmx web service, I don't think that the memebership provider will have any valid data to operate on.
If this is the case, you will probably need to switch to WCF or implement WSE (NOT recommended).
The exception you're getting means that either myObject (the current user) is null or myObject.ProviderUserKey is null. I'd suggest that when you get to the breakpoint after myObject is set you should inspect the value of myObject and see whether it is null.
Depending on what is actually null affects where you look for the problem. If myObject is null then you'll need to look at the code to get the current user, and check whether someone is actually logged in etc. etc. If ProviderUserKey is null, consider whether you need this ID or would be better off with just using the username directly, check whether the membership provider actually provides that property in any meaningful way.
instead you can use this code directly....
string UserID = MembershipUser.ProviderUserKey.ToString();

Strange Error When Using System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials with SAM

I am hosting a WCF Web Service with IIS 6.0. My application pool is running under a local Administrator account, and I have other local users defined for accessing the Web Service. I've written the following code to validate users:
//Any public static (Shared in Visual Basic) members of this type are thread safe
public static PrincipalContext vpc;
//static initializer
static UserManagement()
{
vpc = new PrincipalContext(ContextType.Machine);
}
//Determines whether the given credentials are for a valid Administrator
public static bool validateAdminCredentials(string username, string password)
{
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
{
if (vpc.ValidateCredentials(username, password))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, username))
{
foreach (GroupPrincipal gp in user.GetGroups())
{
try
{
if (gp.Name.Equals("Administrators"))
{
return true;
}
}
finally
{
gp.Dispose();
}
}
}
}
return false;
} //end using PrincipalContext
}
...and in another class:
//verify the user's password
if (!UserManagement.vpc.ValidateCredentials(username, password))
{
string errorMsg = string.Format("Invalid credentials received for user '{0}'", username);
throw new Exception(errorMsg);
}
(Note: I am using the public static PrincipalContext (vpc) solely for calling the ValidateCredentials method; I create a different, temporary PrincipalContext for creating, deleting, and finding users and groups, as I got various COM-related errors when I tried using the global PrincipalContext for everything).
So, most of the time, this code works wonderfully. However, intermittently, I get the following error:
Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. (Exception from HRESULT: 0x800704C3)
Application:
System.DirectoryServices.AccountManagement
Stack Trace:
at System.DirectoryServices.AccountManagement.CredentialValidator.BindSam(String target, String userName, String password)
at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)
at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)
at MyNamespace.User..ctor(String username, String password)
Once the error occurs, I continue to get it until I restart my entire server (not just IIS). I've tried restarting my application pool and/or IIS, but the error does not go away until I restart the machine. I've also tried instantiating (via a using block) a new PrincipalContext for every call to ValidateCredentials (which I shouldn't have to do), but I still eventually get the same error. From what I've read on System.DirectoryServices.AccountManagement (msdn docs, articles), I believe I'm using it correctly, but this error is crippling my application! I need (and should be able) to validate local user credentials from web service requests coming from multiple clients. Am I doing something wrong? Any help on solving this issue would be much appreciated...
"Any public static (Shared in Visual Basic) members of this type are thread safe"
This boilerplate text confuses a lot of people. It means that any static members exposed by the type are thread-safe. It doesn't mean that any instance of the type stored in a static member will be thread-safe.
Your validateAdminCredentials method creates a new PrincipalContext object, but then proceeds to use the static vpc instance to validate the credentials. Since you have no locks around the access to the static instance, and the instance methods are not thread-safe, you'll eventually get two threads trying to access the same instance at the same time, which will not work.
Try removing the vpc field and using the principalContext instance to validate the credentials. You'll need to create and dispose of the PrincipalContext on every call.
Also, rather than manually iterating the user's groups, you can use the IsMemberOf method to test for membership in a group.
public static bool ValidateCredentials(string username, string password)
{
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
{
return principalContext.ValidateCredentials(username, password);
}
}
public static bool validateAdminCredentials(string username, string password)
{
using (PrincipalContext principalContext = new PrincipalContext(ContextType.Machine))
{
if (principalContext.ValidateCredentials(username, password))
{
using (UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, username))
using (GroupPrincipal group = GroupPrincipal.FindByIdentity(principalContext, IdentityType.SamAccountName, "Administrators"))
{
if (null != group && user.IsMemberOf(group))
{
return true;
}
}
}
return false;
}
}
My suggestion would be to not use ValidateCredentials with the SAM provider. I believe it should work, but I am also not surprised that it runs into problems. I think you'll do better by doing a p/invoke into LogonUser using Network logon type for programmatic credential validation.
I'd like to see if Microsoft has a known issue and solution to this problem but in the meantime I'd suggest just coding around it.
If you need a solution the works even with Windows 2000 and w/o running code as system you could also use this:
public static bool ValidateUser(
string userName,
string domain,
string password)
{
var tcpListener = new TcpListener(IPAddress.Loopback, 0);
tcpListener.Start();
var isLoggedOn = false;
tcpListener.BeginAcceptTcpClient(
delegate(IAsyncResult asyncResult)
{
using (var serverSide =
new NegotiateStream(
tcpListener.EndAcceptTcpClient(
asyncResult).GetStream()))
{
try
{
serverSide.AuthenticateAsServer(
CredentialCache.DefaultNetworkCredentials,
ProtectionLevel.None,
TokenImpersonationLevel.Impersonation);
var id = (WindowsIdentity)serverSide.RemoteIdentity;
isLoggedOn = id != null;
}
catch (InvalidCredentialException) { }
}
}, null);
var ipEndpoint = (IPEndPoint) tcpListener.LocalEndpoint;
using (var clientSide =
new NegotiateStream(
new TcpClient(
ipEndpoint.Address.ToString(),
ipEndpoint.Port).GetStream()))
{
try
{
clientSide.AuthenticateAsClient(
new NetworkCredential(
userName,
password,
domain),
"",
ProtectionLevel.None,
TokenImpersonationLevel.Impersonation);
}
catch(InvalidCredentialException){}
}
tcpListener.Stop();
return isLoggedOn;
}
I resolved this same problem by using the domain context. It is possible to use the machine context to delegate to the domain, but it appears that you will be forced to run into this problem eventually.
Note that you should create the domain context fresh for each call, because it must be created using the domain of the user you want to authenticate.
My application needed to support machine users as well. I found that whenever a user was a machine user, the domain context would throw an exception. If I catch the exception, I work against the machine context.

Determining members of local groups via C#

I wondered whether anybody knows how to obtain membership of local groups on a remote server programmatically via C#. Would this require administrator permissions? And if so is there any way to confirm the currently logged in user's membership (or not) of these groups?
Howto: (Almost) Everything In Active Directory via C# is very helpfull and also includes instructions on how to iterate AD members in a group.
public ArrayList Groups(string userDn, bool recursive)
{
ArrayList groupMemberships = new ArrayList();
return AttributeValuesMultiString("memberOf", userDn,
groupMemberships, recursive);
}
You will also need this function:
public ArrayList AttributeValuesMultiString(string attributeName,
string objectDn, ArrayList valuesCollection, bool recursive)
{
DirectoryEntry ent = new DirectoryEntry(objectDn);
PropertyValueCollection ValueCollection = ent.Properties[attributeName];
IEnumerator en = ValueCollection.GetEnumerator();
while (en.MoveNext())
{
if (en.Current != null)
{
if (!valuesCollection.Contains(en.Current.ToString()))
{
valuesCollection.Add(en.Current.ToString());
if (recursive)
{
AttributeValuesMultiString(attributeName, "LDAP://" +
en.Current.ToString(), valuesCollection, true);
}
}
}
}
ent.Close();
ent.Dispose();
return valuesCollection;
}
If you do now want to use this AD-method, you could use the info in this article, but it uses unmanaged code:
http://www.codeproject.com/KB/cs/groupandmembers.aspx
The sample application that they made:
It appears there is a new Assembly in .net 3.5 called System.DirectoryServices.AccountManagement which gives a cleaner implementation than System.DirectoryServices. Dominick Baier blogs about a couple of simple operations including checking membership of a group:-
public static bool IsUserInGroup(string username, string groupname, ContextType type)
{
PrincipalContext context = new PrincipalContext(type);
UserPrincipal user = UserPrincipal.FindByIdentity(
context,
IdentityType.SamAccountName,
username);
GroupPrincipal group = GroupPrincipal.FindByIdentity(
context, groupname);
return user.IsMemberOf(group);
}
I think I will use this approach, thanks for the suggestions though however! :-)
Perhaps this is something that can be done via WMI?
I asked a similar question, and ended up writing an answer which used WMI to enum the group members. I had real problems with authentication in the system.directoryservices.accountmanagement stuff. YMMV, of course.
I'd be curious if the System.DirectoryServices.AccountManagement is fully managed. I've used System.DirectoryServices.ActiveDirectory which is a wrapper for COM Interop which has led to many headaches...
This may possibly help. I had to develop an app where we want to authenticate against active directory, and also examine the groups strings that the user is in.
For a couple of reasons we don't want to use windows authentication, but rather have our own forms based authentication. I developed the routine below to firstly authenticate the user, and secondly examine all the groups that the user belongs to. Perhaps it may help. The routine uses LogonUser to authenticate, and then gets the list of numerical guid-like group ids (SIDs) for that user, and translates each one to a human readable form.
Hope this helps, I had to synthesise this approach from a variety of different google searches.
private int validateUserActiveDirectory()
{
IntPtr token = IntPtr.Zero;
int DBgroupLevel = 0;
// make sure you're yourself -- recommended at msdn http://support.microsoft.com/kb/248187
RevertToSelf();
if (LogonUser(txtUserName.Value, propDomain, txtUserPass.Text, LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, token) != 0) {
// ImpersonateLoggedOnUser not required for us -- we are not doing impersonated stuff, but leave it here for completeness.
//ImpersonateLoggedOnUser(token);
// do impersonated stuff
// end impersonated stuff
// ensure that we are the original user
CloseHandle(token);
RevertToSelf();
System.Security.Principal.IdentityReferenceCollection groups = Context.Request.LogonUserIdentity.Groups;
IdentityReference translatedGroup = default(IdentityReference);
foreach (IdentityReference g in groups) {
translatedGroup = g.Translate(typeof(NTAccount));
if (translatedGroup.Value.ToLower().Contains("desired group")) {
inDBGroup = true;
return 1;
}
}
}
else {
return 0;
}
}

Categories