Skip to content

Commit a35462a

Browse files
committed
wip
1 parent d093559 commit a35462a

File tree

12 files changed

+903
-68
lines changed

12 files changed

+903
-68
lines changed

Doc/spelling_wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ refreshDeletes
117117
refreshOnly
118118
requestName
119119
requestValue
120+
responseName
120121
resiter
121122
respvalue
122123
ResultProcessor

Lib/ldap/connection.py

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717
import ldap
1818
from ldap.controls import DecodeControlTuples, RequestControl
1919
from ldap.extop import ExtendedRequest
20+
from ldap.extop.passwd import PasswordModifyResponse
2021
from ldap.ldapobject import SimpleLDAPObject, NO_UNIQUE_ENTRY
2122
from ldap.response import (
2223
Response,
23-
SearchEntry, SearchReference, SearchResult,
24+
SearchEntry, SearchReference,
2425
IntermediateResponse, ExtendedResult,
2526
)
2627

@@ -32,10 +33,15 @@ class Connection(SimpleLDAPObject):
3233
resp_ctrl_classes = None
3334

3435
def result(self, msgid: int = ldap.RES_ANY, *, all: int = 1,
35-
timeout: Optional[float] = None) -> Optional[list[Response]]:
36+
timeout: Optional[float] = None,
37+
defaultIntermediateClass:
38+
Optional[type[IntermediateResponse]] = None,
39+
defaultExtendedClass: Optional[type[ExtendedResult]] = None
40+
) -> Optional[list[Response]]:
3641
"""
37-
result([msgid: int = RES_ANY [, all: int = 1 [, timeout :
38-
Optional[float] = None]]]) -> Optional[list[Response]]
42+
result([msgid: int = RES_ANY [, all: int = 1 [,
43+
timeout: Optional[float] = None]]])
44+
-> Optional[list[Response]]
3945
4046
This method is used to wait for and return the result of an
4147
operation previously initiated by one of the LDAP asynchronous
@@ -87,13 +93,26 @@ def result(self, msgid: int = ldap.RES_ANY, *, all: int = 1,
8793

8894
results = []
8995
for msgid, msgtype, controls, data in messages:
90-
controls = DecodeControlTuples(controls, self.resp_ctrl_classes)
96+
if controls is not None:
97+
controls = DecodeControlTuples(controls, self.resp_ctrl_classes)
9198

99+
if msgtype == ldap.RES_INTERMEDIATE:
100+
data['defaultClass'] = defaultIntermediateClass
101+
if msgtype == ldap.RES_EXTENDED:
102+
data['defaultClass'] = defaultExtendedClass
92103
m = Response(msgid, msgtype, controls, **data)
93104
results.append(m)
94105

95106
return results
96107

108+
def add_s(self, dn: str,
109+
modlist: list[tuple[str, Union[bytes, list[bytes]]]], *,
110+
ctrls: RequestControls = None) -> ldap.response.AddResult:
111+
msgid = self.add_ext(dn, modlist, serverctrls=ctrls)
112+
responses = self.result(msgid)
113+
result, = responses
114+
return result
115+
97116
def bind_s(self, dn: Optional[str] = None,
98117
cred: Union[None, str, bytes] = None, *,
99118
method: int = ldap.AUTH_SIMPLE,
@@ -119,17 +138,36 @@ def delete_s(self, dn: str, *,
119138
result, = responses
120139
return result
121140

122-
def extop_s(self, oid: Optional[str] = None,
141+
def extop_s(self, name: Optional[str] = None,
123142
value: Optional[bytes] = None, *,
124143
request: Optional[ExtendedRequest] = None,
125-
ctrls: RequestControls = None
144+
ctrls: RequestControls = None,
145+
defaultIntermediateClass: Optional[type[IntermediateResponse]] = None,
146+
defaultExtendedClass: Optional[type[ExtendedResult]] = None
126147
) -> list[Union[IntermediateResponse, ExtendedResult]]:
127148
if request is not None:
128-
oid = request.requestName
149+
name = request.requestName
129150
value = request.encodedRequestValue()
130151

131-
msgid = self.extop(oid, value, serverctrls=ctrls)
132-
return self.result(msgid)
152+
msgid = self.extop(name, value, serverctrls=ctrls)
153+
return self.result(msgid,
154+
defaultIntermediateClass=defaultIntermediateClass,
155+
defaultExtendedClass=defaultExtendedClass)
156+
157+
def modify_s(self, dn: str,
158+
modlist: list[tuple[str, Union[bytes, list[bytes]]]], *,
159+
ctrls: RequestControls = None) -> ldap.response.ModifyResult:
160+
msgid = self.modify_ext(dn, modlist, serverctrls=ctrls)
161+
responses = self.result(msgid)
162+
result, = responses
163+
return result
164+
165+
def passwd_s(self, user: Optional[str] = None,
166+
oldpw: Optional[bytes] = None, newpw: Optional[bytes] = None,
167+
ctrls: RequestControls = None) -> PasswordModifyResponse:
168+
msgid = self.passwd(user, oldpw, newpw, serverctrls=ctrls)
169+
res, = self.result(msgid, defaultExtendedClass=PasswordModifyResponse)
170+
return res
133171

134172
def search_s(self, base: Optional[str] = None,
135173
scope: int = ldap.SCOPE_SUBTREE,
@@ -147,8 +185,11 @@ def search_s(self, base: Optional[str] = None,
147185
attrsonly=attrsonly, serverctrls=ctrls,
148186
sizelimit=sizelimit, timeout=timelimit)
149187
result = self.result(msgid, timeout=timeout)
188+
# FIXME: we want a better way of returning a result with multiple
189+
# messages, always useful in searches but other operations can also
190+
# elicit those (by way of an IntermediateResponse)
150191
result[-1].raise_for_result()
151-
return result[:-1]
192+
return result
152193

153194
def search_subschemasubentry_s(
154195
self, dn: Optional[str] = None) -> Optional[str]:
@@ -212,6 +253,6 @@ def find_unique_entry(self, base: Optional[str] = None,
212253
r = self.search_s(base, scope, filter, attrlist=attrlist,
213254
attrsonly=attrsonly, ctrls=ctrls, timeout=timeout,
214255
sizelimit=2)
215-
if len(r) != 1:
256+
if len(r) != 2:
216257
raise NO_UNIQUE_ENTRY(f'No or non-unique search result for {filter}')
217258
return r[0]

Lib/ldap/extop/__init__.py

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
from ldap import __version__
1313
from ldap import KNOWN_EXTENDED_RESPONSES, KNOWN_INTERMEDIATE_RESPONSES
1414

15+
import ldap
16+
import ldap.response
17+
18+
from typing import Optional
19+
20+
_NOTSET = object()
21+
1522

1623
class ExtendedRequest:
1724
"""
@@ -39,7 +46,7 @@ def encodedRequestValue(self):
3946
return self.requestValue
4047

4148

42-
class ExtendedResponse:
49+
class ExtendedResponse(ldap.response.ExtendedResult):
4350
"""
4451
Generic base class for a LDAPv3 extended operation response
4552
@@ -55,9 +62,116 @@ def __init_subclass__(cls):
5562

5663
KNOWN_EXTENDED_RESPONSES.setdefault(cls.responseName, cls)
5764

58-
def __init__(self,responseName,encodedResponseValue):
65+
@classmethod
66+
def __convert_old_api(cls, responseName_or_msgid=_NOTSET,
67+
encodedResponseValue_or_msgtype=_NOTSET,
68+
controls=None, *,
69+
result=_NOTSET, matcheddn=_NOTSET, message=_NOTSET,
70+
referrals=_NOTSET, name=None, value=None,
71+
defaultClass: Optional[type['ExtendedResult']] = None,
72+
msgid=_NOTSET, msgtype=_NOTSET,
73+
responseName=_NOTSET, encodedResponseValue=_NOTSET,
74+
**kwargs):
75+
"""
76+
Implements both old and new API:
77+
__init__(self, responseName, encodedResponseValue)
78+
and
79+
__init__/__new__(self, msgid, msgtype, controls=None, *,
80+
result, matcheddn, message, referrals,
81+
defaultClass=None, **kwargs)
82+
"""
83+
if responseName is not _NOTSET:
84+
name = responseName
85+
value = encodedResponseValue
86+
msgid = None
87+
msgtype = ldap.RES_EXTENDED
88+
result = ldap.SUCCESS.errnum
89+
elif responseName_or_msgid is not _NOTSET and \
90+
isinstance(responseName_or_msgid, (str, type(None))):
91+
if responseName is not _NOTSET:
92+
raise TypeError("responseName passed twice")
93+
if encodedResponseValue_or_msgtype is not _NOTSET and \
94+
encodedResponseValue is not _NOTSET:
95+
raise TypeError("encodedResponseValue passed twice")
96+
name = responseName = responseName_or_msgid
97+
value = encodedResponseValue = encodedResponseValue_or_msgtype
98+
msgid = None
99+
msgtype = ldap.RES_EXTENDED
100+
result = ldap.SUCCESS.errnum
101+
else:
102+
responseName = name
103+
encodedResponseValue = value
104+
if msgid is _NOTSET:
105+
if responseName_or_msgid is _NOTSET:
106+
raise TypeError("msgid parameter not provided")
107+
msgid = responseName_or_msgid
108+
if msgtype is _NOTSET:
109+
if encodedResponseValue_or_msgtype is _NOTSET:
110+
raise TypeError("msgtype parameter not provided")
111+
msgtype = encodedResponseValue_or_msgtype or ldap.RES_EXTENDED
112+
if result is _NOTSET:
113+
raise TypeError("result parameter not provided")
114+
if matcheddn is _NOTSET:
115+
raise TypeError("matcheddn parameter not provided")
116+
if message is _NOTSET:
117+
raise TypeError("message parameter not provided")
118+
if referrals is _NOTSET:
119+
raise TypeError("referrals parameter not provided")
120+
121+
return (
122+
responseName, encodedResponseValue,
123+
(msgid, msgtype, controls),
124+
{'result': result,
125+
'matcheddn': matcheddn,
126+
'message': message,
127+
'referrals': referrals,
128+
'name': name,
129+
'value': value,
130+
'defaultClass': defaultClass,
131+
**kwargs
132+
}
133+
)
134+
135+
def __new__(cls, *args, **kwargs):
136+
"""
137+
Has to support both old and new API:
138+
__new__(cls, responseName: Optional[str],
139+
encodedResponseValue: Optional[bytes])
140+
and
141+
__new__(cls, msgid: int, msgtype: int, controls: Controls = None, *,
142+
result: int, matcheddn: str, message: str, referrals: List[str],
143+
defaultClass: Optional[type[ExtendedResponse]] = None,
144+
**kwargs)
145+
146+
The old API is deprecated and will be removed in 4.0.
147+
"""
148+
# TODO: retire polymorhpism when old API is removed (4.0?)
149+
_, _, args, kwargs = __class__.__convert_old_api(*args, **kwargs)
150+
151+
return super().__new__(cls, *args, **kwargs)
152+
153+
def __init__(self, *args, **kwargs):
154+
"""
155+
Supports both old and new API:
156+
__init__(self, responseName: Optional[str],
157+
encodedResponseValue: Optional[bytes])
158+
and
159+
__init__(self, msgid: int, msgtype: int, controls: Controls = None, *,
160+
result: int, matcheddn: str, message: str, referrals: List[str],
161+
defaultClass: Optional[type[ExtendedResponse]] = None,
162+
**kwargs)
163+
164+
The old API is deprecated and will be removed in 4.0.
165+
"""
166+
# TODO: retire polymorhpism when old API is removed (4.0?)
167+
responseName, encodedResponseValue, _, _ = \
168+
__class__.__convert_old_api(*args, **kwargs)
169+
59170
self.responseName = responseName
60-
self.responseValue = self.decodeResponseValue(encodedResponseValue)
171+
if encodedResponseValue is not None:
172+
self.responseValue = self.decodeResponseValue(encodedResponseValue)
173+
else:
174+
self.responseValue = None
61175

62176
def __repr__(self):
63177
return f'{self.__class__.__name__}({self.responseName},{self.responseValue})'
@@ -70,7 +184,7 @@ def decodeResponseValue(self,value):
70184
return value
71185

72186

73-
class IntermediateResponse:
187+
class IntermediateResponse(ldap.response.IntermediateResponse):
74188
"""
75189
Generic base class for a LDAPv3 intermediate response message
76190

Lib/ldap/ldapobject.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,14 +380,17 @@ def extop_result(self,msgid=ldap.RES_ANY,all=1,timeout=None):
380380

381381
def extop_s(self,extreq,serverctrls=None,clientctrls=None,extop_resp_class=None):
382382
msgid = self.extop(extreq,serverctrls,clientctrls)
383-
res = self.extop_result(msgid,all=1,timeout=self.timeout)
383+
resulttype,_,msgid,respctrls,respoid,respvalue = self.extop_result(msgid,all=1,timeout=self.timeout)
384+
extop_resp_class = extop_resp_class or KNOWN_EXTENDED_RESPONSES.get(respoid)
384385
if extop_resp_class:
385-
respoid,respvalue = res
386386
if extop_resp_class.responseName!=respoid:
387387
raise ldap.PROTOCOL_ERROR(f"Wrong OID in extended response! Expected {extop_resp_class.responseName}, got {respoid}")
388-
return extop_resp_class(extop_resp_class.responseName,respvalue)
388+
return extop_resp_class(msgid, resulttype, respctrls,
389+
result=0, matcheddn=None,
390+
message=None, referrals=None,
391+
name=respoid, value=respvalue)
389392
else:
390-
return res
393+
return respoid, respvalue
391394

392395
def modify_ext(self,dn,modlist,serverctrls=None,clientctrls=None):
393396
"""

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