Skip to content

Extract new password in passwd_s (#246) #299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PasswordModifyResponse.responseName is set to None. Does the password control always return no OID?

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
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