I need to implement an "update password" feature in a Python web application (Flask). The goal is to allow user to self-update his password on a remote Active Directory server.

I ended with this quick unit test

import os
import ldap

def test_change_passwd():
    ad_server = "ldaps://ad.xxx_domain.com"
    ad_dn = "CN={0},OU=Users,OU=AF,DC=xxx_domain,DC=com"

    username = 'my_username'
    old_pwd = 'the_old_complicated_password'
    new_pwd = 'the_new_complicated_password'

    cert = os.path.join('/path', "to", 'server_cert.cer')

    # LDAP connection initialization
    l = ldap.initialize(ad_server)
    # Set LDAP protocol version used
    l.protocol_version = ldap.VERSION3
    # Force cert validation
    l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
    # Set path name of file containing all trusted CA certificates
    l.set_option(ldap.OPT_X_TLS_CACERTFILE, cert)
    # Force libldap to create a new SSL context (must be last TLS option!)
    l.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
    # Bind
    l.simple_bind_s(ad_dn.format(username), old_pwd)

    # Now, perform the password update
    newpwd_utf16 = '"{0}"'.format(new_pwd).encode('utf-16-le')
    mod_list = [(ldap.MOD_REPLACE, "unicodePwd", newpwd_utf16)]
    l.modify_s(ad_dn.format(username), mod_list)

When I run it, it fail on the last line (l.modify_s()) with this error:

INSUFFICIENT_ACCESS: {'info': u'00000005: SecErr: DSID-031A11D7, problem 4003 (INSUFF_ACCESS_RIGHTS), data 0\n', 'desc': u'Insufficient access'}

I don't know if the issue come from my python code or a bad configuration of my user on the directory. In addition, I'm not the administrator of AD server (and haven't any knowledge in this).

Do I need to set something special on my user to allow him to self-update his password ? Do I use the right method to update the password ?

Note : I also tried this method to update password, without success:

l.passwd_s(dn.format(username), old_pwd, new_pwd)

fail with error:

PROTOCOL_ERROR({'info': u'0000203D: LdapErr: DSID-0C0911D4, comment: Unknown extended request OID, data 0, v3839', 'desc': u'Protocol error'},)

I read everywhere that this function should not be used with AD servers...


MS Active Directory distinguishes two different use-cases.

Note: old_passwd_value and new_passwd_value both have to be the weird double-quoted low-ending UTF-16 encoding like in your code snippet.

If an admin sets the password of another user you can use your code:

mod_list = [ (ldap0.MOD_REPLACE, 'unicodePwd', [new_passwd_value]), ]

If the user changes his own password you have to use:

mod_list = [ (ldap0.MOD_DELETE, 'unicodePwd', [old_passwd_value]), (ldap0.MOD_ADD, 'unicodePwd', [new_passwd_value]), ]

| improve this answer | |
  • To be honest, I already tested the "delete then add" solution, but I didn't think to put both in the same modlist in a single modify_s() call (I called modify_s() twice, 1 to delete and 1 more time to add. Thank you very much – Antwane Oct 26 '18 at 12:39
  • In general you should try to minimize the number of modify operations because a single write operation is guaranteed to be atomic. In this particular case both mods are related and cannot be divided. There are other cases where mods cannot be divided, e.g. adding an object class and together with the required attribute(s). – Michael Ströder Oct 26 '18 at 12:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.