PrincipalContext with smartcard inserted - c#

We have been using an application for a while that uses System.DirectoryServices.AccountManagement to communicate with the Active directory (domain context).
ContextOptions options = ContextOptions.Negotiate |
ContextOptions.SecureSocketLayer;
Using(PrincipalContext adContext = new PrincipalContext(ContextType.Domain, "AD.DOMAIN", "DC=AD,DC=intranet", options))
{
//Do stuff
}
This works fine until we insert a smartcard. As soon as we insert a smartcard with a user certificate, it will prompt for a smartcard pin as soon as it hits the PrincipalContext constructor. When cancelling out, the application will crash. When entering the correct pin, it will just keep on prompting over and over.
It seems to be linked to the TLS session which is set up in the background. The issue does not exist when we do not enable encryption. But encryption is mandatory.
Has anyone run into this issue before? Resources seem to be limited. Closest I could find was:
https://connect.microsoft.com/VisualStudio/feedback/details/3100569/initializing-contextoptions-does-not-work-in-system-directoryservices-accountmanagement-principalcontext-constructor
Thanks in advance

The PrincipalContext utilizes the internal class CredentialValidator to authenticate during the LDAP bind.
private bool BindLdap(NetworkCredential creds, ContextOptions contextOptions)
{
LdapConnection current = (LdapConnection) null;
...
current = new LdapConnection(this.directoryIdent);
...
try
{
current.SessionOptions.FastConcurrentBind();
The method FastConcurrentBind finds the certificate and as SSL is enabled in the connection options it asks for the PIN.
if fast bind is not supported Bind is called and does the same:
private void lockedLdapBind(
LdapConnection current,
NetworkCredential creds,
ContextOptions contextOptions)
{
current.AuthType =
(ContextOptions.SimpleBind & contextOptions) > (ContextOptions) 0
? AuthType.Basic
: AuthType.Negotiate;
current.SessionOptions.Signing =
(ContextOptions.Signing & contextOptions) > (ContextOptions) 0;
current.SessionOptions.Sealing =
(ContextOptions.Sealing & contextOptions) > (ContextOptions) 0;
if (creds.UserName == null && creds.Password == null)
current.Bind();
else
current.Bind(creds);
}
To prevent this the session options must be modified like this
current.SessionOptions.QueryClientCertificate =
new QueryClientCertificateCallback((a,b) => null);
The issue is that this cannot be done for the internal class from the outside.
This can only be done when manually constructing the LdapConnection object.

Related

Active Directory not working for offsite

Technology used: asp.net(C#), MVC, LINQ, Entity
I use the Active Directory for our company website. This system works for all of our employees on site and when we take our laptop offsite and use VPN.
However we recently hired some developers(offsite) who we have given VPN access to. But they are unable to run our code and I am unsure why. We have active directory users set up for them and they can connect to us through VPN.
They are running into two errors.
A username/password error.
The Server is not Operational error.
Does the VPN username/password have to match the active directory account?
If they log into their development machine does that username/password have to match the active directory account?
Is there some restriction on Active Directory and offsite I am unaware of as to why this would work for us but not our off site developers?
Below is the section of code that is giving the error: It errors on the line SearchResult rs = ds.FindOne();
public AD_CurrentUserProfile GetUserProfile(string userName)
{
using (DirectoryEntry de = new DirectoryEntry("LDAP://server.com"))
using (DirectorySearcher ds = new DirectorySearcher(de))
{
ds.SearchScope = SearchScope.Subtree;
ds.Filter = ("(&(objectCategory=person)(objectClass=User)(samaccountname=" + userName + "))");
ds.PropertiesToLoad.Add("distinguishedName");
ds.PropertiesToLoad.Add("manager");
ds.PropertiesToLoad.Add("directreports");
SearchResult rs = ds.FindOne();
AD_CurrentUserProfile userProfile = new AD_CurrentUserProfile();
userProfile.currentUser = GetProfileFromDN(rs.Properties["distinguishedName"][0].ToString());
userProfile.managerProfile = GetProfileFromDN(rs.Properties["manager"][0].ToString(), true);
int departmentID = db.IPACS_Department.First(v => (v.name == userProfile.currentUser.department)).departmentID;
userProfile.ipacs_department = db.IPACS_Department.Find(departmentID);
if (userProfile.currentUser.userName == userProfile.ipacs_department.owner)
{
userProfile.currentUser.isManager = true;
}
// Override for Provana and upper management
if (userProfile.currentUser.department == "Provana" || userProfile.currentUser.department == "JM")
{
userProfile.currentUser.isManager = true;
}
if (rs.Properties["DirectReports"].Count > 0)
{
if (!userProfile.currentUser.isManager)
{
userProfile.currentUser.isSupervisor = true;
}
userProfile.directReports = new HashSet<AD_User>();
foreach (string value in rs.Properties["DirectReports"])
{
userProfile.directReports.Add(GetProfileFromDN(value));
}
}
return userProfile;
}
}
I never pass 'password' anywhere in any of my code, so is this something Active Directory does by default?
A connection to AD will always require windows credentials. Your code, as posted, does not supply any credentials to AD. (You pass in a user name that you are looking up, but that is not the same as supplying credentials for the connection). This will work for users whose machines are attached to the domain...because your network credentials are passed in implicitly. For the external devs, when they VPN in, they supply credentials to the VPN protocol, which allows their machines to access your network, but that doesn't mean their machines are 'joined' to the domain...so AD will still require explicit credentials from them, including a personal password or a service account password that has permissions to access AD.
This line:
using (DirectoryEntry de = new DirectoryEntry("LDAP://server.com"))
essentially needs to allow a user name and password:
using (DirectoryEntry de = new DirectoryEntry("LDAP://server.com"), userName, pwd)
{
de.AuthenticationType = AuthenticationTypes.None | AuthenticationTypes.ReadonlyServer;
}
...you'll have to experiment with the AuthenticationTypes flags depending on how your network admins have it set up.

Check Password Reset on Active Directory Server

I need to reset windows password of any user through my .Net application. I am using the user's username to get its Directory Entry from AD server. I got these two different methods for changing password :
entry.Invoke("ChangePassword", oldPass, newPass);
&
entry.Invoke("SetPassword", "pass#123");
But I am getting the following error when am trying these methods on live AD server :
Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
I have 2 AD servers. One of them is live and another is for testing purpose. I just want to check if my code is working or not. Since, access is denied on live server I can not change and check later my own password through code.
And if I am using the test AD server to change password, I don't know how to check whether the pasword is changed or not.
Kindly give any suggestions to check if my code is working properly or not.
Thanks in advance.
I think you're not getting a proper context setup before you call the invoke. Here's what I use for something similar. You'll need to set your own variables:
I'm using System.DirectoryServices.AccountManagement to get the functions.
//Domain related info
string _DCToUse = "myserver.domain.local";
string _ADConDomain = "DC=domain,DC=local";
string _AdDomain = "domain";
string _ADAdminUser = "administrator";
string _ADAdminPass = "password";
//User specific
string _UserName = "jsmith";
string _CurrentPass = "oldPass";
string _NewPass = "newPass";
PrincipalContext principalContext =
new PrincipalContext(ContextType.Domain, _DCToUse,
_ADConDomain, _ADDomain+#"\"+_ADAdminUser, _ADAdminPass);
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, _UserName);
if (user == null)
{
string ADErrorMsg = "Couldn't find user, check your spelling.";
return Changed;
}
user.ChangePassword(oldPass, newPass);
user.Save();

Create Windows User programmatically c# .net (using PricinpalUser / CreateProfile)

In a nutshell, what I'm trying to do is create a new user, which has the ability to log in.
I have plucked code from various sources, and tried to simplify it. However, I'm hitting a few stumbling blocks.
When I call UserPrincipal.Save() - it gives me an error
'The directory property cannot be found in the cache' with an
exception type of.. 'COMExceptioncrossed a native/managed boundary'.
For some reason, when I run my program directly (not through vs2010) it works fine. So I can get around that !
My main problem though, is that even though everything seems ok, when I try to log in, it comes up with the message 'loading desktop' or whatever it is, and then just says 'logging out'. So it's almost as if the profile hasn't been set up correctly.
The return value from the API 'CreateProfile' isn't 0, so maybe that's causing a problem.
Is there anything else I need to do ?
My Code is...
private void Run(string un, string pw)
{
UserPrincipal NewUP = CreateUser(un, pw);
AddGroup(NewUP, "Users");
AddGroup(NewUP, "HomeUsers");
CreateProfile(NewUP);
}
private UserPrincipal CreateUser(string Username, string Password)
{
PrincipalContext pc = new PrincipalContext(ContextType.Machine, Environment.MachineName);
UserPrincipal up = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, Username);
if (up == null)
{
up = new UserPrincipal(pc, Username, Password, true);
up.UserCannotChangePassword = false;
up.PasswordNeverExpires = false;
up.Save(); // this is where it crashes when I run through the debugger
}
return up;
}
private void AddGroup(UserPrincipal Up, string GroupName)
{
PrincipalContext pc = new PrincipalContext(ContextType.Machine, Environment.MachineName);
GroupPrincipal gp = GroupPrincipal.FindByIdentity(pc, GroupName);
if (!gp.Members.Contains(Up))
{
gp.Members.Add(Up);
gp.Save();
}
gp.Dispose();
}
private void CreateProfile(UserPrincipal Up)
{
int MaxPath = 240;
StringBuilder pathBuf = new StringBuilder(MaxPath);
uint pathLen = (uint)pathBuf.Capacity;
int Res = CreateProfile(Up.Sid.ToString(), Up.SamAccountName, pathBuf, pathLen);
}
Strangely, when this is run on a server machine (i.e. not my development machine) it works fine. I've got a feeling this is something to do with Windows 7, or my particular installation of it.
Thanks for your suggestions anyway.

Can not retrieve Active Directory user in C#

I built a test Active Directory server in Window 2008 and I also run the DNS server on it. On my client machine which runs the C# application, I can authenticate the user against the Active directory server using the function below:
public static UserPrincipal GetUserPrincipal(string usrName,string pswd,string domainName)
{
UserPrincipal usr;
PrincipalContext ad;
// Enter Active Directory settings
ad = new PrincipalContext(ContextType.Domain, domainName,usrName,pswd);
//search user
usr = new UserPrincipal(ad);
usr.SamAccountName = usrName;
PrincipalSearcher search = new PrincipalSearcher(usr);
usr = (UserPrincipal)search.FindOne();
search.Dispose();
return usr;
}
In a separate logic I tried to retrieve a user back from the server using a user name. I used the functions below:
public static DirectoryEntry CreateDirectoryEntry()
{
// create AD connection
DirectoryEntry de = new DirectoryEntry("LDAP://CN=Users,DC=rootforest,DC=com","LDAP","password");
de.AuthenticationType = AuthenticationTypes.Secure;
return de;
}
public static ResultPropertyCollection GetUserProperty(string domainName, string usrName)
{
DirectoryEntry de = CreateDirectoryEntry();
DirectorySearcher deSearch = new DirectorySearcher();
deSearch.SearchRoot = de;
deSearch.Filter = "(SamAccountName=" + usrName + ")";
SearchResult results = deSearch.FindOne();
return null;
}
However, I got no response back from the LDAP server at all, not even an exception. Am I missing certain settings on LDAP server, any of you able to see a flaw in my code (pls don't mind the hard code values, I was testing with this code).
As part of my troubleshooting, I confirmed that I can ping to the rootforest.com from the client machine. I confirmed the user with property samaccountname "LDAP" exists. My path seems to be right because when I go onto the LDAP server and type :
dsquery user -name LDAP*
I got the following:
CN=LDAP L. LDAP,CN=Users,DC=rootforest,DC=com
Any help would be greatly appreciated, I've spent most of my day troubleshooting and researching this little bugger and I think it could be something small which I overlooked.
I don't understand why you're using the new PrincipalContext / UserPrincipal stuff in your first example, but fall back to the hard to use DirectoryEntry stuff in your second example.... doesn't really make sense... also: your second function GetUserProperty seems to return null always - typo or not??
Since you're on already using the System.DirectoryServices.AccountManagement (S.DS.AM) namespace - use it for your second task, too! 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:
public static ????? GetUserProperty(string domainName, string usrName)
{
// set up domain context
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
// find a user
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, usrName);
if(user != null)
{
// return what you need to return from your user principal here
}
else
{
return null;
}
}
The new S.DS.AM makes it really easy to play around with users and groups in AD:
I think your code have a few problems:
Why do you return null in your GetUserProperty() function? You should return results instead.
The attribute you are using in your search filter is misspelled. Use sSAMAccountName instead. Furthermore extend your query to search only for user accounts. Here is an example: (&(objectCategory=person)(objectClass=user)(sAMAccountName=usrName))
You could also use the UserPrincipal class to search for an identity in Active Directory. The UserPrincipal class provides a static method called FindByIdentity() to search for a user identity.
Hope, this helps.

Creating Active Directory user with password in C#

I'm looking for a way to create Active Directory users and set their password, preferably without giving my application/service Domain Admin privileges.
I've tried the following:
DirectoryEntry newUser = _directoryEntry.Children.Add("CN=" + fullname, USER);
newUser.Properties["samAccountName"].Value = username;
newUser.Properties["userPassword"].Value = password;
newUser.Properties["mail"].Value = email;
newUser.CommitChanges();
The user is created, but it seems the password is never set on the user.
Does anyone have an idea on how to set the user's password initially when creating the user? I know about
.Invoke("SetPassword", new object[] { password })
But that requires my code to be run with Domain Admin privileges. As I don't really see the point to grant my code Domain Admin privileges, just to set the initial password (I also allow user password resets, but those run in the context of that particular user), I am hoping someone has a clever solution that doesn't require me to do so.
Thanks in advance!
You can do this whole process much easier now with System.DirectoryServices.AccountManagement (long as you're on .Net 3.5):
See here for a full rundown
Here's a quick example of your specific case:
using(var pc = new PrincipalContext(ContextType.Domain))
{
using(var up = new UserPrincipal(pc))
{
up.SamAccountName = username;
up.EmailAddress = email;
up.SetPassword(password);
up.Enabled = true;
up.ExpirePasswordNow();
up.Save();
}
}
I'd use #Nick's code (wrapped in using statements so the context and principal are disposed properly). As for privileges, you'll need to at least have enough privileges on the OU where you are creating the user to create and manage objects. I'd create a specific user under which your program will run and give it just enough privileges to do the tasks that it needs in that specific OU and no more.
Yes can also use below code to create bulk of users
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 0; i < 10; 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)
{
}
}
The actual attribute for the password is unicodePwd, which requires a specific format that is described in the documentation. But this is how you can do it:
newUser.Properties["unicodePwd"].Value = Encoding.Unicode.GetBytes("\"NewPassword\"");
Doing it this way, you can create the user with a password in one step.

Categories