Skip to content

Commit cd712ab

Browse files
committed
Draft new response handling API
1 parent e75c24d commit cd712ab

File tree

6 files changed

+819
-11
lines changed

6 files changed

+819
-11
lines changed

Lib/ldap/connection.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
"""
2+
connection.py - wraps class _ldap.LDAPObject
3+
4+
See https://www.python-ldap.org/ for details.
5+
"""
6+
7+
from ldap.pkginfo import __version__, __author__, __license__
8+
9+
__all__ = [
10+
'Connection',
11+
]
12+
13+
14+
if __debug__:
15+
# Tracing is only supported in debugging mode
16+
import traceback
17+
18+
import sys,time,pprint,_ldap,ldap,ldap.sasl,ldap.functions
19+
import warnings
20+
21+
from ldap import LDAPError
22+
from ldap.controls import DecodeControlTuples
23+
from ldap.ldapobject import SimpleLDAPObject
24+
from ldap.response import Response
25+
26+
27+
class Connection(SimpleLDAPObject):
28+
resp_ctrl_classes = None
29+
30+
def result(self, msgid=ldap.RES_ANY, all=1, timeout=None):
31+
"""
32+
result([msgid=RES_ANY [,all=1 [,timeout=None]]]) -> Optional[list[LDAPMessage]]
33+
34+
This method is used to wait for and return the result of an
35+
operation previously initiated by one of the LDAP asynchronous
36+
operation routines (e.g. search(), modify(), etc.) They all
37+
return an invocation identifier (a message id) upon successful
38+
initiation of their operation. This id is guaranteed to be
39+
unique across an LDAP session, and can be used to request the
40+
result of a specific operation via the msgid parameter of the
41+
result() method.
42+
43+
If the result of a specific operation is required, msgid should
44+
be set to the invocation message id returned when the operation
45+
was initiated; otherwise RES_ANY should be supplied.
46+
47+
The all parameter is used to wait until a final response for
48+
a given operation is received, this is useful with operations
49+
(like search) that generate multiple responses and is used
50+
to select whether a single item should be returned or to wait
51+
for all the responses before returning.
52+
53+
Using search as an example: A search response is made up of
54+
zero or more search entries followed by a search result. If all
55+
is 0, search entries will be returned one at a time as they
56+
come in, via separate calls to result(). If all is 1, the
57+
search response will be returned in its entirety, i.e. after
58+
all entries and the final search result have been received. If
59+
all is 2, all search entries that have been received so far
60+
will be returned.
61+
62+
The method returns a list of messages or None if polling and no
63+
messages arrived yet.
64+
65+
The result() method will block for timeout seconds, or
66+
indefinitely if timeout is negative. A timeout of 0 will
67+
effect a poll. The timeout can be expressed as a floating-point
68+
value. If timeout is None the default in self.timeout is used.
69+
70+
If a timeout occurs, a TIMEOUT exception is raised, unless
71+
polling (timeout = 0), in which case None is returned.
72+
"""
73+
74+
if timeout is None:
75+
timeout = self.timeout
76+
77+
messages = self._ldap_call(self._l.result, msgid, all, timeout)
78+
79+
if messages is None:
80+
return None
81+
82+
results = []
83+
for msgid, msgtype, controls, data in messages:
84+
controls = DecodeControlTuples(controls, self.resp_ctrl_classes)
85+
86+
m = Response(msgid, msgtype, controls, **data)
87+
results.append(m)
88+
89+
return results

Lib/ldap/response.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""
2+
connection.py - wraps class _ldap.LDAPObject
3+
4+
See https://www.python-ldap.org/ for details.
5+
"""
6+
7+
from ldap.pkginfo import __version__, __author__, __license__
8+
9+
__all__ = [
10+
'Response',
11+
'Result',
12+
13+
'SearchEntry'
14+
'SearchReference',
15+
'SearchResult',
16+
17+
'IntermediateResponse',
18+
'ExtendedResult',
19+
20+
'BindResult',
21+
'ModifyResult',
22+
'AddResult',
23+
'DeleteResult',
24+
'ModRDNResult',
25+
'CompareResult',
26+
]
27+
28+
29+
import ldap
30+
from typing import Optional
31+
32+
33+
from ldap import LDAPError
34+
from ldap.controls import LDAPControl
35+
36+
37+
_SUCCESS_CODES = [
38+
ldap.SUCCESS,
39+
ldap.COMPARE_TRUE,
40+
ldap.COMPARE_FALSE,
41+
ldap.SASL_BIND_IN_PROGRESS,
42+
]
43+
44+
45+
class Response:
46+
msgid: int
47+
msgtype: int
48+
controls: list[LDAPControl]
49+
50+
__subclasses: dict[int, type] = {}
51+
52+
def __init_subclass__(cls):
53+
if not hasattr(cls, 'msgtype'):
54+
return
55+
c = __class__.__subclasses.setdefault(cls.msgtype, cls)
56+
assert issubclass(cls, c)
57+
58+
def __new__(cls, msgid, msgtype, controls=None, **kwargs):
59+
if cls is not __class__:
60+
return super().__new__(cls)
61+
62+
c = __class__.__subclasses.get(msgtype)
63+
if c:
64+
return c.__new__(c, msgid, msgtype, controls, **kwargs)
65+
66+
instance = super().__new__(cls)
67+
instance.msgid = msgid
68+
instance.msgtype = msgtype
69+
instance.controls = controls
70+
return instance
71+
72+
73+
class Result(Response):
74+
result: int
75+
matcheddn: str
76+
message: str
77+
referrals: Optional[list[str]]
78+
79+
def __new__(cls, msgid, msgtype, controls,
80+
result, matcheddn, message, referrals, **kwargs):
81+
instance = super().__new__(cls, msgid, msgtype, controls, **kwargs)
82+
83+
instance.result = result
84+
instance.matcheddn = matcheddn
85+
instance.message = message
86+
instance.referrals = referrals
87+
88+
return instance
89+
90+
def raise_for_result(self):
91+
if self.result in _SUCCESS_CODES:
92+
return
93+
raise LDAPError(self)
94+
95+
96+
class SearchEntry(Response):
97+
msgtype = ldap.RES_SEARCH_ENTRY
98+
99+
dn: str
100+
attrs: dict[str, Optional[list[bytes]]]
101+
102+
def __new__(cls, msgid, msgtype, controls, dn, attrs, **kwargs):
103+
instance = super().__new__(cls, msgid, msgtype, controls, **kwargs)
104+
105+
instance.dn = dn
106+
instance.attrs = attrs
107+
108+
return instance
109+
110+
111+
class SearchReference(Response):
112+
msgtype = ldap.RES_SEARCH_REFERENCE
113+
114+
referrals: list[str]
115+
116+
def __new__(cls, msgid, msgtype, controls, referrals, **kwargs):
117+
instance = super().__new__(cls, msgid, msgtype, controls, **kwargs)
118+
119+
instance.referrals = referrals
120+
121+
return instance
122+
123+
124+
class SearchResult(Result):
125+
msgtype = ldap.RES_SEARCH_RESULT
126+
127+
128+
class IntermediateResponse(Response):
129+
msgtype = ldap.RES_INTERMEDIATE
130+
131+
oid: Optional[str]
132+
value: Optional[bytes]
133+
134+
__subclasses: dict[str, type] = {}
135+
136+
def __new__(cls, msgid, msgtype, controls=None, **kwargs):
137+
# TODO: instantiate
138+
pass
139+
140+
141+
class BindResult(Result):
142+
msgtype = ldap.RES_BIND
143+
144+
servercreds: Optional[bytes]
145+
146+
147+
class ModifyResult(Result):
148+
msgtype = ldap.RES_MODIFY
149+
150+
151+
class AddResult(Result):
152+
msgtype = ldap.RES_ADD
153+
154+
155+
class DeleteResult(Result):
156+
msgtype = ldap.RES_DELETE
157+
158+
159+
class ModRDNResult(Result):
160+
msgtype = ldap.RES_MODRDN
161+
162+
163+
class CompareResult(Result):
164+
msgtype = ldap.RES_COMPARE
165+
166+
def __bool__(self):
167+
if self.result == ldap.COMPARE_FALSE:
168+
return False
169+
if self.result == ldap.COMPARE_TRUE:
170+
return True
171+
raise LDAPError(self)
172+
173+
174+
class ExtendedResult(Result):
175+
msgtype = ldap.RES_EXTENDED
176+
177+
oid: Optional[str]
178+
value: Optional[bytes]
179+
# TODO: how to subclass these dynamically? (UnsolicitedResponse, ...),
180+
# is it just with __new__?

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