web2ldap  1.7.7
About: web2ldap is a full-featured web-based LDAPv3 client.
  Fossies Dox: web2ldap-1.7.7.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

openldap.py
Go to the documentation of this file.
1# -*- coding: ascii -*-
2"""
3web2ldap plugin classes for OpenLDAP
4"""
5
6import re
7import binascii
8from typing import Dict
9
10from pyasn1.codec.ber import decoder as ber_decoder
11
12import ldap0.ldapurl
13import ldap0.controls
14import ldap0.openldap
15from ldap0.controls import KNOWN_RESPONSE_CONTROLS
16
17import web2ldapcnf
18
19from ..searchform import SEARCH_OPT_IS_EQUAL, SEARCH_SCOPE_STR_ONELEVEL
20from ..schema.syntaxes import (
21 AuthzDN,
22 BindDN,
23 DirectoryString,
24 DistinguishedName,
25 DynamicDNSelectList,
26 IA5String,
27 Integer,
28 LDAPUrl,
29 LDAPv3ResultCode,
30 MultilineText,
31 NotBefore,
32 OctetString,
33 SchemaDescription,
34 SelectList,
35 Uri,
36 UUID,
37 syntax_registry,
38)
39from ...ldaputil.oidreg import OID_REG
40from .quirks import NamingContexts
41
42#---------------------------------------------------------------------------
43# Schema information
44#---------------------------------------------------------------------------
45
47 oid: str = 'OlcSchemaDescription-oid'
48
49 def _validate(self, attr_value: bytes) -> bool:
50 try:
51 # strip X-ORDERED number
52 schema_desc = attr_value.split(b'}', 1)[1]
53 except IndexError:
54 schema_desc = attr_value
55 return SchemaDescription._validate(self, schema_desc)
56
57
59 oid: str = 'OlcObjectClasses-oid'
60 schema_cls = ldap0.schema.models.ObjectClass
61
62syntax_registry.reg_at(
63 OlcObjectClasses.oid, [
64 '1.3.6.1.4.1.4203.1.12.2.3.0.32', # olcObjectClasses
65 ]
66)
67
68
70 oid: str = 'OlcAttributeTypes-oid'
71 schema_cls = ldap0.schema.models.AttributeType
72
73syntax_registry.reg_at(
74 OlcAttributeTypes.oid, [
75 '1.3.6.1.4.1.4203.1.12.2.3.0.4', # olcAttributeTypes
76 ]
77)
78
79
81 oid: str = 'OlcLdapSyntaxes-oid'
82 schema_cls = ldap0.schema.models.LDAPSyntax
83
84syntax_registry.reg_at(
85 OlcLdapSyntaxes.oid, [
86 '1.3.6.1.4.1.4203.1.12.2.3.0.85', # olcLdapSyntaxes
87 ]
88)
89
90
92 oid: str = 'OlcDitContentRules-oid'
93 schema_cls = ldap0.schema.models.DITContentRule
94
95syntax_registry.reg_at(
96 OlcDitContentRules.oid, [
97 '1.3.6.1.4.1.4203.1.12.2.3.0.16', # olcDitContentRules
98 ]
99)
100
101#---------------------------------------------------------------------------
102# slapo-syncprov
103#---------------------------------------------------------------------------
104
105# see https://www.openldap.org/faq/data/cache/1145.html
107 oid: str = '1.3.6.1.4.1.4203.666.11.2.4'
108 desc: str = 'change sequence number SID (CSN SID)'
109 min_len: int = 3
110 max_len: int = 3
111 pattern = re.compile('^[a-fA-F0-9]{3}$')
112
113
114# see https://www.openldap.org/faq/data/cache/1145.html
116 oid: str = '1.3.6.1.4.1.4203.666.11.2.1'
117 desc: str = 'change sequence number (CSN)'
118 min_len: int = 40
119 max_len: int = 40
120 pattern = re.compile('^[0-9]{14}\\.[0-9]{6}Z#[a-fA-F0-9]{6}#[a-fA-F0-9]{3}#[a-fA-F0-9]{6}$')
121
122syntax_registry.reg_at(
123 CSN.oid, [
124 '1.3.6.1.4.1.4203.666.1.25', # contextCSN
125 '1.3.6.1.4.1.4203.666.1.7', # entryCSN
126 '1.3.6.1.4.1.4203.666.1.13', # namingCSN
127 # also register by name in case OpenLDAP was built without -DSLAP_SCHEMA_EXPOSE
128 'contextCSN', 'entryCSN', 'namingCSN',
129 ]
130)
131
132#---------------------------------------------------------------------------
133# back-config
134#---------------------------------------------------------------------------
135
136syntax_registry.reg_at(
137 NamingContexts.oid, [
138 '1.3.6.1.4.1.4203.1.12.2.3.2.0.10', # olcSuffix
139 ]
140)
141
142
144 oid: str = 'OlcDbIndex-oid'
145 desc: str = 'OpenLDAP indexing directive'
146 pattern = re.compile("^[a-zA-Z]?[a-zA-Z0-9.,;-]* (pres|eq|sub)(,(pres|eq|sub))*$")
147
148syntax_registry.reg_at(
149 OlcDbIndex.oid, [
150 '1.3.6.1.4.1.4203.1.12.2.3.2.0.2', # olcDbIndex
151 ]
152)
153
154
156 oid: str = 'OlcSubordinate-oid'
157 desc: str = 'Indicates whether backend is subordinate'
158 attr_value_dict: Dict[str, str] = {
159 '': '-/- (FALSE)',
160 'TRUE': 'TRUE',
161 'advertise': 'advertise',
162 }
163
164syntax_registry.reg_at(
165 OlcSubordinate.oid, [
166 '1.3.6.1.4.1.4203.1.12.2.3.2.0.15', # olcSubordinate
167 ]
168)
169
170
172 oid: str = 'OlcRootDN-oid'
173 desc: str = 'The rootdn in the database'
174 default_rdn = 'cn=admin'
175
176 def form_value(self) -> str:
177 fval = BindDN.form_value(self)
178 try:
179 olc_suffix = self._entry['olcSuffix'][0].decode()
180 except KeyError:
181 pass
182 else:
183 if not fval or not fval.endswith(olc_suffix):
184 try:
185 fval = ','.join((self.default_rdn, olc_suffix))
186 except KeyError:
187 pass
188 return fval
189
190syntax_registry.reg_at(
191 OlcRootDN.oid, [
192 '1.3.6.1.4.1.4203.1.12.2.3.2.0.8', # olcRootDN
193 ]
194)
195
196
198 oid: str = 'OlcMultilineText-oid'
199 desc: str = 'OpenLDAP multiline configuration strings'
200 cols = 90
201 min_input_rows = 3
202
203 def display(self, vidx, links) -> str:
204 return '<code>%s</code>' % MultilineText.display(self, vidx, links)
205
206syntax_registry.reg_at(
207 OlcMultilineText.oid, [
208 '1.3.6.1.4.1.4203.1.12.2.3.0.1', # olcAccess
209 '1.3.6.1.4.1.4203.1.12.2.3.0.6', # olcAuthIDRewrite
210 '1.3.6.1.4.1.4203.1.12.2.3.0.8', # olcAuthzRegexp
211 '1.3.6.1.4.1.4203.1.12.2.3.2.0.5', # olcLimits
212 ]
213)
214
216 oid: str = 'OlcSyncRepl-oid'
217 desc: str = 'OpenLDAP syncrepl directive'
218 min_input_rows = 5
219
220 def __init__(self, app, dn: str, schema, attrType: str, attr_value: bytes, entry=None):
221 OlcMultilineText.__init__(self, app, dn, schema, attrType, attr_value, entry)
222
223 def display(self, vidx, links) -> str:
224 if not links or not self._av:
225 return OlcMultilineText.display(self, vidx, links)
226 srd = ldap0.openldap.SyncReplDesc(self.av_u)
227 return ' '.join((
228 OlcMultilineText.display(self, vidx, links),
229 self._app.ldap_url_anchor(srd.ldap_url()),
230 ))
231
232syntax_registry.reg_at(
233 OlcSyncRepl.oid, [
234 '1.3.6.1.4.1.4203.1.12.2.3.2.0.11', # olcSyncrepl
235 ]
236)
237
238
240 oid: str = 'OlmSeeAlso-oid'
241 desc: str = 'DN of a overlase or database object in back-monitor'
242 ldap_url = (
243 'ldap:///_?monitoredInfo?sub?'
244 '(&'
245 '(objectClass=monitoredObject)'
246 '(|'
247 '(entryDN:dnOneLevelMatch:=cn=Databases,cn=Monitor)'
248 '(entryDN:dnOneLevelMatch:=cn=Overlays,cn=Monitor)'
249 '(entryDN:dnOneLevelMatch:=cn=Backends,cn=Monitor)'
250 ')'
251 ')'
252 )
253
254syntax_registry.reg_at(
255 OlmSeeAlso.oid, [
256 '2.5.4.34', # seeAlso
257 ],
258 structural_oc_oids=['1.3.6.1.4.1.4203.666.3.16.8'], # monitoredObject
259)
260
261
263 oid: str = 'OlcPPolicyDefault-oid'
264 desc: str = 'DN of a pwdPolicy object for uncustomized objects'
265
266syntax_registry.reg_at(
267 OlcPPolicyDefault.oid, [
268 '1.3.6.1.4.1.4203.1.12.2.3.3.12.1', # olcPPolicyDefault
269 ]
270)
271
272
274 oid: str = 'OlcMemberOfDangling-oid'
275 desc: str = 'Behavior in case of dangling references during modification'
276 attr_value_dict: Dict[str, str] = {
277 '': '-/-',
278 'ignore': 'ignore',
279 'drop': 'drop',
280 'error': 'error',
281 }
282
283syntax_registry.reg_at(
284 OlcMemberOfDangling.oid, [
285 '1.3.6.1.4.1.4203.1.12.2.3.3.18.1', # olcMemberOfDangling
286 ]
287)
288
289
290#---------------------------------------------------------------------------
291# slapo-accesslog
292#---------------------------------------------------------------------------
293
294
295syntax_registry.reg_at(
296 NotBefore.oid, [
297 '1.3.6.1.4.1.4203.666.11.5.1.2', 'reqStart',
298 '1.3.6.1.4.1.4203.666.11.5.1.3', 'reqEnd',
299 ]
300)
301
302
304 oid: str = 'AuditContext'
305 desc: str = 'OpenLDAP DN pointing to audit naming context'
306
307 def display(self, vidx, links) -> str:
308 res = [DistinguishedName.display(self, vidx, links)]
309 if links:
310 res.extend([
311 self._app.anchor(
312 'searchform', 'Search',
313 [
314 ('dn', self.av_u),
315 ('scope', str(ldap0.SCOPE_ONELEVEL)),
316 ],
317 title='Go to search form for audit log',
318 ),
319 self._app.anchor(
320 'search', 'List all',
321 [
322 ('dn', self.av_u),
323 ('filterstr', '(objectClass=auditObject)'),
324 ('scope', str(ldap0.SCOPE_ONELEVEL)),
325 ],
326 title='List audit log entries of all operations',
327 ),
328 self._app.anchor(
329 'search', 'List writes',
330 [
331 ('dn', self.av_u),
332 ('filterstr', '(objectClass=auditWriteObject)'),
333 ('scope', str(ldap0.SCOPE_ONELEVEL)),
334 ],
335 title='List audit log entries of all write operations',
336 ),
337 ])
338 return web2ldapcnf.command_link_separator.join(res)
339
340syntax_registry.reg_at(
341 AuditContext.oid,
342 [
343 '1.3.6.1.4.1.4203.666.11.5.1.30', 'auditContext',
344 '1.3.6.1.4.1.4203.1.12.2.3.3.4.1', # olcAccessLogDB
345 ]
346)
347
348
350 oid: str = 'ReqResult-oid'
351
352syntax_registry.reg_at(
353 ReqResult.oid, [
354 '1.3.6.1.4.1.4203.666.11.5.1.7', 'reqResult', # reqResult
355 ]
356)
357
358
360 oid: str = 'ReqMod-oid'
361 desc: str = 'List of modifications/old values'
362 known_modtypes = {b'+', b'-', b'=', b'#', b''}
363
364 def _validate(self, attr_value: bytes) -> bool:
365 return OctetString._validate(self, attr_value)
366
367 def display(self, vidx, links) -> str:
368 if self._av_av == b':':
369 # magic value used for fixing OpenLDAP ITS#6545
370 return ':'
371 try:
372 mod_attr_type, mod_attr_rest = self._av_av.split(b':', 1)
373 mod_type = mod_attr_rest[0:1].strip()
374 except (ValueError, IndexError):
375 return OctetString.display(self, vidx, links)
376 if not mod_type in self.known_modtypes:
377 return OctetString.display(self, vidx, links)
378 if len(mod_attr_rest) > 1:
379 try:
380 mod_type, mod_attr_value = mod_attr_rest.split(b' ', 1)
381 except ValueError:
382 return OctetString.display(self, vidx, links)
383 else:
384 mod_attr_value = b''
385 mod_attr_type_u = mod_attr_type.decode(self._app.ls.charset)
386 mod_type_u = mod_type.decode(self._app.ls.charset)
387 try:
388 mod_attr_value.decode(self._app.ls.charset)
389 except UnicodeDecodeError:
390 return '%s:%s<br>\n<code>\n%s\n</code>\n' % (
391 self._app.form.s2d(mod_attr_type_u),
392 self._app.form.s2d(mod_type_u),
393 mod_attr_value.hex().upper(),
394 )
395 else:
396 return DirectoryString.display(self, vidx, links)
397 raise ValueError
398
399syntax_registry.reg_at(
400 ReqMod.oid, [
401 '1.3.6.1.4.1.4203.666.11.5.1.16', 'reqMod',
402 '1.3.6.1.4.1.4203.666.11.5.1.17', 'reqOld',
403 ]
404)
405
406
408 oid: str = '1.3.6.1.4.1.4203.666.11.5.3.1'
409 desc: str = 'List of LDAPv3 extended controls sent along with a request'
410
411 def display(self, vidx, links) -> str:
412 result_lines = [IA5String.display(self, vidx, links)]
413 # Eliminate X-ORDERED prefix
414 _, rest = self.av_u.strip().split('}{', 1)
415 # check whether it ends with }
416 if rest.endswith('}'):
417 result_lines.append('Extracted:')
418 # consume } and split tokens
419 ctrl_tokens = list(filter(
420 None,
421 [t.strip() for t in rest[:-1].split(' ')]
422 ))
423 ctrl_type = ctrl_tokens[0]
424 try:
425 ctrl_name, _, _ = OID_REG[ctrl_type]
426 except (KeyError, ValueError):
427 try:
428 ctrl_name = KNOWN_RESPONSE_CONTROLS.get(ctrl_type).__class__.__name__
429 except KeyError:
430 ctrl_name = None
431 if ctrl_name:
432 result_lines.append(self._app.form.s2d(ctrl_name))
433 # Extract criticality
434 try:
435 ctrl_criticality = {
436 'TRUE': True,
437 'FALSE': False,
438 }[ctrl_tokens[ctrl_tokens.index('criticality')+1].upper()]
439 except (KeyError, ValueError, IndexError):
440 ctrl_criticality = False
441 result_lines.append('criticality %s' % str(ctrl_criticality).upper())
442 # Extract controlValue
443 try:
444 ctrl_value = binascii.unhexlify(
445 ctrl_tokens[ctrl_tokens.index('controlValue')+1].upper()[1:-1]
446 )
447 except (KeyError, ValueError, IndexError):
448 pass
449 else:
450 try:
451 decoded_control_value = ber_decoder.decode(ctrl_value)
452 except Exception:
453 decoded_control_value = ctrl_value
454 result_lines.append(
455 'controlValue %s' % (
456 self._app.form.s2d(
457 repr(decoded_control_value)
458 ).replace('\n', '<br>')
459 )
460 )
461 return '<br>'.join(result_lines)
462
463syntax_registry.reg_at(
464 ReqControls.oid, [
465 '1.3.6.1.4.1.4203.666.11.5.1.10', 'reqControls',
466 '1.3.6.1.4.1.4203.666.11.5.1.11', 'reqRespControls',
467 ]
468)
469
470
472 oid: str = 'ReqEntryUUID-oid'
473
474 def display(self, vidx, links) -> str:
475 display_value = UUID.display(self, vidx, links)
476 if not links:
477 return display_value
478 return web2ldapcnf.command_link_separator.join((
479 display_value,
480 self._app.anchor(
481 'search', 'Search target',
482 (
483 ('dn', self._dn),
484 (
485 'filterstr',
486 '(entryUUID=%s)' % (self.av_u),
487 ),
488 (
489 'search_root',
490 str(
491 self._app.ls.get_search_root(
492 self._app.ls.uc_decode(self._entry['reqDN'][0])[0]
493 )
494 ),
495 ),
496 ),
497 title='Search entry by UUID',
498 )
499 ))
500
501syntax_registry.reg_at(
502 ReqEntryUUID.oid, [
503 '1.3.6.1.4.1.4203.666.11.5.1.31', 'reqEntryUUID', # reqEntryUUID
504 ]
505)
506
507
509 oid: str = 'ReqSession-oid'
510
511 def display(self, vidx, links) -> str:
512 display_value = Integer.display(self, vidx, links)
513 if not links:
514 return display_value
515 return web2ldapcnf.command_link_separator.join((
516 display_value,
517 self._app.anchor(
518 'search', '&raquo;',
519 (
520 ('dn', self._dn),
521 ('search_root', str(self._app.naming_context)),
522 ('searchform_mode', 'adv'),
523 ('search_attr', 'reqSession'),
524 ('search_option', SEARCH_OPT_IS_EQUAL),
525 ('search_string', self.av_u),
526 ),
527 title='Search all audit entries with same session number',
528 )
529 ))
530
531syntax_registry.reg_at(
532 ReqSession.oid, [
533 '1.3.6.1.4.1.4203.666.11.5.1.5', 'reqSession', # reqSession
534 ]
535)
536
537
539 oid: str = 'ReqDN-oid'
540 desc: str = 'Target DN of request'
541 ref_attrs = (
542 ('reqDN', 'Same target', None, 'Search all entries with same target DN'),
543 )
544
545
546syntax_registry.reg_at(
547 ReqDN.oid, [
548 '1.3.6.1.4.1.4203.666.11.5.1.1', 'reqDN', # reqDN
549 ]
550)
551
552
554 oid: str = 'ReqAuthzID-oid'
555 desc: str = 'Authorization DN'
556 ref_attrs = (
557 ('reqAuthzID', 'Same authz-DN', None, 'Search all entries with same authz DN'),
558 )
559
560
561syntax_registry.reg_at(
562 ReqAuthzID.oid, [
563 'reqAuthzID',
564 '1.3.6.1.4.1.4203.666.11.5.1.6', # reqAuthzID
565 ]
566)
567
568
569#---------------------------------------------------------------------------
570# General
571#---------------------------------------------------------------------------
572
573
575 oid: str = '1.3.6.1.4.1.4203.666.2.7'
576 desc: str = 'OpenLDAP authz'
577
578
579syntax_registry.reg_at(
580 AuthzDN.oid, [
581 'monitorConnectionAuthzDN',
582 '1.3.6.1.4.1.4203.666.1.55.7', # monitorConnectionAuthzDN
583 ]
584)
585
586
588 oid: str = '1.3.6.1.4.1.4203.666.2.1'
589 desc: str = 'OpenLDAP ACI'
590
591
593 oid: str = 'OpenLDAPSpecialBackendSuffix-oid'
594 desc: str = 'OpenLDAP special backend suffix'
595
596 def _config_link(self):
597 attr_type_u = self._at[:-7]
598 try:
599 config_context = self._app.ls.uc_decode(self._app.ls.root_dse['configContext'][0])[0]
600 except KeyError:
601 return None
602 return self._app.anchor(
603 'search', 'Config',
604 (
605 ('dn', config_context),
606 ('scope', SEARCH_SCOPE_STR_ONELEVEL),
607 (
608 'filterstr',
609 '(&(objectClass=olcDatabaseConfig)(olcDatabase=%s))' % (attr_type_u),
610 ),
611 ),
612 title='Search for configuration entry below %s' % (config_context),
613 )
614
615syntax_registry.reg_at(
616 OpenLDAPSpecialBackendSuffix.oid,
617 [
618 'monitorContext', '1.3.6.1.4.1.4203.666.1.10',
619 'configContext', '1.3.6.1.4.1.4203.1.12.2.1',
620 ]
621)
622
623
624syntax_registry.reg_at(
625 Uri.oid, ['monitorConnectionListener']
626)
627
628
629syntax_registry.reg_at(
630 DistinguishedName.oid, [
631 'entryDN',
632 ]
633)
634
635# Register all syntax classes in this module
636syntax_registry.reg_syntaxes(__name__)
bool _validate(self, bytes attr_value)
Definition: openldap.py:49
def __init__(self, app, str dn, schema, str attrType, bytes attr_value, entry=None)
Definition: openldap.py:220
bool _validate(self, bytes attr_value)
Definition: openldap.py:364
str display(self, vidx, links)
Definition: openldap.py:367
str display(self, vidx, links)
Definition: openldap.py:511