Developer Interface
This part of the documentation covers all the classes and functions that make up python-ldap-faker.
Unittest Support
- class ldap_faker.LDAPFakerMixin(*args, **kwargs)[source]
A mixin for use with
unittest.TestCase. Properly configured, it will patchldap.initializeto use ourFakeLDAP.initializefake function instead, which will returnFakeLDAPObjectobjects instead ofldap.ldapobject.LDAPObjectobjects.ldap_modulesis a list of python module paths in which we should patchldap.initializewith ourFakeLDAP.initializemethod. For example:class TestMyStuff(LDAPFakerMixin, unittest.TestCase): ldap_modules = ['myapp.module']
will cause
LDAPFakerMixinto patchmyapp.module.ldap.initialize.ldap_fixturesnames one or more JSON files containing LDAP records to load into aObjectStoreviaObjectStore.load_objects.ldap_fixturescan be either a single string, aTuple[str, List[str]], or a list ofTuple[str, str, List[str]].If we define our test class like so:
class TestMyStuff(LDAPFakerMixin, unittest.TestCase): ldap_fixtures = 'myfixture.json'
We will build our
LDAPServerFactorywith a single defaultObjectStorewith the contents ofmyfixture.jsonloaded in.If we define our test class like so:
class TestMyStuff(LDAPFakerMixin, unittest.TestCase): ldap_fixtures = ('myfixture.json', ['389'])
We will build our
LDAPServerFactorywith a single defaultObjectStorewith the contents ofmyfixture.jsonloaded in, with the tag 389 applied to it.If we define our test class like this instead:
class TestMyStuff(LDAPFakerMixin, unittest.TestCase): ldap_fixtures = [ ('server1.json', 'ldap://server1', []), ('server2.json', 'ldap://read-server2', ['389']), ]
we will build our
LDAPServerFactorywith twoObjectStoreobjects. The first will have the data fromserver1.jsonand will be used with urildap://server1. The second will be used with urildap://server2and have the data from with the contents ofserver2.jsonloaded in, and will have the tag389applied to it.Note
The tags are used when configuring behavior for our
ObjectStore`. The389tag tells theObjectStoreto emulate a 389 type LDAP server (Redhat Directory Server).- server_factory: LDAPServerFactory
The
LDAPServerFactoryconfigured by oursetUpClass
- classmethod resolve_file(filename: str) str[source]
Given
filename, if that filename is a non-absolute path, resolve that filename to an absolute path under the folder in which our subclass’ file resides. Iffilenameis an absoute path, don’t change it.- Parameters
filename – the non-absolute file path to a fixture file
- Raises
FileNotFoundError – the fixture file did not exist
- Returns
The absolute path to the fixture file.
- classmethod load_servers(server_factory: LDAPServerFactory) None[source]
Configure
server_factorywith one or moreObjectStoreobjects by looking atldap_fixtures, a dict where the key is a uri and the value is the name of a JSON file to use as the objects for the associatedObjectStoreNote
If you want to populate your
LDAPServerFactoryin a different way than loading directly from the JSON files listed inldap_fixtures, this is the classmethod you want to override.- Parameters
server_factory – the
LDAPServerFactoryobject to populate
- classmethod setUpClass()[source]
Build the
LDAPServerFactorywe’ll use and save it as a class attribute.We do this as a classmethod because constructing our
ObjectStoreobjects is time consuming and we don’t want to have to do it for each of our tests.
- classmethod tearDownClass()[source]
Delete our
server_factoryso we con’t corrupt future tests or leak memory.
- setUp() None[source]
Create a
FakeLDAPinstance, make it use theserver_factorythat oursetUpClasscreated, andpatchldap.initializein each of the modules named inldap_modules. Save theFakeLDAPinstance to ourfake_ldapattribute for later use in our test code.
- last_connection() ldap_faker.faker.FakeLDAPObject | None[source]
Return the
FakeLDAPObjectfor the last connection made during ourtest. Hopefully a useful shortcut for when we only make one connection.- Returns
The last connection made
- get_connections(uri: str | None = None) list[ldap_faker.faker.FakeLDAPObject][source]
Return a the list of
FakeLDAPObjectobjects generated during our test, optionally filtered by LDAP URI.- Keyword Arguments
uri – the LDAP URI by which to filter our connections
- assertGlobalOptionSet(option: int, value: ldap_faker.types.LDAPOptionValue) None[source]
Assert that a global LDAP option was set.
- Parameters
option – an LDAP option (e.g. ldap.OPT_DEBUG_LEVEL)
value – the value we expect the option to be set to
- assertGlobalFunctionCalled(api_name: str) None[source]
Assert that a global LDAP function was called.
- Parameters
api_name – the name of the function to look for (e.g.
initialize)
- assertLDAPConnectionOptionSet(conn: FakeLDAPObject, option: str, value: ldap_faker.types.LDAPOptionValue) None[source]
Assert that a specific
FakeLDAPObjectoption was set with a specific value.- Parameters
conn – the connection object to examine
option – the code for the option (e.g.
ldap.OPT_X_TLS_NEWCTX)value – the value we expect the option to be set to
- assertLDAPConnectionMethodCalled(conn: FakeLDAPObject, api_name: str, arguments: dict[str, Any] | None = None) None[source]
Assert that a specific
FakeLDAPObjectmethod was called, possibly specifying the specific arguments it should have been called with.- Parameters
conn – the connection object to examine
api_name – the name of the function to look for (e.g.
simple_bind_s)
- Keyword Arguments
arguments – if given, assert that the call exists AND was called this set of arguments. See
LDAPCallRecordfor how theargumentsdict should be constructed.
- assertLDAPConnectionMethodCalledAfter(conn: FakeLDAPObject, api_name: str, target_api_name: str) None[source]
Assert that a specific
FakeLDAPObjectmethod was called after another specificFakeLDAPObjectmethod.- Parameters
conn – the connection object to examine
api_name – the name of the function to look for (e.g.
simple_bind_s)target_api_name – the name of the function which should appear before
api_namein the call history
- class ldap_faker.LDAPCallRecord(api_name: str, args: dict[str, Any])[source]
A single LDAP call record, used by
CallHistoryto store information about calls to LDAP api functions.api_nameis the name of the LDAP api call made (e.g.simple_bind_s,search_s).argsis the argument list of the call, including defaults for keyword arguments not passed. This is a dict where the key is the name of the positional or keyword argument, and the value is the passed in (or default) value for that argument.Example
If we make this call to a patched
FakeLDAPObject:ldap_obj.search_s('ou=bar,o=baz,c=country', ldap.SCOPE_SUBTREE, '(uid=foo)')
This will be recorded as:
LDAPCallRecord( api_name='search_s', args={ 'base': 'ou=bar,o=baz,c=country', 'scope': 2, 'filterstr': '(uid=foo)', 'attrlist': None, 'attrsonly': 0 } )
- class ldap_faker.CallHistory(calls: list[ldap_faker.db.LDAPCallRecord] | None = None)[source]
Records the
python-ldapcall history for a particularFakeLDAPObjectasLDAPCallRecordobjects. It works in conjunction with the@record_calldecorator. AnCallHistoryobject will be configured on eachFakeLDAPObjectand on eachFakeLDAPobject capture their call history.We use this in our tests with appropriate asserts to ensure that our code called the
python-ldapmethods we expected, in the order we expected, with the arguments we expected.- filter_calls(api_name: str) list[ldap_faker.db.LDAPCallRecord][source]
Filter our call history by function name.
- Parameters
api_name – look through our history for calls to this function
- Returns
A list of (
api_name,arguments) tuples in the order in which the calls were made. Arguments is aDict[str, Any].
- property calls: list[ldap_faker.db.LDAPCallRecord]
Returns the list of all calls made against the parent object.
Example
To test that your code did a
ldap.simple_bind_scall with the usernam and password you expected, you could do:from unittest import TestCase import ldap from ldap_faker import LDAPFakerMixin from my_code import App class MyTest(LDAPFakerMixin, TestCase): ldap_modules = ['my_code'] ldap_fixtures = 'myfixture.json' def test_option_was_set(self): app = MyApp() app.do_the_thing() conn = self.ldap_faker.connections[0] self.assertEqual( conn.calls, [('simple_bind_s', {'who': 'uid=foo,ou=dept,o=org,c=country', 'cred': 'pass'})] )
- Returns
Returns a list of 2-tuples, one for each method call made since the last reset. Each tuple contains the name of the API and a dictionary of arguments. Argument defaults are included.
- property names: list[str]
Returns the list names of
python-ldapfunctions or methods called, in the order they were called. You can use this to test whether an particularyExample
To test that your code did at least one
ldap.add_scall, you could do:from unittest import TestCase import ldap from ldap_faker import LDAPFakerMixin from my_code import App class MyTest(LDAPFakerMixin, TestCase): ldap_modules = ['my_code'] ldap_fixtures = 'myfixture.json' def test_option_was_set(self): app = MyApp() app.do_the_thing() conn = self.ldap_faker.connections[0] self.assertEqual('add_s" in conn.calls.names)
- Returns
A list of method names, in the order they were called.
python-ldap replacements
- class ldap_faker.FakeLDAP(server_factory: LDAPServerFactory)[source]
We use this class to house our replacement code for these three prime
python-ldapfunctions:The class takes a fully configured
LDAPServerFactoryas an argument, and will use that factory’s collection ofOptionStoreobjects to construct newFakeLDAPObjectobjects.As a test runs,
FakeLDAPkeeps track of each LDAP connection made and each global LDAP call made so that they can be inspected after your code has run.Note
This is meant to be a disposable object, recreated for each test method. When used properly, all internal state (connections made, calls made, options set) will be empty at the start of every test.
- Parameters
server_factory – a fully configured
LDAPServerFactory
- connections: list[ldap_faker.faker.FakeLDAPObject]
List of
FakeLDAPObjectconnections created in the order in which they were requested
- calls: CallHistory
Call history for global LDAP function calls
- options: OptionStore
A dictionary of LDAP options set
- stores: dict[str, ldap_faker.db.ObjectStore]
A dictionary of LDAP stores
- server_factory: LDAPServerFactory
The server factory used to create LDAP connections
- initialize(uri: str, trace_level: int = 0, trace_file: ~typing.TextIO = <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>, trace_stack_limit: int | None = None, fileno: ~typing.Any = None) FakeLDAPObject[source]
We use this to patch
ldap.initializewhen we are testing our LDAP code. When it is called, we will ask ourFakeLDAP.server_factoryfactory for theObjectStoremost appropriate for the LDAP URIuri, create aFakeLDAPObjectwith acopy.deepcopyof thatObjectStore, and return theFakeLDAPObject.Note
Of all the arguments in our signature, we only actually use
uri. The other arguments are recorded in ourFakeLDAP.callscall history, but are otherwise ignored.- Parameters
uri – an LDAP URI
trace_level – logging level (ignored)
trace_file – file descriptor to which to write traces (ignored)
trace_stack_limit – stack limit of tracebacks in the debug log (ignored)
fileno – a socket or file descriptor (ignored)
- Raises
ldap.SERVER_DOWN – could not find an appropriate
ObjectStoreforuri- Returns
A properly configured
FakeLDAPObject
- set_option(option: int, invalue: ldap_faker.types.LDAPOptionValue) None[source]
Set a global
python-ldap option. This will create a keyoptionin ourFakeLDAP.optionsdictionary and set its value tovalue.Example
In your test code, you can thus test whether your code set the proper global LDAP option like so:
from unittest import TestCase import ldap from ldap_faker import LDAPFakerMixin from my_code import App class MyTest(LDAPFakerMixin, TestCase): ldap_modules = ['my_code'] ldap_fixtures = 'myfixture.json' def test_option_was_set(self): app = MyApp() app.set_the_option(ldap.OPT_DEBUG_LEVEL, 1) self.assertEqual(self.ldap_faker.options[ldap.OPT_DEBUG_LEVEL], 1)
- Parameters
option – an option from python-ldap
invalue – the value to set for the option
- get_option(option: int) LDAPOptionValue | dict[str, int | str][source]
Get a global python-ldap option. If our code hasn’t set an
optionyet, return the default frompython-ldapfor that option.- Parameters
option – an option from python-ldap
- Returns
The value currently set for the option.
- has_connection(uri: str) bool[source]
Test to see whether an
ldap.initializecall was made with LDAP URI ofuri.- Parameters
uri – The LDAP URI to look for in our connection history
- Returns
Trueif at least one connection touriwas made,Falseotherwise.
- get_connections(uri: str) list[ldap_faker.faker.FakeLDAPObject][source]
Return a list of
FakeLDAPObjectconnections to LDAP URIuri.- Parameters
uri – The LDAP URI to look for in our connection history
- Returns
A list of
FakeLDAPObjectobjects associated with LDAP URIuri.
- connection_calls(api_name: str | None = None, uri: str | None = None) CallHistory[source]
Filter our the call history for our connections by function name and optionally LDAP URI.
- Keyword Arguments
api_name – restrict through our history for calls to this function
uri – restrict our search to only calls to this URI
- Returns
A
CallHistorywith combined calls from the filtered connections.
- class ldap_faker.FakeLDAPObject(uri: str, store: ldap_faker.db.ObjectStore | None = None)[source]
Simulates most of the interface of
ldap.ldapobject.LDAPObjectwhich is the object that gets returned when you callldap.initialize().Note
This is a disposable object that should be recreated for each test, mostly because changes to our
ObjectStorecan’t be undone without re-copying from its source inServers.- Parameters
uri – the LDAP URI of the connection
- Keyword Arguments
directory – a populated
ObjectStore
- options: OptionStore
we store data from
set_optioncalls here
- store: ObjectStore
our copy of our ObjectStore for this connection
- calls: CallHistory
The method call history
- set_option(option: int, invalue: ldap_faker.types.LDAPOptionValue) None[source]
Sets the value of the
ldap.ldap.ldapobject.LDAPObject`option specified byoptiontoinvalue.- Parameters
option – the option
invalue – the value to set the option to
- Raises
ValueError –
optionis not a validpython-ldapoption
- get_option(option: int) LDAPOptionValue | dict[str, int | str][source]
Returns the value of the
ldap.ldap.ldapobject.LDAPObject`option specified byoption.Note
If your code did not call
FakeLDAPOption.set_optionfor this option, we’ll getKeyError- Parameters
option – the option
- Raises
ValueError –
optionis not a validpython-ldapoptionKeyError –
optionis not a validpython-ldapoption
- Returns
The value of the option
- simple_bind_s(who: str | None = None, cred: str | None = None, serverctrls: list[ldap.controls.LDAPControl] | None = None, clientctrls: list[ldap.controls.LDAPControl] | None = None) Result3 | None[source]
Perform a bind. This will look in the object store for an object with dn of
whoand comparecredto theuserPasswordattribute for that object.- Keyword Arguments
who – the dn of the user with which to bind
cred – the password for that user
serverctrls – server controls (ignored)
clientctrls – client controls (ignored)
- Raises
ldap.INVALID_CREDENTIALS – the
whodid not match thecred
- whoami_s() str[source]
Implements the LDAP “Who Am I?” extended operation.
It is useful for finding out to find out which identity is assumed by the LDAP server after a bind.
- Returns
{the dn}”
- Return type
Empty string if we haven’t bound as an identity, otherwise “dn
- search_ext(base: str, scope: int, filterstr: str = '(objectClass=*)', attrlist: list[str] | None = None, attrsonly: int = 0, serverctrls: list[ldap.controls.LDAPControl] | None = None, clientctrls: list[ldap.controls.LDAPControl] | None = None, timeout: int = -1, sizelimit: int = 0) int[source]
Performs a search.
This method supports the following LDAP controls in the serverctrls list:
1.2.840.113556.1.4.319 (Paged Results): Sets a cookie on the control for paging
1.2.840.113556.1.4.473 (Server Side Sort): Sorts results by specified attributes
2.16.840.1.113730.3.4.9 (Virtual List View): Slices results based on target position and counts
Examples
Paged Search:
import ldap from ldap.controls import SimplePagedResultsControl # Create paged results control page_control = SimplePagedResultsControl(True, size=10, cookie='') # Perform paged search msgid = conn.search_ext( 'ou=users,dc=example,dc=com', ldap.SCOPE_SUBTREE, '(objectClass=person)', serverctrls=[page_control] ) # Get results rtype, rdata, rmsgid, rctrls = conn.result3(msgid) # Check for more pages for ctrl in rctrls: if ctrl.controlType == '1.2.840.113556.1.4.319': if ctrl.cookie: # More pages available page_control.cookie = ctrl.cookie # Continue with next page...
Server Side Sort:
import ldap from ldap.controls import LDAPControl # Create sort control (sort by cn, then by uid) # The controlValue should contain the sort keys in BER format # For this fake implementation, we'll use simple UTF-8 encoding sort_keys = ['cn', 'uid'] control_value = ','.join(sort_keys).encode('utf-8') encoded_control_value = ldap.encode_sort_control_value(control_value) sort_control = LDAPControl( '1.2.840.113556.1.4.473', True, encoded_control_value, ) # Perform sorted search msgid = conn.search_ext( 'ou=users,dc=example,dc=com', ldap.SCOPE_SUBTREE, '(objectClass=person)', serverctrls=[sort_control] ) # Get sorted results rtype, rdata, rmsgid, rctrls = conn.result3(msgid)
Paged Search with Server Side Sort:
import ldap from ldap.controls import SimplePagedResultsControl, LDAPControl # Create both controls page_control = SimplePagedResultsControl(True, size=5, cookie='') sort_keys = ['cn'] control_value = ','.join(sort_keys).encode('utf-8') encoded_control_value = ldap.encode_sort_control_value(control_value) sort_control = LDAPControl( '1.2.840.113556.1.4.473', True, encoded_control_value, ) # Perform paged and sorted search msgid = conn.search_ext( 'ou=users,dc=example,dc=com', ldap.SCOPE_SUBTREE, '(objectClass=person)', serverctrls=[page_control, sort_control] ) # Get results (sorted and paged) rtype, rdata, rmsgid, rctrls = conn.result3(msgid)
Virtual List View:
import ldap from ldap.controls import LDAPControl # Create VLV control (get 5 entries before and after position 10) # The controlValue should contain: beforeCount,afterCount,target vlv_value = "5,5,10".encode('utf-8') vlv_control = LDAPControl( '2.16.840.1.113730.3.4.9', True, vlv_value, ) # Perform VLV search msgid = conn.search_ext( 'ou=users,dc=example,dc=com', ldap.SCOPE_SUBTREE, '(objectClass=person)', serverctrls=[vlv_control] ) # Get results with VLV response control rtype, rdata, rmsgid, rctrls = conn.result3(msgid) # Check for VLV response control for ctrl in rctrls: if ctrl.controlType == '2.16.840.1.113730.3.4.10': # VLV response control contains target position and total count vlv_response = ctrl.controlValue.decode('utf-8') target_pos, total_count = vlv_response.split(',')[:2] print(f"Target position: {target_pos}, Total count: {total_count}")
- Keyword Arguments
base – the base DN for the search
scope – the scope of the search
filterstr – the filter to use for the search
attrlist – the list of attributes to return for each object
attrsonly – if 1, return only the attribute values; if 0, return the
object (entire) –
serverctrls – server controls (supports Paged Results, Server Side Sort, and Virtual List View)
clientctrls – client controls (ignored)
timeout – timeout for the search (ignored)
sizelimit – size limit for the search (applied after sorting)
- Returns
The message ID of the search
- result3(msgid: int = -1, all: int = 1, timeout: int | None = None) Result3[source]
Retrieve the results of our
FakeLDAPObject.search_extcall.Note
The
allandtimeoutkeyword arguments are ignored here.- Keyword Arguments
msgid – the
msgidreturned by theFakeLDAPObject.search_extcallall – if 1, return all results at once; if 0, return them one at a time (ignored)
timeout – timeout for the search (ignored)
- Returns
A
ldap.result34-tuple.
- search_s(base: str, scope: int, filterstr: str = '(objectClass=*)', attrlist: list[str] | None = None, attrsonly: int = 0) list[ldap_faker.types.LDAPRecord][source]
- start_tls_s() None[source]
Negotiate TLS with server.
This sets our
tls_enabledattribute toTrue.- Raises
ldap.LOCAL_ERROR –
start_tls_swas done twice on the same connection
- compare_s(dn: str, attr: str, value: bytes) bool[source]
Perform an LDAP comparison between the attribute named
attrof entrydn, and the valuevalue. For multi-valued attributes, the test is whether any of the values matchvalue.- Parameters
dn – the dn of the object to look at
attr – the name of the attribute on our object to compare
value – the value to which to compare the object value
- Raises
ldap.NO_SUCH_OBJECT – no object with dn of
dnexists in our object store- Returns
Trueif the values are equal,Falseotherwise.
- modify_s(dn, modlist: ModList) Result3[source]
Modify the object with dn of
dnusing the modlistmodlist.Each element in the list modlist should be a tuple of the form
(mod_op: int, mod_type: str, mod_vals: bytes | List[bytes]), wheremod_opindicates the operation (one ofldap.MOD_ADD,ldap.MOD_DELETE, orldap.MOD_REPLACE,mod_typeis a string indicating the attribute type name, andmod_valsis either a bytes value or a list of bytes values to add, delete or replace respectively. For the delete operation,mod_valsmay beNoneindicating that all attributes are to be deleted.Note
ldap.modlist.modifyModlistMAY be your friend here for generating modlists. Do read the note in those docs aboutldap.MOD_DELETE/ldap.MOD_ADDvs.ldap.MOD_REPLACEto see whether that will affect you poorly.Example
Here is an example of constructing a modlist for
modify_s:>>> import ldap >>> modlist = [ (ldap.MOD_ADD, 'mail', [b'user@example.com', b'user+foo@example.com']), (ldap.MOD_REPLACE, 'cn', [b'My Name']), (ldap.MOD_DELETE, 'gecos', None) ]
- Parameters
dn – the dn of the object to delete
modlist – a modlist suitable for
modify_s
- Raises
ldap.NO_SUCH_OBJECT – no object with dn of
dnexists in our object storeldap.TYPE_OR_VALUE_EXISTS – you tried to add an value to an attribute, but it was already in the value list
ldap.INSUFFICIENT_ACCESS – you need to do a non-anonymous bind before doing this
- Returns
A
ldap.result3type 4-tuple.
- delete_s(dn: str) None[source]
Delete the object with dn of
dnfrom our object store.Each element in the list modlist should be a tuple of the form
(mod_type: str, mod_vals: List[bytes]), wheremod_typeis a string indicating the attribute type name, andmod_valsis either a string value or a list of string values to add, delete or replace respectively. For the delete operation, mod_vals may beNoneindicating that all attributes are to be deleted.- Parameters
dn – the dn of the object to delete
- Raises
ldap.NO_SUCH_OBJECT – no object with dn of
dnexists in our object storeldap.INSUFFICIENT_ACCESS – you need to do a non-anonymous bind before doing this
- add_s(dn: str, modlist: ldap_faker.types.AddModList) None[source]
Add an object with dn of
dn.modlistis similar the one passed tomodify_s, except that the operation integer is omitted from the tuples inmodlist. You might want to look into sub-module refmodule{ldap.modlist} for generating the modlist.Example
Here is an example of constructing a modlist for
add_s:>>> modlist = [ ('uid', [b'user']), ('gidNumber', [b'1000']), ('uidNumber', [b'1000']), ('loginShell', [b'/bin/bash']), ('homeDirectory', [b'/home/user']), ('userPassword', [b'the password']), ('cn', [b'My Name']), ('objectClass', [b'top', b'posixAccount']), ]
- Parameters
dn – the dn of the object to add
modlist – the add modlist
- Raises
ldap.ALREADY_EXISTS – an object with dn of
dnalready exists in our object storeldap.INSUFFICIENT_ACCESS – you need to do a non-anonymous bind before doing this
- rename_s(dn: str, newrdn: str, newsuperior: str | None = None, delold: int = 1, serverctrls: list[ldap.controls.LDAPControl] | None = None, clientctrls: list[ldap.controls.LDAPControl] | None = None) None[source]
Take
dn(the DN of the entry whose RDN is to be changed, andnewrdn, the new RDN to give to the entry. The optional parameternewsuperioris used to specify a new parent DN for moving an entry in the tree (not all LDAP servers support this).- Parameters
dn – the dn of the object to rename
newrdn – the new RDN
- Keyword Arguments
newsuperior – the new basedn
delold – if 1, delete the old entry after renaming, if 0, don’t.
serverctrls – server controls (ignored)
clientctrls – client controls (ignored)
- Raises
ldap.NO_SUCH_OBJECT – no object with dn of
dnexists in our object storeldap.INSUFFICIENT_ACCESS – you need to do a non-anonymous bind before doing this
- modrdn_s(dn: str, newrdn: str, delold: int = 1, serverctrls: list[ldap.controls.LDAPControl] | None = None, clientctrls: list[ldap.controls.LDAPControl] | None = None) None[source]
Modify the RDN (Relative Distinguished Name) of an entry.
This method changes the RDN of the entry with DN
dntonewrdn. Unlikerename_s, this method does not support moving entries to a different parent DN.- Parameters
dn – the DN of the object whose RDN is to be changed
newrdn – the new RDN
- Keyword Arguments
delold – if 1, delete the old entry after renaming, if 0, don’t
serverctrls – server controls (ignored)
clientctrls – client controls (ignored)
- Raises
ldap.NO_SUCH_OBJECT – no object with DN of
dnexists in our object storeldap.INSUFFICIENT_ACCESS – you need to do a non-anonymous bind before doing this
LDAP Server like objects
- class ldap_faker.LDAPServerFactory[source]
Registers
ObjectStoreobjects to be used byFakeLDAP.initialize()in constructingFakeLDAPObjectobjects.ObjectStoreobjects are named registered here by LDAP uri (in reality, any string).You may do one of two things, but not both:
Configure a default
ObjectStorethat will be used for allldap.initializecalls regardless ofuriAssign a specific
ObjectStorefor eachuriyou will be using in your code.
Example
To register a default
ObjectStorethat will be used for everyuripassed toFakeLDAP.initialize:>>> from ldap_faker import ObjectStore, LDAPServerFactory, FakeLDAP >>> data = [ ... ] # some LDAP records >>> factory = LDAPServerFactory() >>> store = ObjectStore(objects=data) >>> factory.register(store) >>> fake_ldap = FakeLDAP(factory)
Now any time your code does an
ldap.initialize()to our patched version of that function, it will get a aFakeLDAPObjectconfigured with acopy.deepcopyof theObjectStorestore, no matter whaturiit passes toldap.initialize().To register a different
ObjectStoresthat will be used for specificuris:>>> from ldap_faker import ObjectStore, Servers >>> data1 = [ ... ] # some LDAP records >>> factory = LDAPServerFactory() >>> store1 = ObjectStore(objects=data1) >>> factory.register(store1, uri='ldap://server1') >>> data2 = [ ... ] # some different LDAP records >>> store2 = ObjectStore(objects=data2) >>> factory.register(store2, uri='ldap://server2') >>> fake_ldap = FakeLDAP(factory)
Now if your code does
ldap.initialize('ldap://server1'), it will get aFakeLDAPObjectconfigured with acopy.deepcopyof theObjectStoreobjectstore1, while if it doesldap.initialize('ldap://server2' ), it will get aFakeLDAPObjectconfigured with acopy.deepcopyof theObjectStoreobjectstore2.- load_from_file(filename: str, uri: str | None = None, tags: list[str] | None = None) None[source]
Given a file path to a JSON file with the objects for an
ObjectStore, create a newObjectStore, load it with that JSON File and register it with uri ofuri.- Parameters
filename – the full path to our JSON file
- Keyword Arguments
uri – the uri to assign to the
ObjectStorewe createtags – the list of tags to apply to the the
ObjectStore
- Raises
ValueError – raised if a default is already configured while trying to register the
ObjectStorewith a specificuriRuntimeWarning – raised if we try to overwrite an already registered object store with our new one
- register(store: ObjectStore, uri: str | None = None) None[source]
Register a new
ObjectStoreto be used as our fake LDAP server for when we run our fakeinitializefunction.- Parameters
store – a configured
ObjectStore- Keyword Arguments
uri – the LDAP uri to associated with
directory- Raises
ValueError – raised if a default is already configured while trying to register an
ObjectStorewith a specificuriRuntimeWarning – raised if we try to overwrite an already registered object store with a new one
- get(uri: str) ObjectStore[source]
Return a
copy.deepcopyof theObjectStoreidentified byuri.- Parameters
uri – use this uri to look up which
ObjectStoreto use- Raises
ldap.SERVER_DOWN – no
ObjectStorecould be found foruri- Returns
A
copy.deepcopyof theObjectStore
- class ldap_faker.ObjectStore(tags: list[str] | None = None)[source]
Represents our actual simulated LDAP object store. Copies of this will be used to configure
FakeLDAPObjectobjects.- raw_objects: RawLDAPObjectStore
LDAP records as they would have been returned by
python-ldap`
- objects: LDAPObjectStore
LDAP records set up to make searching better
- convert_LDAPData(data: ldap_faker.types.LDAPData) ldap_faker.types.CILDAPData[source]
Convert an incoming
LDAPData` dict (``Dict[str, List[bytes]]) to aCILDAPDatadict (CaseInsensitiveDict[str, List[str]]))We need the data dict to have values as
List[str]so that our filtering works properly –ldap_filter.Filter.matchonly works with strings, not bytes.- Parameters
data – the LDAPData dict to convert
- Returns
The convered CILDAPData dict.
- load_objects(filename: str) None[source]
Load a list of LDAP records stored as JSON from a file into our internal database. Use this when setting up the data you will use to run your tests.
Note
One caveat with this method vs.
ObjectStore.register_objectsis that the records returned bypython-ldapare of typetuple[str, dict[str, list[bytes]]]but JSON has no concept ofbytesortuple. Thus we will expect the LDAP records in the file to have typelist[str, dict[str, list[str]]]and we will convert them totuple[str, dict[str, list[bytes]]]before saving toraw_objects- Parameters
filename – the path to the JSON file to load
- Raises
ldap.ALREADY_EXISTS – there is already an object in our object store with this dn
ldap.INVALID_DN_SYNTAX – one of the object DNs is not well formed
- register_objects(objs: list[ldap_faker.types.LDAPRecord]) None[source]
Load a list of LDAP records into our internal database. Use this when setting up the data you will use to run your tests. Each record in the list should be in exactly the format that
python-ldapitself returns: a 2-tuple with dn as the first element and the attribute/value dict as the second element.Example
Adding a several PosixAccount objects:
>>> obj = [ ( 'uid=user,ou=mydept,o=myorg,c=country', { 'cn': [b'Firstname User1'], 'uid': [b'user'], 'uidNumber': [b'123'], 'gidNumber': [b'456'], 'homeDirectory': [b'/home/user'], 'loginShell': [b'/bin/bash'], 'userPassword': [b'the password'], 'objectclass': [b'posixAccount', b'top'] } ), ( 'uid=user2,ou=mydept,o=myorg,c=country', { 'cn': [b'Firstname User2'], 'uid': [b'user2'], 'uidNumber': [b'124'], 'gidNumber': [b'457'], 'homeDirectory': [b'/home/user1'], 'loginShell': [b'/bin/bash'], 'userPassword': [b'the password'], 'objectclass': [b'posixAccount', b'top'] } ) ] >>> directory = ObjectStore() >>> directory.register_objects(obj)
- Parameters
objs – A list of LDAP records as they would have been returned by
ldap.ldapobject.LDAPObject.search_s(). These are 2-tuples, where the first element is the dn (astr) and the second element is a dict where the keys arestrand the values are lists ofbytes.- Raises
ldap.ALREADY_EXISTS – there is already an object in our object store with this dn
ldap.INVALID_DN_SYNTAX – one of the object DNs is not well formed
TypeError – the LDAPData portion for an object was not of type
- register_object(obj: ldap_faker.types.LDAPRecord) None[source]
Add an LDAP record our internal database. Use this to add a single record when setting up the data you will use to run your tests. The data should be in exactly the format that python-ldap itself returns: a 2-tuple with dn as the first element and the attribute/value dict as the second element.
Example
Adding a PosixAccount object:
>>> obj = ( 'uid=user,ou=mydept,o=myorg,c=country', { 'cn': [b'Firstname Lastname'], 'uid': [b'user'], 'uidNumber': [b'123'], 'gidNumber': [b'456'], 'homeDirectory': [b'/home/user'], 'loginShell': [b'/bin/bash'], 'userPassword': [b'the password'] 'objectclass': [b'posixAccount', b'top'] } ) >>> directory = ObjectStore() >>> directory.register_object(obj)
- Parameters
obj – An LDAP record as it would have been returned by
ldap.ldapobject.LDAPObject.search_s(). This is a 2-tuple, where the first element is the dn (astr) and the second element is a dict where the keys arestrand the values are lists ofbytes.- Raises
ldap.ALREADY_EXISTS – there is already an object in our object store with this dn
ldap.INVALID_DN_SYNTAX – the DN is not well formed
TypeError – the LDAPData portion was not of type
dict[str, list[bytes]]
- property count
- exists(dn: str, validate: bool = True) bool[source]
Test whether an object with dn
dnexists.- Parameters
dn – the dn of the object to look for
- Keyword Arguments
validate – if
True, validate thatdnis a valid dn- Returns
Trueif the object exists,Falseotherwise.
- get(dn: str) ldap_faker.types.LDAPData[source]
Return all data for an object from our object store.
- Parameters
dn – the dn of the object to copy.
- Raises
ldap.NO_SUCH_OBJECT – no object with dn of
dnexists in our object store- Returns
The data for an LDAP object
- copy(dn: str) ldap_faker.types.LDAPData[source]
Return a copy of the data for an object from our object store.
- Parameters
dn – the dn of the object to copy.
- Raises
ldap.NO_SUCH_OBJECT – no object with dn of
dnexists in our object store- Returns
The data for an LDAP object
- set(dn: str, data: ldap_faker.types.LDAPData, bind_dn: str | None = None) None[source]
Add or update data for the object with dn
dn.- Parameters
dn – the dn of the object to copy.
data – the dict of data for this object
- Keyword Arguments
bind_dn – the dn of the user doing the set, if any
- Raises
ldap.INVALID_DN_SYNTAX – the DN is not well formed
TypeError – the LDAPData portion was not of type
Dict[str, List[bytes]]
- update(dn: str, modlist: ldap_faker.types.ModList, bind_dn: str | None = None) None[source]
Modify the object with dn of
dnusing the modlistmodlist.Each element in the list modlist should be a tuple of the form
(mod_op: int, mod_type: str, mod_vals: bytes | List[bytes]), wheremod_opindicates the operation (one ofldap.MOD_ADD,ldap.MOD_DELETE, orldap.MOD_REPLACE,mod_typeis a string indicating the attribute type name, andmod_valsis either a bytes value or a list of bytes values to add, delete or replace respectively. For the delete operation,mod_valsmay beNoneindicating that all attributes are to be deleted.Note
ldap.modlist.modifyModlistMAY be your friend here for generating modlists. Do read the note in those docs aboutldap.MOD_DELETE/ldap.MOD_ADDvs.ldap.MOD_REPLACEto see whether that will affect you poorly.Example
Here is an example of constructing a modlist for
modify_s:>>> import ldap >>> modlist = [ (ldap.MOD_ADD, 'mail', [b'user@example.com', b'user+foo@example.com']), (ldap.MOD_REPLACE, 'cn', [b'My Name']), (ldap.MOD_DELETE, 'gecos', None) ]
- Parameters
dn – the dn of the object to delete
modlist – a modlist suitable for
modify_s
- Keyword Arguments
bind_dn – the dn of the user doing the update, if any
- Raises
ldap.INVALID_DN_SYNTAX – the dn was not well-formed
ldap.NO_SUCH_OBJECT – no object with dn of
dnexists in our object storeldap.TYPE_OR_VALUE_EXISTS – you tried to add an value to an attribute, but it was already in the value list
ldap.INSUFFICIENT_ACCESS – you need to do a non-anonymous bind before doing this
- create(dn: str, modlist: ldap_faker.types.AddModList, bind_dn: str | None = None) None[source]
Create an object in our store with dn of
dn.modlistis similar the one passed tomodify_s, except that the operation integer is omitted from the tuples inmodlist. You might want to look into sub-module ldap.modlist for generating the modlist.Example
Here is an example of constructing a modlist for
create:>>> modlist = [ ('uid', [b'user']), ('gidNumber', [b'1000']), ('uidNumber', [b'1000']), ('loginShell', [b'/bin/bash']), ('homeDirectory', [b'/home/user']), ('userPassword', [b'the password']), ('cn', [b'My Name']), ('objectClass', [b'top', b'posixAccount']), ]
- Parameters
dn – the dn of the object to add
modlist – the add modlist
- Keyword Arguments
bind_dn – the dn of the user doing the create, if any
- Raises
ldap.INVALID_DN_SYNTAX – the dn was not well-formed
ldap.ALREADY_EXISTS – an object with dn of
dnalready exists in our object storeldap.INSUFFICIENT_ACCESS – you need to do a non-anonymous bind before doing this
- delete(dn: str, bind_dn: str | None = None) None[source]
Delete an object from our objects directory.
- Parameters
dn – the dn of the object to delete
- Keyword Arguments
bind_dn – the dn of the user doing the delete, if any
- Raises
ldap.INVALID_DN_SYNTAX – the dn was not well-formed
- search_base(base: str, filterstr: str, attrlist: list[str] | None = None) ldap_faker.types.LDAPSearchResult[source]
Do a
ldap.SCOPE_BASEsearch. Return the requested attributes of the object in our object store withdnofbasethat also matchesfilterstr.Note
We return a
copy.deepcopyof the object, not the actual object. This ensures that if the caller modifies the object they don’t update the objects in us unintentionally.Note
Some attributes are “operational” and are not returned by default They must be named specifically if you want them. Example:
>>> store.search_base( 'thebasedn', '(objectclass=*)', ['*', 'createTimestamp'])
- Parameters
base – the dn of the object to return
filterstr – the ldap filter string
- Keyword Arguments
attrlist – the list of attributes to return for each object
- Raises
ldap.INVALID_DN_SYNTAX –
basewas not a well-formed DNldap.FILTER_ERROR –
filterstris has bad filter syntaxldap.NO_SUCH_OBJECT – no object with dn of
baseexists in the object store
- Returns
A list with one element – the object with dn of
base.
- search_onelevel(base: str, filterstr: str, attrlist: list[str] | None = None) ldap_faker.types.LDAPSearchResult[source]
Do a
ldap.SCOPE_ONELEVELsearch, for objects directly under basednbasethat matchfilterstr.Note
We return a
copy.deepcopyof each object, not the actual object. This ensures that if the caller modifies the object they don’t update the objects in us unintentionally.- Parameters
base – the dn of the object to return
filterstr – the ldap filter string
- Keyword Arguments
attrlist – the list of attributes to return for each object
- Raises
ldap.INVALID_DN_SYNTAX –
basewas not a well-formed DNldap.FILTER_ERROR –
filterstris has bad filter syntax
- Returns
A list of LDAP objects – 2-tuples of
(dn, data).
- search_subtree(base: str, filterstr: str, attrlist: list[str] | None = None, include_operational_attributes: bool = False) ldap_faker.types.LDAPSearchResult[source]
Do a
ldap.SCOPE_SUBTREEsearch, for objects under basednbasethat matchfilterstr.- Parameters
base – the dn of the object to return
filterstr – the ldap filter string
Note
We return a
copy.deepcopyof each object, not the actual object. This ensures that if the caller modifies the object they don’t update the objects in us unintentionally.- Keyword Arguments
attrlist – the list of attributes to return for each object
include_operational_attributes – include all operational attributes even if they are not named in
attrlist
- Raises
ldap.INVALID_DN_SYNTAX –
basewas not a well-formed DNldap.FILTER_ERROR –
filterstris has bad filter syntax
- Returns
A list of LDAP objects – 2-tuples of
(dn, data).
- class ldap_faker.OptionStore[source]
We use this to store options set via
set_option.- set(option: int, invalue: ldap_faker.types.LDAPOptionValue) None[source]
Set an option.
- Parameters
option – the code for the option (e.g.
ldap.OPT_X_TLS_NEWCTX)invalue – the value we want the option to be set to
- Raises
ValueError –
optionis not a validpython-ldapoption
- get(option: int) LDAPOptionValue | dict[str, int | str][source]
Get the value for a previosly set option that was set via
OptionStore.set.- Parameters
option – the code for the option (e.g.
ldap.OPT_X_TLS_NEWCTX)- Raises
ValueError –
optionis not a validpython-ldapoption- Returns
The value for the option, or the default.
Hook management
- ldap_faker.hooks = <ldap_faker.hooks.HookRegistry object>
A registry for hooks.
- class ldap_faker.Hook(func: Callable[[...], Any], tags: list[str])[source]
A hook function.
- func
the hook function
- Type
collections.abc.Callable[[…], Any]
- class ldap_faker.HookDefinition(name: str, signature: str)[source]
The definition for a hook. This is comprised of a name and a signature.
Example
>>> hook_def = HookDefinition( name='pre_save", signature="Callable[[ObjectStore, LDAPRecord], None] ) >>> hook_def.name "pre_save" >>> hook_def.signature "Callable[[ObjectStore, LDAPRecord], None]"
- signature
the python type annotation signature that the hook should implement, e.g. “Callable[[ObjectStore, LDAPRecord], None]”
- Type
- class ldap_faker.HookRegistry[source]
A registry for hooks.
- property definitions: list[ldap_faker.hooks.HookDefinition]
Return a list of known hooks definitions as
- register_hook_definition(hook_name: str, signature: str) None[source]
Register a hook definition. Hook definitions define what hooks exist, and what their function signature must be.
Example
>>> hooks = HookRegistry() >>> hooks.register_definition('pre_set', 'Callable[[ObjectStore, LDAPRecord], None]')
- Parameters
hook_name – the name of the hook
signature – A string in Python type annotation format describing the signature the hook must have
- register_hook(hook_name: str, func: Callable[[...], Any], tags: list[str] | None = None) None[source]
Register a hook for this object store. Hooks are functions with this signature:
def myhook(store: ObjectStore, record: LDAPRecord) -> None:
Use hooks to implement side-effects on select
ObjectStoremethods.Example
To register a hook that updates a an attribute named
modifyTimestampbefore saving a record to the object store, you could define the hook like so:def update_modifyTimestamp(store: ObjectStore, record: LDAPRecord) -> None: record[1]['modifyTimestamp'] = datetime.datetime.utcnow().strftime('%Y%m%d%H%M%SZ')
and register it as a pre_modify method like so:
store = ObjectStore() store.register_hook('pre_set', update_modifyTimestamp)
Note
Hooks for a particular
hook_nameare applied in the order they are registered.- Parameters
hook_name – the name of the known hook to which register this
funcfunc – the hook function
tags – the tags for the hook
- Raises
ValueError –
hook_nameis not a known hook
- get(hook_name: str, tags: list[str] | None = None) list[collections.abc.Callable[..., Any]][source]
Get a list of hook callables for the hook named by
name, possibly filtering hooks by tag.Tag filtering rules:
If a hook has no tags associated with it, it always applies.
Otherwise, if at least one of the hooks tags are present in
tags, the hook applies.
- Parameters
hook_name – the name of the hook for which to return functions
- Keyword Arguments
tags – if provided, filter the available hook functions to include only those with tags listed in
tags- Raises
ValueError – there is no known hook with name
hook_name- Returns
A list of callables.
Type Aliases
- ldap_faker.types.LDAPOptionValue = int | str
Represent a PEP 604 union type
E.g. for int | str
- ldap_faker.types.LDAPData
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object’s
(key, value) pairs
- dict(iterable) -> new dictionary initialized as if via:
d = {} for k, v in iterable:
d[k] = v
- dict(**kwargs) -> new dictionary initialized with the name=value pairs
in the keyword argument list. For example: dict(one=1, two=2)
- ldap_faker.types.LDAPRecord
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.
If the argument is a tuple, the return value is the same object.
- ldap_faker.types.LDAPSearchResult
Built-in mutable sequence.
If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.
- ldap_faker.types.ModList
Built-in mutable sequence.
If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.
- ldap_faker.types.AddModList
Built-in mutable sequence.
If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.
- ldap_faker.types.LDAPFixtureList = str | tuple[str, list[str]] | list[tuple[str, str, list[str]]]
Represent a PEP 604 union type
E.g. for int | str