Skip to content

Commit

Permalink
Extract new password in passwd_s
Browse files Browse the repository at this point in the history
  • Loading branch information
Ondřej Kuzník authored and GitHub committed Jun 5, 2020
1 parent c803bfc commit 9a91bbd
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 5 deletions.
10 changes: 9 additions & 1 deletion Doc/reference/ldap.rst
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,7 @@ and wait for and return with the server's result, or with

.. py:method:: LDAPObject.passwd(user, oldpw, newpw [, serverctrls=None [, clientctrls=None]]) -> int
.. py:method:: LDAPObject.passwd_s(user, oldpw, newpw [, serverctrls=None [, clientctrls=None]]) -> None
.. py:method:: LDAPObject.passwd_s(user, oldpw, newpw [, serverctrls=None [, clientctrls=None] [, extract_newpw=False]]]) -> (respoid, respvalue)
Perform a ``LDAP Password Modify Extended Operation`` operation
on the entry specified by *user*.
Expand All @@ -974,6 +974,13 @@ and wait for and return with the server's result, or with
of the specified *user* which is sometimes used when a user changes
his own password.

*respoid* is always :py:const:`None`. *respvalue* is also
:py:const:`None` unless *newpw* was :py:const:`None`. This requests that
the server generate a new random password. If *extract_newpw* is
:py:const:`True`, this password is a bytes object available through
``respvalue.genPasswd``, otherwise *respvalue* is the raw ASN.1 response
(this is deprecated and only for backwards compatibility).

*serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`.

The asynchronous version returns the initiated message id.
Expand All @@ -983,6 +990,7 @@ and wait for and return with the server's result, or with
.. seealso::

:rfc:`3062` - LDAP Password Modify Extended Operation
:py:mod:`ldap.extop.passwd`



Expand Down
1 change: 1 addition & 0 deletions Lib/ldap/extop/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ def decodeResponseValue(self,value):

# Import sub-modules
from ldap.extop.dds import *
from ldap.extop.passwd import PasswordModifyResponse
33 changes: 33 additions & 0 deletions Lib/ldap/extop/passwd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
"""
ldap.extop.passwd - Classes for Password Modify extended operation
(see RFC 3062)
See https://www.python-ldap.org/ for details.
"""

from ldap.extop import ExtendedResponse

# Imports from pyasn1
from pyasn1.type import namedtype, univ, tag
from pyasn1.codec.der import decoder


class PasswordModifyResponse(ExtendedResponse):
responseName = None

class PasswordModifyResponseValue(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.OptionalNamedType(
'genPasswd',
univ.OctetString().subtype(
implicitTag=tag.Tag(tag.tagClassContext,
tag.tagFormatSimple, 0)
)
)
)

def decodeResponseValue(self, value):
respValue, _ = decoder.decode(value, asn1Spec=self.PasswordModifyResponseValue())
self.genPasswd = bytes(respValue.getComponentByName('genPasswd'))
return self.genPasswd
15 changes: 11 additions & 4 deletions Lib/ldap/ldapobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

from ldap.schema import SCHEMA_ATTRS
from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples
from ldap.extop import ExtendedRequest,ExtendedResponse
from ldap.extop import ExtendedRequest,ExtendedResponse,PasswordModifyResponse
from ldap.compat import reraise

from ldap import LDAPError
Expand Down Expand Up @@ -656,9 +656,16 @@ def passwd(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
newpw = self._bytesify_input('newpw', newpw)
return self._ldap_call(self._l.passwd,user,oldpw,newpw,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))

def passwd_s(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
msgid = self.passwd(user,oldpw,newpw,serverctrls,clientctrls)
return self.extop_result(msgid,all=1,timeout=self.timeout)
def passwd_s(self, user, oldpw, newpw, serverctrls=None, clientctrls=None, extract_newpw=False):
msgid = self.passwd(user, oldpw, newpw, serverctrls, clientctrls)
respoid, respvalue = self.extop_result(msgid, all=1, timeout=self.timeout)

if respoid != PasswordModifyResponse.responseName:
raise ldap.PROTOCOL_ERROR("Unexpected OID %s in extended response!" % respoid)
if extract_newpw and respvalue:
respvalue = PasswordModifyResponse(PasswordModifyResponse.responseName, respvalue)

return respoid, respvalue

def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
"""
Expand Down
39 changes: 39 additions & 0 deletions Tests/t_ldapobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,45 @@ def test_async_search_no_such_object_exception_contains_message_id(self):
self._ldap_conn.result()
self.assertEqual(cm.exception.args[0]["msgid"], msgid)

def test_passwd_s(self):
l = self._ldap_conn

# first, create a user to change password on
dn = "cn=PasswordTest," + self.server.suffix
result, pmsg, msgid, ctrls = l.add_ext_s(
dn,
[
('objectClass', b'person'),
('sn', b'PasswordTest'),
('cn', b'PasswordTest'),
('userPassword', b'initial'),
]
)
self.assertEqual(result, ldap.RES_ADD)
self.assertIsInstance(msgid, int)
self.assertEqual(pmsg, [])
self.assertEqual(ctrls, [])

# try changing password with a wrong old-pw
with self.assertRaises(ldap.UNWILLING_TO_PERFORM):
l.passwd_s(dn, "bogus", "ignored")

# have the server generate a new random pw
respoid, respvalue = l.passwd_s(dn, "initial", None, extract_newpw=True)
self.assertEqual(respoid, None)

password = respvalue.genPasswd
self.assertIsInstance(password, bytes)
if PY2:
password = password.decode('utf-8')

# try changing password back
respoid, respvalue = l.passwd_s(dn, password, "initial")
self.assertEqual(respoid, None)
self.assertEqual(respvalue, None)

l.delete_s(dn)


class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject):
"""
Expand Down

0 comments on commit 9a91bbd

Please sign in to comment.