0
# Abstract Layer
1
2
High-level ORM-like interface with object definitions, attribute definitions, entry objects, and reader classes for simplified LDAP programming.
3
4
## Capabilities
5
6
### Object Definition
7
8
Define LDAP object schemas with attribute definitions for type-safe and validated entry manipulation.
9
10
```python { .api }
11
class ObjectDef:
12
def __init__(self, object_class=None):
13
"""
14
Define LDAP object schema.
15
16
Args:
17
object_class (str or list, optional): LDAP object class name(s)
18
"""
19
20
def add(self, definition):
21
"""
22
Add attribute definition to object.
23
24
Args:
25
definition (AttrDef): Attribute definition object
26
"""
27
28
def remove(self, item):
29
"""
30
Remove attribute definition.
31
32
Args:
33
item (str or AttrDef): Attribute name or definition to remove
34
"""
35
36
def clear(self):
37
"""Clear all attribute definitions."""
38
39
def __iadd__(self, other):
40
"""Add attribute definition using += operator."""
41
42
def __isub__(self, other):
43
"""Remove attribute definition using -= operator."""
44
45
def __contains__(self, item):
46
"""Check if attribute definition exists."""
47
48
def __getitem__(self, item):
49
"""Get attribute definition by name."""
50
51
def __len__(self):
52
"""Get number of attribute definitions."""
53
54
def __iter__(self):
55
"""Iterate over attribute definitions."""
56
```
57
58
### Attribute Definition
59
60
Define individual LDAP attributes with validation, transformation, and dereferencing rules.
61
62
```python { .api }
63
class AttrDef:
64
def __init__(self, name, key=None, validate=None, pre_query=None, post_query=None,
65
default=NotImplemented, dereference_dn=None, description=None):
66
"""
67
Define LDAP attribute with validation and transformation.
68
69
Args:
70
name (str): LDAP attribute name
71
key (str, optional): Friendly name for Python access
72
validate (callable, optional): Validation function for values
73
pre_query (callable, optional): Transform values before LDAP query
74
post_query (callable, optional): Transform values after LDAP response
75
default: Default value if attribute is missing
76
dereference_dn (str, optional): DN dereferencing mode
77
description (str, optional): Attribute description
78
"""
79
```
80
81
**Properties**:
82
- `name`: LDAP attribute name
83
- `key`: Python-friendly name
84
- `validate`: Validation function
85
- `pre_query`: Pre-query transformation
86
- `post_query`: Post-query transformation
87
- `default`: Default value
88
- `dereference_dn`: DN dereferencing mode
89
- `description`: Human-readable description
90
91
### Attribute Value Container
92
93
Container for LDAP attribute values with automatic type conversion and validation.
94
95
```python { .api }
96
class Attribute:
97
def __init__(self, attr_def, entry):
98
"""
99
Create attribute value container.
100
101
Args:
102
attr_def (AttrDef): Attribute definition
103
entry (Entry): Parent entry object
104
"""
105
106
@property
107
def value(self):
108
"""
109
Get attribute value(s).
110
111
Returns:
112
Single value for single-valued attributes, list for multi-valued
113
"""
114
115
def __len__(self):
116
"""Get number of values."""
117
118
def __iter__(self):
119
"""Iterate over values."""
120
121
def __getitem__(self, item):
122
"""Get value by index."""
123
124
def __eq__(self, other):
125
"""Compare attribute values."""
126
```
127
128
### Entry Object
129
130
High-level representation of LDAP entries with attribute access and manipulation methods.
131
132
```python { .api }
133
class Entry:
134
def __init__(self, dn, reader):
135
"""
136
Create entry object.
137
138
Args:
139
dn (str): Distinguished Name of entry
140
reader (Reader): Reader object that retrieved this entry
141
"""
142
143
def entry_get_dn(self):
144
"""
145
Get entry distinguished name.
146
147
Returns:
148
str: Entry DN
149
"""
150
151
def entry_get_response(self):
152
"""
153
Get raw LDAP response for entry.
154
155
Returns:
156
dict: Raw LDAP response
157
"""
158
159
def entry_get_reader(self):
160
"""
161
Get reader object that retrieved this entry.
162
163
Returns:
164
Reader: Reader object
165
"""
166
167
def entry_get_raw_attributes(self):
168
"""
169
Get raw attribute values from LDAP response.
170
171
Returns:
172
dict: Raw attributes
173
"""
174
175
def entry_get_raw_attribute(self, name):
176
"""
177
Get raw values for specific attribute.
178
179
Args:
180
name (str): Attribute name
181
182
Returns:
183
list: Raw attribute values
184
"""
185
186
def entry_get_attribute_names(self):
187
"""
188
Get list of available attribute names.
189
190
Returns:
191
list: Attribute names
192
"""
193
194
def entry_get_attributes_dict(self):
195
"""
196
Get attributes as dictionary.
197
198
Returns:
199
dict: Attribute name to value mapping
200
"""
201
202
def entry_refresh_from_reader(self):
203
"""Refresh entry data from reader."""
204
205
def entry_to_json(self):
206
"""
207
Convert entry to JSON format.
208
209
Returns:
210
str: Entry in JSON format
211
"""
212
213
def entry_to_ldif(self):
214
"""
215
Convert entry to LDIF format.
216
217
Returns:
218
str: Entry in LDIF format
219
"""
220
221
def __iter__(self):
222
"""Iterate over attribute names."""
223
224
def __contains__(self, item):
225
"""Check if attribute exists in entry."""
226
227
def __getitem__(self, item):
228
"""Access attribute by name."""
229
230
def __eq__(self, other):
231
"""Compare entries."""
232
233
def __lt__(self, other):
234
"""Compare entries for sorting."""
235
236
def entry_get_changes_dict(self):
237
"""
238
Get dictionary of pending changes for entry.
239
240
Returns:
241
dict: Dictionary of changes by attribute name
242
"""
243
244
def entry_commit_changes(self):
245
"""
246
Commit pending changes to LDAP server.
247
248
Returns:
249
bool: True if changes committed successfully
250
"""
251
252
def entry_discard_changes(self):
253
"""Discard pending changes without committing."""
254
255
def entry_delete(self):
256
"""
257
Delete entry from LDAP server.
258
259
Returns:
260
bool: True if entry deleted successfully
261
"""
262
263
def entry_refresh(self):
264
"""
265
Refresh entry data from LDAP server.
266
267
Returns:
268
bool: True if entry refreshed successfully
269
"""
270
271
def entry_move(self, destination_dn):
272
"""
273
Move entry to new DN location.
274
275
Args:
276
destination_dn (str): Destination DN for entry
277
278
Returns:
279
bool: True if entry moved successfully
280
"""
281
282
def entry_rename(self, new_name):
283
"""
284
Rename entry with new relative DN.
285
286
Args:
287
new_name (str): New relative DN
288
289
Returns:
290
bool: True if entry renamed successfully
291
"""
292
```
293
294
**Dynamic Attribute Access**: Attributes can be accessed as properties:
295
```python
296
entry.cn # Access 'cn' attribute
297
entry.mail # Access 'mail' attribute
298
entry.memberOf # Access 'memberOf' attribute
299
```
300
301
### Reader Class
302
303
High-level search interface with object-oriented query building and result processing.
304
305
```python { .api }
306
class Reader:
307
def __init__(self, connection, object_def, query, base, components_in_and=True,
308
sub_tree=True, get_operational_attributes=False, controls=None):
309
"""
310
Create reader for high-level LDAP searches.
311
312
Args:
313
connection (Connection): LDAP connection object
314
object_def (ObjectDef): Object definition for entries
315
query (str): Search query string
316
base (str): Search base DN
317
components_in_and (bool): Combine query components with AND logic
318
sub_tree (bool): Use subtree search scope
319
get_operational_attributes (bool): Include operational attributes
320
controls (list, optional): LDAP controls for search
321
"""
322
323
def search(self):
324
"""
325
Execute search and populate entries.
326
327
Returns:
328
bool: True if search successful
329
"""
330
331
def search_level(self):
332
"""
333
Execute single-level search.
334
335
Returns:
336
bool: True if search successful
337
"""
338
339
def search_subtree(self):
340
"""
341
Execute subtree search.
342
343
Returns:
344
bool: True if search successful
345
"""
346
347
def search_object(self, entry_dn):
348
"""
349
Search for specific entry by DN.
350
351
Args:
352
entry_dn (str): Distinguished Name to search for
353
354
Returns:
355
bool: True if search successful
356
"""
357
358
def search_size_limit(self, size_limit):
359
"""
360
Execute search with size limit.
361
362
Args:
363
size_limit (int): Maximum entries to return
364
365
Returns:
366
bool: True if search successful
367
"""
368
369
def search_time_limit(self, time_limit):
370
"""
371
Execute search with time limit.
372
373
Args:
374
time_limit (int): Search time limit in seconds
375
376
Returns:
377
bool: True if search successful
378
"""
379
380
def search_types_only(self):
381
"""
382
Execute search returning attribute types only.
383
384
Returns:
385
bool: True if search successful
386
"""
387
388
def search_paged(self, paged_size, paged_criticality=False):
389
"""
390
Execute paged search.
391
392
Args:
393
paged_size (int): Page size
394
paged_criticality (bool): Paged search criticality
395
396
Returns:
397
bool: True if search successful
398
"""
399
400
def clear(self):
401
"""Clear search results."""
402
403
def reset(self):
404
"""Reset reader state."""
405
406
def __iter__(self):
407
"""Iterate over found entries."""
408
409
def __getitem__(self, item):
410
"""Get entry by index."""
411
412
def __len__(self):
413
"""Get number of entries found."""
414
```
415
416
**Properties**:
417
- `definition`: Object definition used
418
- `query`: Current search query
419
- `components_in_and`: Query component logic
420
- `entries`: List of found Entry objects
421
422
### Operational Attribute
423
424
Specialized attribute container for LDAP operational attributes.
425
426
```python { .api }
427
class OperationalAttribute(Attribute):
428
"""
429
Operational attribute container (inherits from Attribute).
430
431
Handles LDAP operational attributes like createTimestamp,
432
modifyTimestamp, entryUUID, etc.
433
"""
434
```
435
436
## Usage Examples
437
438
### Object Definition and Attribute Definition
439
440
```python
441
import ldap3
442
443
# Define object schema
444
person = ldap3.ObjectDef('inetOrgPerson')
445
446
# Add attribute definitions with validation
447
person += ldap3.AttrDef('cn', key='name')
448
person += ldap3.AttrDef('mail', key='email', validate=lambda x: '@' in x)
449
person += ldap3.AttrDef('telephoneNumber', key='phone')
450
person += ldap3.AttrDef('employeeNumber', key='emp_id',
451
validate=lambda x: x.isdigit())
452
453
# Add attribute with default value
454
person += ldap3.AttrDef('department', default='IT')
455
```
456
457
### Reader-based Searching
458
459
```python
460
# Create connection and reader
461
server = ldap3.Server('ldap://ldap.example.com')
462
conn = ldap3.Connection(server, 'cn=user,dc=example,dc=com', 'password', auto_bind=True)
463
464
# Create reader with object definition
465
reader = ldap3.Reader(conn, person, 'name: John*', 'ou=people,dc=example,dc=com')
466
467
# Perform search
468
reader.search()
469
470
# Access results through entry objects
471
for entry in reader:
472
print(f"Name: {entry.name}") # Using key from AttrDef
473
print(f"Email: {entry.email}") # Validated email attribute
474
print(f"Phone: {entry.phone}")
475
print(f"Employee ID: {entry.emp_id}") # Validated numeric
476
print(f"Department: {entry.department}") # May use default value
477
print(f"DN: {entry.entry_get_dn()}")
478
print("---")
479
```
480
481
### Advanced Query Building
482
483
```python
484
# Complex queries using Reader
485
reader = ldap3.Reader(conn, person,
486
'name: John* & email: *@company.com | department: Engineering',
487
'dc=example,dc=com')
488
489
# Execute subtree search
490
reader.search_subtree()
491
492
# Access entry details
493
for entry in reader:
494
# Get all attributes as dictionary
495
attrs = entry.entry_get_attributes_dict()
496
print(f"All attributes: {attrs}")
497
498
# Convert to different formats
499
json_data = entry.entry_to_json()
500
ldif_data = entry.entry_to_ldif()
501
```
502
503
### Working with Multi-valued Attributes
504
505
```python
506
# Define group object with member attribute
507
group = ldap3.ObjectDef('groupOfNames')
508
group += ldap3.AttrDef('cn', key='name')
509
group += ldap3.AttrDef('member', key='members')
510
511
reader = ldap3.Reader(conn, group, 'name: *Admin*', 'ou=groups,dc=example,dc=com')
512
reader.search()
513
514
for group_entry in reader:
515
print(f"Group: {group_entry.name}")
516
517
# Multi-valued attribute access
518
if hasattr(group_entry, 'members'):
519
print(f"Number of members: {len(group_entry.members)}")
520
for member in group_entry.members:
521
print(f" Member: {member}")
522
```
523
524
### Custom Validation and Transformation
525
526
```python
527
import re
528
from datetime import datetime
529
530
def validate_email(email):
531
"""Email validation function."""
532
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
533
return re.match(pattern, email) is not None
534
535
def format_phone(phone):
536
"""Phone number formatting function."""
537
# Remove non-digits
538
digits = ''.join(filter(str.isdigit, phone))
539
if len(digits) == 10:
540
return f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
541
return phone
542
543
def parse_timestamp(timestamp):
544
"""Parse LDAP timestamp to datetime object."""
545
if isinstance(timestamp, list):
546
timestamp = timestamp[0]
547
return datetime.strptime(timestamp, '%Y%m%d%H%M%SZ')
548
549
# Create object definition with custom functions
550
person = ldap3.ObjectDef('inetOrgPerson')
551
person += ldap3.AttrDef('cn', key='name')
552
person += ldap3.AttrDef('mail', key='email', validate=validate_email)
553
person += ldap3.AttrDef('telephoneNumber', key='phone', post_query=format_phone)
554
person += ldap3.AttrDef('createTimestamp', key='created', post_query=parse_timestamp)
555
556
reader = ldap3.Reader(conn, person, 'name: *', 'ou=people,dc=example,dc=com',
557
get_operational_attributes=True)
558
reader.search()
559
560
for entry in reader:
561
print(f"Name: {entry.name}")
562
print(f"Email: {entry.email}") # Validated
563
print(f"Phone: {entry.phone}") # Formatted
564
print(f"Created: {entry.created}") # Parsed datetime
565
```
566
567
### Pagination with Reader
568
569
```python
570
# Reader-based paged search
571
reader = ldap3.Reader(conn, person, 'name: *', 'dc=example,dc=com')
572
573
# Search with pagination
574
page_size = 100
575
reader.search_paged(page_size)
576
577
print(f"Found {len(reader)} entries in first page")
578
579
# Continue with more pages if needed
580
while True:
581
# Check if more results available
582
if not conn.result.get('controls', {}).get('1.2.840.113556.1.4.319', {}).get('value', {}).get('cookie'):
583
break
584
585
reader.search_paged(page_size)
586
print(f"Found {len(reader)} additional entries")
587
```
588
589
### DN Dereferencing
590
591
```python
592
# Dereference DN attributes to get referenced entry data
593
person = ldap3.ObjectDef('inetOrgPerson')
594
person += ldap3.AttrDef('cn', key='name')
595
person += ldap3.AttrDef('manager', key='manager_info', dereference_dn='cn')
596
597
reader = ldap3.Reader(conn, person, 'name: *', 'ou=people,dc=example,dc=com')
598
reader.search()
599
600
for entry in reader:
601
print(f"Employee: {entry.name}")
602
if hasattr(entry, 'manager_info'):
603
print(f"Manager: {entry.manager_info}") # Dereferenced manager CN
604
```
605
606
### Error Handling in Abstract Layer
607
608
```python
609
try:
610
# Create object definition
611
person = ldap3.ObjectDef('inetOrgPerson')
612
person += ldap3.AttrDef('mail', validate=lambda x: '@' in x)
613
614
reader = ldap3.Reader(conn, person, 'mail: *', 'dc=example,dc=com')
615
reader.search()
616
617
for entry in reader:
618
# Access validated attributes
619
print(f"Valid email: {entry.mail}")
620
621
except ldap3.LDAPAttributeError as e:
622
print(f"Attribute error: {e}")
623
except ldap3.LDAPEntryError as e:
624
print(f"Entry error: {e}")
625
except ldap3.LDAPReaderError as e:
626
print(f"Reader error: {e}")
627
```
628
629
### Combining Abstract Layer with Raw Operations
630
631
```python
632
# Use abstract layer for searching, raw operations for modifications
633
reader = ldap3.Reader(conn, person, 'name: John*', 'ou=people,dc=example,dc=com')
634
reader.search()
635
636
for entry in reader:
637
# Get DN from abstract entry
638
entry_dn = entry.entry_get_dn()
639
640
# Use raw connection for modifications
641
changes = {'description': [(ldap3.MODIFY_REPLACE, f'Updated by script on {datetime.now()}')]}
642
conn.modify(entry_dn, changes)
643
644
print(f"Updated entry: {entry.name}")
645
```