Skip to content

Commit 9a91bbd

Browse files
authored
Extract new password in passwd_s
Fixes: python-ldap#246 python-ldap#299
1 parent c803bfc commit 9a91bbd

File tree

5 files changed

+93
-5
lines changed

5 files changed

+93
-5
lines changed

Doc/reference/ldap.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ and wait for and return with the server's result, or with
963963

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

977+
*respoid* is always :py:const:`None`. *respvalue* is also
978+
:py:const:`None` unless *newpw* was :py:const:`None`. This requests that
979+
the server generate a new random password. If *extract_newpw* is
980+
:py:const:`True`, this password is a bytes object available through
981+
``respvalue.genPasswd``, otherwise *respvalue* is the raw ASN.1 response
982+
(this is deprecated and only for backwards compatibility).
983+
977984
*serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`.
978985

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

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

987995

988996

Lib/ldap/extop/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ def decodeResponseValue(self,value):
6565

6666
# Import sub-modules
6767
from ldap.extop.dds import *
68+
from ldap.extop.passwd import PasswordModifyResponse

Lib/ldap/extop/passwd.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
ldap.extop.passwd - Classes for Password Modify extended operation
4+
(see RFC 3062)
5+
6+
See https://www.python-ldap.org/ for details.
7+
"""
8+
9+
from ldap.extop import ExtendedResponse
10+
11+
# Imports from pyasn1
12+
from pyasn1.type import namedtype, univ, tag
13+
from pyasn1.codec.der import decoder
14+
15+
16+
class PasswordModifyResponse(ExtendedResponse):
17+
responseName = None
18+
19+
class PasswordModifyResponseValue(univ.Sequence):
20+
componentType = namedtype.NamedTypes(
21+
namedtype.OptionalNamedType(
22+
'genPasswd',
23+
univ.OctetString().subtype(
24+
implicitTag=tag.Tag(tag.tagClassContext,
25+
tag.tagFormatSimple, 0)
26+
)
27+
)
28+
)
29+
30+
def decodeResponseValue(self, value):
31+
respValue, _ = decoder.decode(value, asn1Spec=self.PasswordModifyResponseValue())
32+
self.genPasswd = bytes(respValue.getComponentByName('genPasswd'))
33+
return self.genPasswd

Lib/ldap/ldapobject.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727

2828
from ldap.schema import SCHEMA_ATTRS
2929
from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples
30-
from ldap.extop import ExtendedRequest,ExtendedResponse
30+
from ldap.extop import ExtendedRequest,ExtendedResponse,PasswordModifyResponse
3131
from ldap.compat import reraise
3232

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

659-
def passwd_s(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
660-
msgid = self.passwd(user,oldpw,newpw,serverctrls,clientctrls)
661-
return self.extop_result(msgid,all=1,timeout=self.timeout)
659+
def passwd_s(self, user, oldpw, newpw, serverctrls=None, clientctrls=None, extract_newpw=False):
660+
msgid = self.passwd(user, oldpw, newpw, serverctrls, clientctrls)
661+
respoid, respvalue = self.extop_result(msgid, all=1, timeout=self.timeout)
662+
663+
if respoid != PasswordModifyResponse.responseName:
664+
raise ldap.PROTOCOL_ERROR("Unexpected OID %s in extended response!" % respoid)
665+
if extract_newpw and respvalue:
666+
respvalue = PasswordModifyResponse(PasswordModifyResponse.responseName, respvalue)
667+
668+
return respoid, respvalue
662669

663670
def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
664671
"""

Tests/t_ldapobject.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,45 @@ def test_async_search_no_such_object_exception_contains_message_id(self):
687687
self._ldap_conn.result()
688688
self.assertEqual(cm.exception.args[0]["msgid"], msgid)
689689

690+
def test_passwd_s(self):
691+
l = self._ldap_conn
692+
693+
# first, create a user to change password on
694+
dn = "cn=PasswordTest," + self.server.suffix
695+
result, pmsg, msgid, ctrls = l.add_ext_s(
696+
dn,
697+
[
698+
('objectClass', b'person'),
699+
('sn', b'PasswordTest'),
700+
('cn', b'PasswordTest'),
701+
('userPassword', b'initial'),
702+
]
703+
)
704+
self.assertEqual(result, ldap.RES_ADD)
705+
self.assertIsInstance(msgid, int)
706+
self.assertEqual(pmsg, [])
707+
self.assertEqual(ctrls, [])
708+
709+
# try changing password with a wrong old-pw
710+
with self.assertRaises(ldap.UNWILLING_TO_PERFORM):
711+
l.passwd_s(dn, "bogus", "ignored")
712+
713+
# have the server generate a new random pw
714+
respoid, respvalue = l.passwd_s(dn, "initial", None, extract_newpw=True)
715+
self.assertEqual(respoid, None)
716+
717+
password = respvalue.genPasswd
718+
self.assertIsInstance(password, bytes)
719+
if PY2:
720+
password = password.decode('utf-8')
721+
722+
# try changing password back
723+
respoid, respvalue = l.passwd_s(dn, password, "initial")
724+
self.assertEqual(respoid, None)
725+
self.assertEqual(respvalue, None)
726+
727+
l.delete_s(dn)
728+
690729

691730
class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject):
692731
"""

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy