Using LDAP Controls¶
LDAP controls are optional extensions that modify the behavior of standard operations. They are sent alongside requests to the server and can influence how the operation is processed or how results are returned.
Controls can be used to request additional information, enforce certain server-side policies, or enable features such as pagination and sorting.
This section demonstrates how to:
Send controls with search and modification requests.
Interpret and handle response controls returned by the server.
Controls are a powerful way to adapt LDAP operations to specific application requirements without changing the protocol’s core commands.
Apply controls to all operations of the connection¶
Controls can be passed explicitly with each operation, or applied globally to all operations via the connection object, as shown below:
# set controls for every operation on the connection
from freeiam.ldap.controls import Controls, pre_read
dn = f'cn=user02,ou=users,{base_dn}'
ctrl = pre_read(['givenName'], criticality=True)
conn.set_controls(Controls([ctrl]))
result = conn.modify_ml(dn, [(Mod.Replace, 'givenName', b'Foo')])
preread = result.controls.get(ctrl)
assert not preread.entry.get('givenName')
result = conn.modify_ml(dn, [(Mod.Replace, 'givenName', b'Test')])
preread = result.controls.get(ctrl)
assert preread.entry['givenName'][0] == b'Foo'
# unset via empty list
conn.set_controls(Controls([]))
Simple Paged Results, Virtual List View, and Server-Side Sorting Controls¶
The SimplePagedResultsControl, VirtualListViewControl, and ServerSideSortingControl are integrated into the search(), search_paged(), and search_paginated() methods.
Refer to the search-examples for practical usage.
PreRead and PostRead Controls¶
from freeiam.ldap.controls import Controls, post_read
post_read_control = post_read(
[
'*', # every regular attribute
'+', # every operational attribute (metadata)
],
criticality=True,
)
controls = Controls([post_read_control])
# receive e.g. the entryUUID directly after creations
dn = f'uid=max.mustermann,{base_dn}'
attrs = {
'objectClass': [b'inetOrgPerson'],
'uid': [b'max.mustermann'],
'cn': [b'Max Mustermann'],
'givenName': [b'Max'],
'sn': [b'Mustermann'],
'mail': [b'max.mustermann@freeiam.org'],
}
result = await conn.add(dn, attrs, controls=controls)
print(result.dn, result.attrs)
entry = result.controls.get(post_read_control).entry
print(decode(entry['entryUUID']))
print(decode(entry['entryCSN']))
print(decode(entry['creatorsName']))
print(decode(entry['createTimestamp']))
print(decode(entry['modifyTimestamp']))
print(decode(entry['modifiersName']))
from freeiam.ldap.controls import Controls, pre_read
pre_read_control = pre_read(
[
'*', # every regular attribute
'+', # every operational attribute (metadata)
],
criticality=True,
)
controls = Controls([pre_read_control])
dn = f'uid=max.mustermann,{base_dn}'
result = await conn.modify(dn, ..., controls=controls)
entry = result.controls.get(pre_read_control).entry
# the attributes prior modification were:
print(decode(entry['sn']))
Assertion Control¶
from freeiam.ldap.controls import Controls, assertion
dn = f'cn=user01,ou=users,{base_dn}'
ctrl = assertion('(sn=Bar1)', criticality=True)
conn.modify_ml(dn, [(Mod.Replace, 'sn', b'Foo1')], controls=Controls([ctrl]))
ctrl = assertion('(sn=Bar1)', criticality=True)
try:
conn.modify_ml(dn, [(Mod.Replace, 'sn', b'Foo2')], controls=Controls([ctrl]))
except errors.AssertionFailed:
... # this assertion is not True
... # do something
RelaxRules Control¶
from freeiam.ldap.controls import Controls, relax_rules
ctrl = relax_rules(criticality=True)
custom_entry_uuid = b'00000000-0000-0000-0000-000000000000'
conn.modify_ml(
f'cn=user01,ou=users,{base_dn}',
[(Mod.Replace, 'entryUUID', custom_entry_uuid)],
controls=Controls([ctrl]),
)
assert conn.get_attr(f'cn=user01,ou=users,{base_dn}', 'entryUUID') == [
custom_entry_uuid
]
MatchedValues Control¶
from freeiam.ldap.controls import Controls, matched_values
ctrl = matched_values('(sn=Muster*)', criticality=True)
results = conn.search(
f'ou=users,{base_dn}',
Scope.Subtree,
'(objectClass=inetOrgPerson)',
controls=Controls([ctrl]),
)
print([obj.attr['sn'] for obj in results]) # contains only 'sn' attributes
PersistentSearch Control¶
from freeiam.ldap.controls import Controls, persistent_search
ctrl = persistent_search(
[LDAPChangeType.Modify],
changes_only=True,
return_entry_change_control=True,
criticality=True,
)
# this blocks until any of the entry is modified!
conn.search(
f'ou=users,{base_dn}',
Scope.Subtree,
'(objectClass=inetOrgPerson)',
controls=Controls([ctrl]),
)
SessionTracking Control¶
from freeiam.ldap.controls import Controls, session_tracking
ctrl = session_tracking(
'127.0.0.1', 'test-client', '1.3.6.1.4.1.1466.115.121.1.15', 'Max.Mustermann'
)
conn.search(base_dn, Scope.Base, '(objectClass=*)', controls=Controls([ctrl]))
GetEffectiveRights Control¶
from freeiam.ldap.controls import Controls, get_effective_rights
ctrl = get_effective_rights(conn.whoami(), criticality=True)
result = conn.get(f'cn=user02,ou=users,{base_dn}', controls=Controls([ctrl]))
print(result.attr['entryLevelRights'])
print(result.attr['attributeLevelRights'])
Dereference Control¶
from freeiam.ldap.controls import Controls, dereference
ctrl = dereference({'member': 'cn'}, criticality=True)
conn.search(
f'ou=groups,{base_dn}',
Scope.Subtree,
'(objectClass=groupOfNames)',
controls=Controls([ctrl]),
)
Manage DSA Information Tree Control¶
from freeiam.ldap.controls import Controls, manage_dsa_information_tree
ctrl = manage_dsa_information_tree(criticality=True)
conn.search(f'{base_dn}', Scope.Base, '(objectClass=*)', controls=Controls([ctrl]))