|
99 | 99 |
|
100 | 100 | # Typing imports
|
101 | 101 | from typing import (
|
| 102 | + Any, |
| 103 | + Dict, |
102 | 104 | List,
|
| 105 | + Union, |
103 | 106 | )
|
104 | 107 |
|
105 | 108 | # Elements of protocol
|
@@ -891,6 +894,29 @@ class LDAP_DelResponse(ASN1_Packet):
|
891 | 894 | )
|
892 | 895 |
|
893 | 896 |
|
| 897 | +# Modify DN Operation |
| 898 | +# https://datatracker.ietf.org/doc/html/rfc4511#section-4.9 |
| 899 | + |
| 900 | + |
| 901 | +class LDAP_ModifyDNRequest(ASN1_Packet): |
| 902 | + ASN1_codec = ASN1_Codecs.BER |
| 903 | + ASN1_root = ASN1F_SEQUENCE( |
| 904 | + LDAPDN("entry", ""), |
| 905 | + LDAPDN("newrdn", ""), |
| 906 | + ASN1F_BOOLEAN("deleteoldrdn", ASN1_BOOLEAN(False)), |
| 907 | + ASN1F_optional(LDAPDN("newSuperior", None, implicit_tag=0xA0)), |
| 908 | + implicit_tag=ASN1_Class_LDAP.ModifyDNRequest, |
| 909 | + ) |
| 910 | + |
| 911 | + |
| 912 | +class LDAP_ModifyDNResponse(ASN1_Packet): |
| 913 | + ASN1_codec = ASN1_Codecs.BER |
| 914 | + ASN1_root = ASN1F_SEQUENCE( |
| 915 | + *LDAPResult, |
| 916 | + implicit_tag=ASN1_Class_LDAP.ModifyDNResponse, |
| 917 | + ) |
| 918 | + |
| 919 | + |
894 | 920 | # Abandon Operation
|
895 | 921 | # https://datatracker.ietf.org/doc/html/rfc4511#section-4.11
|
896 | 922 |
|
@@ -1024,6 +1050,8 @@ class LDAP(ASN1_Packet):
|
1024 | 1050 | LDAP_AddResponse,
|
1025 | 1051 | LDAP_DelRequest,
|
1026 | 1052 | LDAP_DelResponse,
|
| 1053 | + LDAP_ModifyDNRequest, |
| 1054 | + LDAP_ModifyDNResponse, |
1027 | 1055 | LDAP_UnbindRequest,
|
1028 | 1056 | LDAP_ExtendedResponse,
|
1029 | 1057 | ),
|
@@ -2128,7 +2156,7 @@ def search(
|
2128 | 2156 | attrsOnly=0,
|
2129 | 2157 | attributes: List[str] = [],
|
2130 | 2158 | controls: List[LDAP_Control] = [],
|
2131 |
| - ): |
| 2159 | + ) -> Dict[str, List[Any]]: |
2132 | 2160 | """
|
2133 | 2161 | Perform a LDAP search.
|
2134 | 2162 |
|
@@ -2189,7 +2217,12 @@ def search(
|
2189 | 2217 | resp.show()
|
2190 | 2218 | raise TimeoutError("Search timed out.")
|
2191 | 2219 | # Now, reassemble the results
|
2192 |
| - _s = lambda x: x.decode(errors="backslashreplace") |
| 2220 | + |
| 2221 | + def _s(x): |
| 2222 | + try: |
| 2223 | + return x.decode() |
| 2224 | + except UnicodeDecodeError: |
| 2225 | + return x |
2193 | 2226 |
|
2194 | 2227 | def _ssafe(x):
|
2195 | 2228 | if self._TEXT_REG.match(x):
|
@@ -2272,6 +2305,94 @@ def modify(
|
2272 | 2305 | resp=resp,
|
2273 | 2306 | )
|
2274 | 2307 |
|
| 2308 | + def add( |
| 2309 | + self, |
| 2310 | + entry: str, |
| 2311 | + attributes: Union[Dict[str, List[Any]], List[ASN1_Packet]], |
| 2312 | + controls: List[LDAP_Control] = [], |
| 2313 | + ): |
| 2314 | + """ |
| 2315 | + Perform a LDAP add request. |
| 2316 | +
|
| 2317 | + :param attributes: the attributes to add. We support two formats: |
| 2318 | + - a list of LDAP_Attribute (or LDAP_PartialAttribute) |
| 2319 | + - a dict following {attribute: [list of values]} |
| 2320 | +
|
| 2321 | + :returns: |
| 2322 | + """ |
| 2323 | + # We handle the two cases in the type of attributes |
| 2324 | + if isinstance(attributes, dict): |
| 2325 | + attributes = [ |
| 2326 | + LDAP_Attribute( |
| 2327 | + type=ASN1_STRING(k), |
| 2328 | + values=[ |
| 2329 | + LDAP_AttributeValue( |
| 2330 | + value=ASN1_STRING(x), |
| 2331 | + ) |
| 2332 | + for x in v |
| 2333 | + ], |
| 2334 | + ) |
| 2335 | + for k, v in attributes.items() |
| 2336 | + ] |
| 2337 | + |
| 2338 | + resp = self.sr1( |
| 2339 | + LDAP_AddRequest( |
| 2340 | + entry=ASN1_STRING(entry), |
| 2341 | + attributes=attributes, |
| 2342 | + ), |
| 2343 | + controls=controls, |
| 2344 | + timeout=3, |
| 2345 | + ) |
| 2346 | + if LDAP_AddResponse not in resp.protocolOp or resp.protocolOp.resultCode != 0: |
| 2347 | + raise LDAP_Exception( |
| 2348 | + "LDAP add failed !", |
| 2349 | + resp=resp, |
| 2350 | + ) |
| 2351 | + |
| 2352 | + def modifydn( |
| 2353 | + self, |
| 2354 | + entry: str, |
| 2355 | + newdn: str, |
| 2356 | + deleteoldrdn=True, |
| 2357 | + controls: List[LDAP_Control] = [], |
| 2358 | + ): |
| 2359 | + """ |
| 2360 | + Perform a LDAP modify DN request. |
| 2361 | +
|
| 2362 | + ..note:: This functions calculates the relative DN and superior required for |
| 2363 | + LDAP ModifyDN automatically. |
| 2364 | +
|
| 2365 | + :param entry: the DN of the entry to rename. |
| 2366 | + :param newdn: the new FULL DN of the entry. |
| 2367 | + :returns: |
| 2368 | + """ |
| 2369 | + # RFC4511 sect 4.9 |
| 2370 | + # Calculate the newrdn (relative DN) and superior |
| 2371 | + newrdn, newSuperior = newdn.split(",", 1) |
| 2372 | + _, cur_superior = entry.split(",", 1) |
| 2373 | + # If the superior hasn't changed, don't update it. |
| 2374 | + if cur_superior == newSuperior: |
| 2375 | + newSuperior = None |
| 2376 | + # Send the request |
| 2377 | + resp = self.sr1( |
| 2378 | + LDAP_ModifyDNRequest( |
| 2379 | + entry=entry, |
| 2380 | + newrdn=newrdn, |
| 2381 | + newSuperior=newSuperior, |
| 2382 | + deleteoldrdn=deleteoldrdn, |
| 2383 | + ), |
| 2384 | + controls=controls, |
| 2385 | + timeout=3, |
| 2386 | + ) |
| 2387 | + if ( |
| 2388 | + LDAP_ModifyDNResponse not in resp.protocolOp |
| 2389 | + or resp.protocolOp.resultCode != 0 |
| 2390 | + ): |
| 2391 | + raise LDAP_Exception( |
| 2392 | + "LDAP modify failed !", |
| 2393 | + resp=resp, |
| 2394 | + ) |
| 2395 | + |
2275 | 2396 | def close(self):
|
2276 | 2397 | if self.verb:
|
2277 | 2398 | print("X Connection closed\n")
|
|
0 commit comments