I have written code using JNDI for creating users using DirContext in AD.
After I create the user I am not able to login with those credentials. When I manually reset the password for that user in AD, I am able to login.
Here I have placed my code for your reference,
Hashtable<String, String> ldapenv = new Hashtable<>();
ldapenv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
ldapenv.put(Context.PROVIDER_URL, "ldap://10.95.144.139:389");
ldapenv.put(Context.SECURITY_AUTHENTICATION, "simple");
ldapenv.put(Context.SECURITY_PRINCIPAL, "CN=Administrator,CN=Users,dc=Merck,dc=local");
ldapenv.put(Context.SECURITY_CREDENTIALS, "Merck2017");
DirContext context = new InitialDirContext(ldapenv);
Attributes attributes = new BasicAttributes();
// Create the objectclass to add
Attribute objClasses = new BasicAttribute("objectClass");
objClasses.add("top");
objClasses.add("person");
objClasses.add("organizationalPerson");
objClasses.add("user");
// Assign the username, first name, and last name
String cnValue = new StringBuffer(user.getFirstName()).append(" ").append(user.getLastName()).toString();
Attribute cn = new BasicAttribute("cn", cnValue);
Attribute sAMAccountName = new BasicAttribute("sAMAccountName", user.getUserName());
Attribute principalName = new BasicAttribute("userPrincipalName", user.getUserName()
+ "#" + "merck.local");
Attribute givenName = new BasicAttribute("givenName", user.getFirstName());
Attribute sn = new BasicAttribute("sn", user.getLastName());
Attribute uid = new BasicAttribute("uid", user.getUserName());
// Add password
Attribute userPassword = new BasicAttribute("userPassword", user.getPassword());
Attribute pwdAge = new BasicAttribute("pwdLastSet","-1");
Attribute userAccountControl = new BasicAttribute("userAccountControl", "544");
// Add these to the container
attributes.put(objClasses);
attributes.put(sAMAccountName);
attributes.put(principalName);
attributes.put(cn);
attributes.put(sn);
attributes.put(givenName);
attributes.put(uid);
attributes.put(userPassword);
attributes.put(userAccountControl);
attributes.put(pwdAge);
// Create the entry
try {
context.createSubcontext(getUserDN(cnValue,"Merck-Users"), attributes);
System.out.println("success === ");
} catch (Exception e) {
System.out.println("Error --- "+e.getMessage());
}
Please help me resolve the following issues:
How do I set AD user password while creating the user using the above code?
How do I set userAccountControl to 66048 in the above code?
How do I create the user enabled while using the above code?
How do I disable the option "user must change the password in next login" while creating the user in the above code?
Thanks in advance.
I don't have all the answers, but this should get you started:
Passwords can only be set over a secure channel, like LDAPS (LDAP over SSL). Since you are connecting to port 389, that is not SSL and AD won't let you set the password. You must connect to the LDAPS port: 636. You may run into issues trusting the SSL certificate. I can't help much here since I'm not a Java developer, but there is an example here.
The answer to your second and third questions is the same: Accounts with no passwords are always disabled. Since you haven't set the password properly, the account will be disabled. Once you figure out how to set the password, you can also set userAccountControl to whatever you need.
You are disabling the "user must change password" option correctly: by setting pwdLastSet to -1. That's the right way to do it. But you may have to fix the other issues first.
Another important thing: I have created AD accounts in .NET, and I have found that I had to create the account first, then go back and set the password and set the userAccountControl attribute after. You may have to do the same.
Related
I want to get incremental changes from Active Directory using C# and for that I am trying to build a solution as mentioned in the following article (using DirSync Control).
https://learn.microsoft.com/en-us/windows/win32/ad/polling-for-changes-using-the-dirsync-control
However, I am facing following problems:
When using following code, I am getting exception that The user has insufficient access rights. The user is part of administrators group.
What more permission needs to be given to that account? And how?
LdapConnection connection = new LdapConnection("adfs.fed.zzz.com");
connection.SessionOptions.ProtocolVersion = 3;
connection.Credential = new System.Net.NetworkCredential("adfsfed\\username", "password");
connection.AuthType = AuthType.Basic;
connection.Bind();
var filter = "(&(objectClass=*))";
var searchRequest = new SearchRequest("", filter, SearchScope.Subtree, properties);
DirSyncRequestControl dirSyncRC = new DirSyncRequestControl(null, DirectorySynchronizationOptions.None);
searchRequest.Controls.Add(dirSyncRC);
var response = connection.SendRequest(searchRequest) as SearchResponse;
If I am using below code, then I am not getting any exception but getting empty result in cookie.
String[] properties = { "objectGUID", "sAMAccountName", "displayName", "mail", "member" };
String filter = "(|(objectClass=group)(objectClass=user))";
DirectorySearcher directorySearcher = new DirectorySearcher(myLdapConnection, filter, properties);
var dSynch = new DirectorySynchronization(System.DirectoryServices.DirectorySynchronizationOptions.None);
directorySearcher.DirectorySynchronization = dSynch;
directorySearcher.SearchScope = System.DirectoryServices.SearchScope.Subtree;
var results = directorySearcher.FindAll();
var cookie = dSynch.GetDirectorySynchronizationCookie();
Considerations:
I have only one Domain Controller
I am system admin. So, I can assign appropriate permissions to the user.
Please help.
• Your user ID will need the "Replicating Directory Changes" permission and should be a member of ‘Domain Administrators’ group to use the DirSync LDAP control extension. But please note that it pretty much can read anything in the directory partition, regardless of standard permissions. Though they cannot change anything.
However - you may have some attributes that are sensitive in your directory. Please refer the powershell script in the below link and execute it with the user ID after giving appropriate permissions using C#. It is a dirsync code that will retrieve even attributes like ‘userAccountControl, userparameters, msexchuseraccountcontrol, pwdlastset, unicodePwd (BLANK, So no hashed domain password is returned), lockouttime, accountexpires, unixuserpassword(Its Hash is returned).
http://dloder.blogspot.com/2012/01/powershell-dirsync-sample.html
Based on the response given by #KartikBhiwapurkar-MT, I figured out the bug.
The error The user has insufficient access rights is completely misleading (User had already Replicating Directory Changes rights and was part of Domain Administrators group). The error was happening in System.DirectoryServices.Protocols is that I was passing out "" as first parameter (distinguishedName)
new SearchRequest("", filter, SearchScope.Subtree, properties);
but it should have been passed as
new SearchRequest("DC=adfs,DC=fed,DC=zzz,DC=com", filter, SearchScope.Subtree, properties);
I was getting empty cookie in System.DirectoryServices because of bug in latest nuget package (6.0.0). At the time of writing this answer, the bug is still open.
Reference to bug
I'm trying to update the user password for a user in Microsoft Active Directory with LDAP, using JNDI library over SASL (DIGEST-MD5). And there are a couple of issues that makes the operation fail.
First issue
During the authentication phase, I receive the error
Exception: #javax.naming.AuthenticationException: [LDAP: error code 49 - 80090303: LdapErr: DSID-0C09054F, comment: The digest-uri does not match any LDAP SPN's registered for this server., data 0, v2580
Steps taken
I added the required SPN to the DC in AD, and the issue was resolved only for one time, then it came back. When I checked the DC, I found out that the SPN that I have just added has been removed. And this keeps happening every time I add the SPN to the DC!
Second issue
During the time that the authentication proceeds successfully, the server refuses to update the user's password. I'm trying to update the "unicodePwd" attribute using a "DirContext.REPLACE_ATTRIBUTE" operation (I'm using a domain controller administrator account for the authentication, and trying to update a normal user account).
This is the error I receive "Error:
#javax.naming.OperationNotSupportedException: [LDAP: error code 53 - 0000001F: SvcErr: DSID-031A12D2, problem 5003 (WILL_NOT_PERFORM), data 0 ]; remaining name '<the DN of the user that I was trying to update>'
Another note, when I check the attribute "unicodePwd", it's always unset!! So, the question here "How does the AD authenticate the user? Which attribute holds the user's password?!!
Third issue
I can use a couple of LDAP clients, and I can update/reset the user's password. I only need to specify the authentication protocol as (SASL) and the operation goes seamlessly =, without having to make any Changes to the AD/SC!
This is the code sample I'm using
// Session variables
String adminUsername = "<administrator sAMAccountName value>";
String adminPwd = "<admin password>";
String userDN = "<DN for the user being updated>";
String newPwd = "<The new password for the user being updated>";
String ipAddress = "<AD ip address>";
// LDAP configuration
String securityProtocol = "sasl";
String providerURL = "ldap://" + ipAddress;
Hashtable<Object, Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put("javax.security.sasl.strength", "high");
env.put("javax.security.sasl.policy.noplaintext", "true");
env.put(Context.PROVIDER_URL, providerURL);
env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
env.put(Context.SECURITY_PRINCIPAL, adminUsername);
env.put(Context.SECURITY_CREDENTIALS, adminPwd);
env.put(Context.SECURITY_PROTOCOL, securityProtocol);
env.put(Context.REFERRAL, "follow");
// Prepare the modifications list
String newQuotedPassword = "\"" + newPwd + "\"";
byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("unicodePwd", newUnicodePassword));
// Initiate the LDAP connection
LdapContext ctx = new InitialLdapContext(env, null);
// Modify the password
ctx.modifyAttributes(userDN, mods);
// Close LDAP connection
ctx.close();
Your help is much appreciated.
So, after a good deal of R&D. There is no way to update the password in the AD without using SSL. Java and MS AD are very strict on that.
I have a below code to reset password which uses PUMA API of IBM Portal:
Below code checks whether user entered old passoword correct or not:
loginService.checkPassword(userName, oldpswd.toCharArray());
Below code update the new password:
ibmPumaUtility.updateUserPassword(userName,resetPasswordForm.getNewPassword());
PumaController pController = pumaHome.getController();
PumaLocator locator = pumaHome.getLocator();
String defaultRealm = "xyz";
User user = locator.findUserByIdentifier("uid=" + username + ",cn=users,o=" + defaultRealm);
HashMap<String, String> userAttrs = new HashMap<String, String>();
userAttrs.put("password", updatedPassword);
pController.setAttributes(user, userAttrs);
The issue is, when user again logs in with old password, he is able to log in but only for some time 5-10 mins and after that he is able to login with the new password.
Can someone please suggest me how to resolve this.
the API call you use to validate if the "old password" is still okay:
loginService.checkPassword(userName, oldpswd.toCharArray());
Does in fact use a performance optimized validation on WAS level in default. This is matching to the most use-cases.
The "real login" won't let you in - that really checks the current password.
So if you open up a 2nd browser and try to login to portal at that time with "old password" you won't get a valid session.
(there is an exception for some LDAP servers as they still allow login with the old password for about 60min).
But if you require the API to return the correct value immediately there is a config option for this.
You switch the "basic login" to a "full login" by:
• Click WP AuthenticationService. Under Additional Properties select Custom Properties.
• Click New to create new custom properties.
• In the Name field, type authentication.basic.login.target
• In the Value field, type Portal_LTPA
• Click OK to confirm the changes.
• Save the configuration.
• Restart the server.
After this change the validation will leverage the complete Portal_LTPA login stack and return with an error if the "old password" is used.
I'm wondering what "principal" I should specify to login in to an Active Directory server. Should the principal be a user inside the AD I try to log into? Or it can be a user in the domain I specify as long as the user has privileges to access the AD?
I tried both with credentials error 49. But I can log in to the AD with ldp.exe by using the Administrator account of the server that AD is installed on.
Here is my code. Many thanks for any prompt help.
Hashtable env= new Hashtable(11);
env.put(Context.SECURITY_AUTHENTICATION,"simple"); // Also tried none w/ the same error
// What principal should I use??
env.put(Context.SECURITY_PRINCIPAL,"CN=Ross,OU=Eng,DC=RossInc");//User
//env.put(Context.SECURITY_PRINCIPAL, user + "#" + domain); // Tried w/ the same error
env.put(Context.SECURITY_CREDENTIALS, "ross");//Password
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,"ldap://myserver:389/DC=RossInc");
DirContext ctx = new InitialDirContext(env); <-- Fails with AuthenticationException: [LDAP: error code 49 - 8009030C
You either can provide:
NT-style login name
Kerberos UPN (implicit UPN)
explicit UPN (if additional UPN suffices have been defined)
More over, NEVER ever perform a simple bind! Either Digest or GSS-API.
According to the following example from Oracle site, the security Principal is a distinguished name.
Here is some code working for me from a computer inside the domain :
Hashtable<String, String> ldapEnv = new Hashtable<String, String>(11);
ldapEnv.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
ldapEnv.put(Context.PROVIDER_URL, "ldap://societe.fr:389");
ldapEnv.put(Context.SECURITY_AUTHENTICATION, "simple");
ldapEnv.put(Context.SECURITY_PRINCIPAL, "cn=administrateur,cn=users,dc=societe,dc=fr");
ldapEnv.put(Context.SECURITY_CREDENTIALS, "test.2011");
ldapContext = new InitialDirContext(ldapEnv);
The principal can be a user inside the AD as long as he has privileges to access the AD.
I've built a Silverlight website where users can create an account and login. Right now, users just create an account through a form and can directly login. I want to incorporate a email verification feature, where the user will receive an email with a verification URL and only then can he login. I also wish to incorporate a forgot password feature that sends an email to the users registered email address to recover password.
How can I do this in silverlight. I'm using Windows SQL Azure as the back-end database. Will I have to create a separate Application for creating user accounts and recovering passwords?
Hope this helps you out on part A of your problem.
I noticed the post might throw you off a bit, so I decided to write a method that will do this for you in the quickest amount of time.
public bool Send(string fromEmail, string toEmail, string subject, string body)
{
try
{
MailMessage message = new MailMessage();
message.From = new MailAddress(fromEmail);
message.To.Add(new MailAddress(toEmail));
message.Subject = subject;
message.Body = body;
message.IsBodyHtml = false;
SmtpClient smtp = new SmtpClient();
smtp.EnableSsl = true;
smtp.Send(message);
return true;
}
catch (Exception ex)
{
return false;
}
}
Essentially, once they create their account you would want to call this filling out all variables. Make sure in your body of text you have a link that sends them to a page where they can submit "activate" their account.
This will essentially be a bit value in the database that is set to false by default and won't be set to true until they click on the "submit" or "activate" button from the link that would be in the body of text.
For password recovery you would do the same. Except instead of sending them to a page to activate their account you'd send them to a page where they could just re-create their password. Since the database doesn't care if the password is old or new you could just send them to a page where they create a new password. You wouldn't even need to create a temp password for them (Unless you wanted to for experience and for a extra caution).
Happy Coding! ;)