A strictly RFC 4510 conforming LDAP V3 pure Python client library
81
All standard LDAP operations including search, add, modify, delete, compare, and abandon operations with full control support and multiple response formats.
Perform LDAP search operations with various scopes, filters, and result processing options.
def search(self, search_base, search_filter, search_scope=SUBTREE,
dereference_aliases=DEREF_ALWAYS, attributes=None, size_limit=0,
time_limit=0, types_only=False, get_operational_attributes=False,
controls=None, paged_size=None, paged_criticality=False,
paged_cookie=None):
"""
Perform LDAP search operation.
Args:
search_base (str): Base DN for search
search_filter (str): LDAP search filter (RFC 4515)
search_scope (str): Search scope (BASE, LEVEL, SUBTREE)
dereference_aliases (str): Alias dereferencing behavior (NEVER, SEARCH, BASE, ALWAYS)
attributes (list, optional): Attributes to retrieve (None for all)
size_limit (int): Maximum number of entries to return (0 for no limit)
time_limit (int): Search time limit in seconds (0 for no limit)
types_only (bool): Return attribute types only, not values
get_operational_attributes (bool): Include operational attributes
controls (list, optional): LDAP controls for search
paged_size (int, optional): Page size for paged search
paged_criticality (bool): Paged search criticality
paged_cookie (bytes, optional): Paged search cookie
Returns:
bool: True if search successful, False otherwise
"""Search Result Access:
connection.entries: List of Entry objects (abstract layer)connection.response: Raw LDAP responseconnection.result: Operation result informationAdd new entries to the LDAP directory with specified object classes and attributes.
def add(self, dn, object_class=None, attributes=None, controls=None):
"""
Add new entry to LDAP directory.
Args:
dn (str): Distinguished Name of new entry
object_class (str or list, optional): Object class(es) for entry
attributes (dict, optional): Entry attributes as {name: value} or {name: [values]}
controls (list, optional): LDAP controls for add operation
Returns:
bool: True if add successful, False otherwise
"""Modify existing LDAP entries by adding, deleting, replacing, or incrementing attribute values.
def modify(self, dn, changes, controls=None):
"""
Modify existing LDAP entry.
Args:
dn (str): Distinguished Name of entry to modify
changes (dict): Modifications as {attribute: [(operation, [values])]}
Operation: MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE, MODIFY_INCREMENT
controls (list, optional): LDAP controls for modify operation
Returns:
bool: True if modify successful, False otherwise
"""Delete entries from the LDAP directory.
def delete(self, dn, controls=None):
"""
Delete entry from LDAP directory.
Args:
dn (str): Distinguished Name of entry to delete
controls (list, optional): LDAP controls for delete operation
Returns:
bool: True if delete successful, False otherwise
"""Rename or move LDAP entries by modifying their distinguished names.
def modify_dn(self, dn, relative_dn, delete_old_dn=True, new_superior=None, controls=None):
"""
Modify distinguished name of entry (rename/move).
Args:
dn (str): Current distinguished name
relative_dn (str): New relative distinguished name
delete_old_dn (bool): Delete old RDN attribute values
new_superior (str, optional): New parent DN for move operation
controls (list, optional): LDAP controls for modify DN operation
Returns:
bool: True if modify DN successful, False otherwise
"""Compare attribute values in LDAP entries without retrieving the actual values.
def compare(self, dn, attribute, value, controls=None):
"""
Compare attribute value in LDAP entry.
Args:
dn (str): Distinguished Name of entry
attribute (str): Attribute name to compare
value (str or bytes): Value to compare against
controls (list, optional): LDAP controls for compare operation
Returns:
bool: True if values match, False if different or error
"""Abandon ongoing LDAP operations using their message IDs.
def abandon(self, message_id, controls=None):
"""
Abandon ongoing LDAP operation.
Args:
message_id (int): Message ID of operation to abandon
controls (list, optional): LDAP controls for abandon operation
Returns:
bool: True if abandon request sent successfully
"""Perform LDAP extended operations with custom request names and values.
def extended(self, request_name, request_value=None, controls=None):
"""
Perform LDAP extended operation.
Args:
request_name (str): Extended operation request name (OID)
request_value (bytes, optional): Extended operation request value
controls (list, optional): LDAP controls for extended operation
Returns:
bool: True if extended operation successful
"""Access and process LDAP operation responses in various formats.
def response_to_ldif(self):
"""
Convert last response to LDIF format.
Returns:
str: Response in LDIF format
"""
def response_to_json(self):
"""
Convert last response to JSON format.
Returns:
str: Response in JSON format
"""
def response_to_file(self, target, raw=False):
"""
Save response to file.
Args:
target (str): Target file path
raw (bool): Save raw response vs formatted
"""import ldap3
server = ldap3.Server('ldap://ldap.example.com')
conn = ldap3.Connection(server, 'cn=user,dc=example,dc=com', 'password', auto_bind=True)
# Search for all people
conn.search('dc=example,dc=com', '(objectClass=person)', attributes=['cn', 'mail', 'telephoneNumber'])
for entry in conn.entries:
print(f"Name: {entry.cn}")
print(f"Email: {entry.mail}")
print(f"Phone: {entry.telephoneNumber}")
print("---")# Complex search filter
filter_str = '(&(objectClass=person)(|(cn=John*)(mail=*@example.com)))'
conn.search('ou=people,dc=example,dc=com', filter_str,
search_scope=ldap3.LEVEL,
attributes=['cn', 'mail', 'description'],
size_limit=100)
# Access raw response
for response_item in conn.response:
if response_item['type'] == 'searchResEntry':
dn = response_item['dn']
attributes = response_item['attributes']
print(f"DN: {dn}")
print(f"Attributes: {attributes}")# Paged search to handle large result sets
search_base = 'dc=example,dc=com'
search_filter = '(objectClass=person)'
search_paged_size = 100
# Perform first page
conn.search(search_base, search_filter, paged_size=search_paged_size)
total_entries = len(conn.entries)
# Continue with additional pages
while True:
cookie = conn.result['controls']['1.2.840.113556.1.4.319']['value']['cookie']
if not cookie:
break
conn.search(search_base, search_filter, paged_size=search_paged_size, paged_cookie=cookie)
total_entries += len(conn.entries)
print(f"Total entries found: {total_entries}")# Add new person entry
new_dn = 'cn=John Doe,ou=people,dc=example,dc=com'
attributes = {
'cn': 'John Doe',
'sn': 'Doe',
'givenName': 'John',
'mail': 'john.doe@example.com',
'telephoneNumber': '+1-555-1234',
'userPassword': 'secretpassword'
}
result = conn.add(new_dn, object_class=['inetOrgPerson', 'person'], attributes=attributes)
if result:
print("Entry added successfully")
else:
print(f"Failed to add entry: {conn.result}")# Modify existing entry
target_dn = 'cn=John Doe,ou=people,dc=example,dc=com'
# Different types of modifications
changes = {
'mail': [(ldap3.MODIFY_REPLACE, 'newemail@example.com')], # Replace email
'telephoneNumber': [(ldap3.MODIFY_ADD, '+1-555-5678')], # Add phone number
'description': [(ldap3.MODIFY_DELETE, [])], # Delete all descriptions
'loginCount': [(ldap3.MODIFY_INCREMENT, 1)] # Increment counter
}
result = conn.modify(target_dn, changes)
if result:
print("Entry modified successfully")
else:
print(f"Failed to modify entry: {conn.result}")# Multiple attribute modifications in single operation
changes = {
'mail': [(ldap3.MODIFY_REPLACE, ['primary@example.com', 'secondary@example.com'])],
'telephoneNumber': [(ldap3.MODIFY_DELETE, '+1-555-1234'), # Delete specific phone
(ldap3.MODIFY_ADD, ['+1-555-9999', '+1-555-8888'])], # Add new phones
'title': [(ldap3.MODIFY_REPLACE, 'Senior Developer')]
}
conn.modify('cn=John Doe,ou=people,dc=example,dc=com', changes)# Rename entry (change CN)
old_dn = 'cn=John Doe,ou=people,dc=example,dc=com'
new_rdn = 'cn=John Smith'
conn.modify_dn(old_dn, new_rdn, delete_old_dn=True)
# Move entry to different OU
current_dn = 'cn=John Smith,ou=people,dc=example,dc=com'
new_superior = 'ou=employees,dc=example,dc=com'
conn.modify_dn(current_dn, 'cn=John Smith', new_superior=new_superior)# Delete single entry
entry_dn = 'cn=John Smith,ou=employees,dc=example,dc=com'
result = conn.delete(entry_dn)
if result:
print("Entry deleted successfully")
else:
print(f"Failed to delete entry: {conn.result}")# Compare attribute value without retrieving it
entry_dn = 'cn=admin,dc=example,dc=com'
result = conn.compare(entry_dn, 'userPassword', 'secretpassword')
if result:
print("Password matches")
elif conn.result['result'] == 5: # RESULT_COMPARE_FALSE
print("Password does not match")
else:
print(f"Compare operation failed: {conn.result}")# Search with server-side sorting control
from ldap3.protocol.rfc2696 import paged_search_control
from ldap3.protocol.rfc2891 import sort_control
# Server-side sort by cn attribute
sort_ctrl = sort_control([('cn', True)]) # True = ascending
conn.search('dc=example,dc=com', '(objectClass=person)',
controls=[sort_ctrl], attributes=['cn', 'mail'])
# Process sorted results
for entry in conn.entries:
print(f"{entry.cn}: {entry.mail}")# Asynchronous connection for non-blocking operations
server = ldap3.Server('ldap://ldap.example.com')
conn = ldap3.Connection(server, 'cn=user,dc=example,dc=com', 'password',
client_strategy=ldap3.ASYNC, auto_bind=True)
# Start search operation (returns immediately)
message_id = conn.search('dc=example,dc=com', '(objectClass=person)')
# Do other work while search is running
import time
time.sleep(1)
# Get results when ready
result, response = conn.get_response(message_id)
if result:
for item in response:
if item['type'] == 'searchResEntry':
print(f"Found: {item['dn']}")# JSON response format
conn.search('dc=example,dc=com', '(objectClass=person)', attributes=['cn', 'mail'])
json_response = conn.response_to_json()
print(json_response)
# LDIF response format
ldif_response = conn.response_to_ldif()
print(ldif_response)
# Save to file
conn.response_to_file('/tmp/search_results.ldif')Install with Tessl CLI
npx tessl i tessl/pypi-ldap3evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10