From efb60e42822a8350c4916855c0ba68c2da67dd3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 20 Aug 2019 10:03:47 +0100 Subject: [PATCH 1/4] Extract new password in passwd_s (#246) --- Lib/ldap/extop/__init__.py | 1 + Lib/ldap/extop/passwd.py | 33 ++++++++++++++++++++++++++++++++ Lib/ldap/ldapobject.py | 15 +++++++++++---- Tests/t_ldapobject.py | 39 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 Lib/ldap/extop/passwd.py diff --git a/Lib/ldap/extop/__init__.py b/Lib/ldap/extop/__init__.py index 874166d9..39e653a9 100644 --- a/Lib/ldap/extop/__init__.py +++ b/Lib/ldap/extop/__init__.py @@ -65,3 +65,4 @@ def decodeResponseValue(self,value): # Import sub-modules from ldap.extop.dds import * +from ldap.extop.passwd import PasswordModifyResponse diff --git a/Lib/ldap/extop/passwd.py b/Lib/ldap/extop/passwd.py new file mode 100644 index 00000000..0a8346a8 --- /dev/null +++ b/Lib/ldap/extop/passwd.py @@ -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 diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index a92b0886..2e9674dc 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -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 @@ -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): + 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 respvalue: + respvalue = PasswordModifyResponse(PasswordModifyResponse.responseName, respvalue) + + return respoid, respvalue def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None): """ diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 24711b21..5b162380 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -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) + 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): """ From c812258768516fcc0da3046bd9ef3ef0e945bd0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 20 Aug 2019 10:56:38 +0100 Subject: [PATCH 2/4] Update documentation --- Doc/reference/ldap.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 1d1b025e..248a52ee 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -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]]) -> (respoid, respvalue) Perform a ``LDAP Password Modify Extended Operation`` operation on the entry specified by *user*. @@ -974,6 +974,11 @@ 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 generates a new random password. With :py:meth:`passwd_s()` + method, this password is available through ``respvalue.genPasswd``. + *serverctrls* and *clientctrls* like described in section :ref:`ldap-controls`. The asynchronous version returns the initiated message id. @@ -983,6 +988,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` From f6dac66cdfe8083d3926cde359ad9a9a065191f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 20 Aug 2019 11:48:47 +0100 Subject: [PATCH 3/4] Doc for extract_newpw --- Doc/reference/ldap.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 248a52ee..06ecb906 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -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]]) -> (respoid, respvalue) +.. 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*. @@ -974,10 +974,12 @@ 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 generates a new random password. With :py:meth:`passwd_s()` - method, this password is available through ``respvalue.genPasswd``. + *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`. From 2550717ba440f8fc56a600d79665b9477f4f2b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 20 Aug 2019 11:50:09 +0100 Subject: [PATCH 4/4] Impl for extract_newpw --- Lib/ldap/ldapobject.py | 4 ++-- Tests/t_ldapobject.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 2e9674dc..ab654e17 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -656,13 +656,13 @@ 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): + 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 respvalue: + if extract_newpw and respvalue: respvalue = PasswordModifyResponse(PasswordModifyResponse.responseName, respvalue) return respoid, respvalue diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 5b162380..1ec00280 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -711,7 +711,7 @@ def test_passwd_s(self): l.passwd_s(dn, "bogus", "ignored") # have the server generate a new random pw - respoid, respvalue = l.passwd_s(dn, "initial", None) + respoid, respvalue = l.passwd_s(dn, "initial", None, extract_newpw=True) self.assertEqual(respoid, None) password = respvalue.genPasswd 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