I'm trying to write a script which will allow users to change their own passwords on Active Directory. After a successful bind as the user with their 'old' password, I'm using:
client.modify(userDN, [
new ldap.Change({
operation: 'delete',
modification: {
unicodePwd: encodePassword(oldPassword)
}
}),
new ldap.Change({
operation: 'add',
modification: {
unicodePwd: encodePassword(newPassword)
}
})
], function(err) {
if (err) {
console.log(err.dn);
console.log(err.code);
console.log(err.name);
console.log(err.message);
client.unbind();
}
else {
console.log('Password changed!');
}
});
Where encodePassword is defined as
function encodePassword(password) {
var newPassword = '';
password = '"' + password + '"';
for (var i = 0; i < password.length; i++) {
newPassword += password[i] + "\000";
}
return new Buffer(newPassword).toString('base64');
}
which was taken from http://www.cs.bham.ac.uk/~smp/resources/ad-passwds/
The PHP script + ldapmodify works fine but the Node.js script gives a ConstraintViolationError with the message
0000216C: AtrErr: DSID-03190EB0, #1:
0: 0000216C: DSID-03190EB0, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 9005a (unicodePwd)
The new password meets the constraints applied by the server (complexity etc) so I'm not sure what's going wrong. As the password change can only be done over ldaps I can't provide a Wireshark capture and I'm out of ideas. Any suggestions?
Well, (1) I'd try 'replace' instead of 'delete' (unless you actually have N
passwords set for these users and just want to get rid of one of them).
It's possible you're getting a constraint violation b/c the schema in AD
is such that a password must be set on those entries. I'm just stabbing
there, but it's something to try.
Otherwise, pass in a bunyan instance to the ldapjs createClient call with
the level set to trace. That will be pretty close to wireshark, but I'm
also certain wireshark won't tell you anything here extra anyway (that is,
there's not a protocol-level error here. This is at the app-level). I
don't really work with AD (intentionally...) - have you looked around MSDN
or whatever for those error codes?
m
On Mon, Sep 17, 2012 at 1:16 AM, Alex Whitman [email protected]:
I'm trying to write a script which will allow users to change their own
passwords on Active Directory. After a successful bind as the user with
their 'old' password, I'm using:client.modify(userDN, [
new ldap.Change({
operation: 'delete',
modification: {
unicodePwd: encodePassword(oldPassword)
}
}),
new ldap.Change({
operation: 'add',
modification: {
unicodePwd: encodePassword(newPassword)
}
})], function(err) {
if (err) {
console.log(err.dn);
console.log(err.code);
console.log(err.name);
console.log(err.message);
client.unbind();
}
else {
console.log('Password changed!');
}});Where encodePassword is defined as
function encodePassword(password) {
var newPassword = '';
password = '"' + password + '"';
for (var i = 0; i < password.length; i++) {
newPassword += password[i] + "\000";
}
return new Buffer(newPassword).toString('base64');}which was taken from http://www.cs.bham.ac.uk/~smp/resources/ad-passwds/
The PHP script + ldapmodify works fine but the Node.js script gives a
ConstraintViolationError with the message0000216C: AtrErr: DSID-03190EB0, #1:
0: 0000216C: DSID-03190EB0, problem 1005 (CONSTRAINT_ATT_TYPE), data 0, Att 9005a (unicodePwd)The new password meets the constraints applied by the server (complexity
etc) so I'm not sure what's going wrong. As the password change can only be
done over ldaps I can't provide a Wireshark capture and I'm out of ideas.
Any suggestions?—
Reply to this email directly or view it on GitHubhttps://github.com/mcavage/node-ldapjs/issues/92.
I'll try a replace but I don't think it will work. The MSDN article on unicodePwd (http://msdn.microsoft.com/en-us/library/cc223248%28v=prot.10%29.aspx) says a replace will only work if the user has the User-Force-Change-Password control access right which I don't believe is enabled for users on our servers, only admin accounts.
I'll grab a bunyan trace tomorrow when I'm back in the office.
Doing some more reading it seems the base64 encoding is only required for an ldif, the actual encoding should be utf16le (which is done by adding \000 to each character) so I've removed the conversion to base64.
The bunyan output for the modify request is:
sending request {"messageID":3,"protocolOp":"ModifyRequest","object":{"rdns":[{"cn":"username"},{"ou":"Misc"},{"ou":"People"},{"dc":"ad"},{"dc":"host"},{"dc":"com"}],"length":9},"changes":[{"operation":"delete","modification":{"type":"unicodePwd","vals":["\"\u0000o\u0000l\u0000d\u0000P\u0000a\u0000s\u0000s\u0000w\u0000o\u0000r\u0000d\u00001\u0000\"\u0000"]}},{"operation":"add","modification":{"type":"unicodePwd","vals":["\"\u0000n\u0000e\u0000w\u0000P\u0000a\u0000s\u0000s\u0000w\u0000o\u0000r\u0000d\u00001\u0000\"\u0000"]}}],"controls":[]} (clazz=Client, ldap_id=1__ldaps://active.directory.host)
I've changed the actual username/passwords/host in the above output as I can't really give them out but the passwords in this example are oldPassword1 and newPassword1. The only thing in the output that looks odd to me is how the double quotes are represented but that could just be how bunyan prints it.
Hi Witman,
I have a request. I have been unable to connect to Active Directory either through clientCreate() or client.bind(). Keep getting InvalidCredentialsError: 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db1 using DN, mail or domain\sAMAccountName. I am 100% sure my domain name, sAMAccountName and password are correct. Would you mind sharing the code you use to connect to AD?
Thanks
To change the password of an ActiveDirectory user you'll need the following things:
I'm using the following function to get the encoded password.
convertPassword: function(str){
var output = '';
str = '"' + str + '"';
for(var i = 0; i < str.length; i++){
output += String.fromCharCode( str.charCodeAt(i) & 0xFF,(str.charCodeAt(i) >>> 8) & 0xFF);
}
return output;
}
@PhilWaldmann Thanks. I'm back in the office today, I've just tried your code and I'm still getting the ContraintViolationError. I think the only thing left to look at is the permissions as the bind is successful.
Do you use replace?
Try to reset the password with your user via the Windows Interface first. If it works the permissions are ok.
The user accounts don't have the permission to use replace so I'm doing a delete and add in the same request as mentioned on http://msdn.microsoft.com/en-us/library/cc223248%28v=prot.10%29.aspx
Using the Windows interface works fine.
The full test script I'm using (with obvious parts changed) is:
var ldapHost = 'ldaps://active.directory.host';
var username = 'username';
var oldPassword = 'OLDPASSWORD';
var newPassword = 'NEWPASSWORD';
var ldap = require('ldapjs');
var client = ldap.createClient({
url: ldapHost
});
client.search('dc=ad,dc=domain,dc=co,dc=uk', {
filter: '(cn=' + username + ')',
attributes: 'dn',
scope: 'sub'
}, function(err, res) {
res.on('searchEntry', function(entry) {
var userDN = entry.object.dn;
client.bind(userDN, oldPassword, function(err) {
client.modify(userDN, [
new ldap.Change({
operation: 'delete',
modification: {
unicodePwd: encodePassword(oldPassword)
}
}),
new ldap.Change({
operation: 'add',
modification: {
unicodePwd: encodePassword(newPassword)
}
})
], function(err) {
if (err) {
console.log(err.code);
console.log(err.name);
console.log(err.message);
client.unbind();
}
else {
console.log('Password changed!');
}
});
});
});
});
function encodePassword(password) {
var newPassword = '';
password = "\"" + password + "\"";
for(var i = 0; i < password.length; i++){
newPassword += String.fromCharCode( password.charCodeAt(i) & 0xFF,(password.charCodeAt(i) >>> 8) & 0xFF);
}
return newPassword;
}
(@venktesh1 you might be interested in parts of the above as it connects and binds fine)
I think the ContraintViolationError occurs because at some point the user does not have a password... What happens if you add the new PW first, and remove the old one afterwards?
Thanks Alex.
@alexwhitman is right, per the MSFT docs, you should HAVE to do a delete/add if you are binding to the directory as a 'regular' use that only has rights to modify their own password. However, doing that does work, sorta. It's putting me in a state where both the old and new password work! If I do a replace, it does work, and only the new password remains valid.
Does anyone know how to handle the case where the user's password has expired? All I can find online is that you HAVE to make the replace command using an account with elevated permissions. That works, but it seems that bypasses the password policy. Anyone have any experience here?
function encodePassword(password) {
return new Buffer('"' + password + '"', 'utf16le').toString();
}
This works.
@alexwhitman could you please advise how you overcame this problem? i'm experiencing the exact same issue. thanks in advance!
@alidood82 I didn't get it working when I was originally looking at it. That said, I just tried the script again with the encodePassword function from @kfll and it all appears to work. Thanks @kfll.
thanks @alexwhitman! turned out my problem was the default password minimum age policy in AD which is 1 day. once I ran net accounts /MINPWAGE:0 from the command prompt everything worked fine.
@jefflage seems the old password is active for a few hours more even after a successful password change. It turns invalid eventually though. Perhaps some cache in Active Directory? Did you find a solution?
@joscarsson we did not find a solution. to be honest, no users have even noticed as they just assume that if it worked they need to use their new password.
we're actually going to go away from using LDAP commands to change passwords and move to use a dedicated node web service running on a windows box that will fork out to a windows DLL for password changes. it seems not to have this issue. it also has the advantage that it allows users with expired passwords to change their own password, which you cannot do with LDAP commands against an AD without using an administrative account, which then bypasses all password policy.
@jefflage thanks for the info!
It's probably a good idea to move away from LDAP, the protocol (or Microsoft's implementation of it) certainly has it quirks.
With ldapjs, do you share our view that the old password eventually is not possible to use, or can your users keep using their old password forever?
@joscarsson correct. it does eventually clear ou
I'm unable to change the user password using ldapjs.
Can someone please help me with this error?
I didn't find anything and tried every possible thing like replace, add, delete, with SSL, without SSL
For SSL connection I've used this code:
await ldap.createClient({
url: ldap://${host},
tlsOptions: {
ca: [
fs.readFileSync('filename.pem'),
]
}
});
I'm using this function to pass password:
encodePassword(password) {
return Buffer.from('"' + password + '"', 'utf16le').toString('base64');
}
This is my change object:
[
new ldap.Change({
operation: 'delete',
modification: {
unicodePwd: this.encodePassword('Admin@123')
}
}),
new ldap.Change({
operation: 'add',
modification: {
unicodePwd: this.encodePassword('Changed@123')
}
})
]
Getting this error:
UnwillingToPerformError
00002077: SvcErr: DSID-03190EBE, problem 5003 (WILL_NOT_PERFORM), data 0
If you connect to an ActiveDirectory, you need to connect via ldaps (port 636).
The format of the password is also important. Here is some working code to transform the password string to the unicodePwd.
I'm not sure if a delete operation is allowed for the unicodePwd field, try replace instead
@PhilWaldmann Thanks for the quick response.
I'm able to change displayName using replace so I have successfully created a connection with my active directory.
I was trying to change the unicodePwd variable from an active directory server.
Steps I've followed:
When I am trying to save the configuration it throws me an error.
Operation Failed. Error code: 0x1f
A device attached to the system is not functioning.
0000001F: SvcErr: DSID-031A1262, problem 5003 (WILL_NOT_PERFORM), data 0
So I am unable to change or set that attribute's value from the server itself maybe that's why I am unable to change that attribute's value from code also.
Please help with this.
Thank you.
Did you read the following?
You can't read the unicodePwd attribute. That's probably the reason why you wont find ich in the Attributes Editor.
However, in order to sehr the passwort your AD needs to be configured properly.
The Section "Process for a UnicodePwd Change" describes the delete + add vs replace operations in detail. So ignore my last comment about delete.
I was working on an administration tool for ActiveDirectory and OpenLDAP Servers a few years ago and the main problems were the proper AD configuration and the unicodePwd format. The AD is sometimes a real pain in the a** ;)
Most helpful comment
To change the password of an ActiveDirectory user you'll need the following things:
I'm using the following function to get the encoded password.