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]
This is a mixin for use with
unittest.TestCase
. Properly configured, it will patchldap.initialize
to use ourFakeLDAP.initialize
fake function instead, which will returnFakeLDAPObject
objects instead ofldap.ldapobject.LDAPObject
objects.ldap_modules
is a list of python module paths in which we should patchldap.initialize
with ourFakeLDAP.initialize
method. For example:class TestMyStuff(LDAPFakerMixin, unittest.TestCase): ldap_modules = ['myapp.module']
will cause
LDAPFakerMixin
to patchmyapp.module.ldap.initialize
.ldap_fixtures
names one or more JSON files containing LDAP records to load into aObjectStore
viaObjectStore.load_objects
.ldap_fixtures
can 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
LDAPServerFactory
with a single defaultObjectStore
with the contents ofmyfixture.json
loaded in.If we define our test class like so:
class TestMyStuff(LDAPFakerMixin, unittest.TestCase): ldap_fixtures = ('myfixture.json', ['389'])
We will build our
LDAPServerFactory
with a single defaultObjectStore
with the contents ofmyfixture.json
loaded 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
LDAPServerFactory
with twoObjectStore
objects. The first will have the data fromserver1.json
and will be used with urildap://server1
. The second will be used with urildap://server2
and have the data from with the contents ofserver2.json
loaded in, and will have the tag389
applied to it.Note
The tags are used when configuring behavior for our
ObjectStore`
. The389
tag tells theObjectStore
to emulate a 389 type LDAP server (Redhat Directory Server).- ldap_fixtures: Optional[ldap_faker.types.LDAPFixtureList] = None
The filenames of fixtures to load into our fake LDAP servers
- server_factory: LDAPServerFactory
The
LDAPServerFactory
configured 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. Iffilename
is 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_factory
with one or moreObjectStore
objects 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 associatedObjectStore
Note
If you want to populate your
LDAPServerFactory
in 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
LDAPServerFactory
object to populate
- classmethod setUpClass()[source]
Build the
LDAPServerFactory
we’ll use and save it as a class attribute.We do this as a classmethod because constructing our
ObjectStore
objects is time consuming and we don’t want to have to do it for each of our tests.
- classmethod tearDownClass()[source]
Delete our
server_factory
so we con’t corrupt future tests or leak memory.
- setUp()[source]
Create a
FakeLDAP
instance, make it use theserver_factory
that oursetUpClass
created, andpatch
ldap.initialize
in each of the modules named inldap_modules
. Save theFakeLDAP
instance to ourfake_ldap
attribute for later use in our test code.
- last_connection() Optional[FakeLDAPObject] [source]
Return the
FakeLDAPObject
for 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: Optional[str] = None) List[FakeLDAPObject] [source]
Return a the list of
FakeLDAPObject
objects 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
FakeLDAPObject
option 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: Optional[Dict[str, Any]] = None) None [source]
Assert that a specific
FakeLDAPObject
method 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
LDAPCallRecord
for how thearguments
dict should be constructed.
- assertLDAPConnectionMethodCalledAfter(conn: FakeLDAPObject, api_name: str, target_api_name: str) None [source]
Assert that a specific
FakeLDAPObject
method was called after another specificFakeLDAPObject
method.- 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_name
in the call history
- class ldap_faker.LDAPCallRecord(api_name: str, args: Dict[str, Any])[source]
This is a single LDAP call record, used by
CallHistory
to store information about calls to LDAP api functions.api_name
is the name of the LDAP api call made (e.g.simple_bind_s
,search_s
).args
is 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: Optional[List[LDAPCallRecord]] = None)[source]
This class records the
python-ldap
call history for a particularFakeLDAPObject
asLDAPCallRecord
objects. It works in conjunction with the@record_call
decorator. AnCallHistory
object will be configured on eachFakeLDAPObject
and on eachFakeLDAP
object capture their call history.We use this in our tests with appropriate asserts to ensure that our code called the
python-ldap
methods we expected, in the order we expected, with the arguments we expected.- filter_calls(api_name: str) List[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[LDAPCallRecord]
This property returns the list of all calls made against the parent object.
Example
To test that your code did a
ldap.simple_bind_s
call 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-ldap
functions 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_s
call, 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-ldap
functions:The class takes a fully configured
LDAPServerFactory
as an argument, and will use that factory’s collection ofOptionStore
objects to construct newFakeLDAPObject
objects.As a test runs,
FakeLDAP
keeps 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[FakeLDAPObject]
list of
FakeLDAPObject
connections created in the order in which they were requested
- calls: CallHistory
The call history for global ldap function calls
- options: OptionStore
A dictionary of LDAP options set
- 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, fileno: ~typing.Any = None) FakeLDAPObject [source]
This is the method we use to patch
ldap.initialize
when we are testing our LDAP code. When it is called, we will ask ourFakeLDAP.server_factory
factory for theObjectStore
most appropriate for the LDAP uriuri
, create aFakeLDAPObject
with acopy.deepcopy
of thatObjectStore
, and return theFakeLDAPObject
.Note
Of all the arguments in our signature, we only actually use
uri
. The other arguments are recorded in ourFakeLDAP.calls
call 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
ObjectStore
foruri
- 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 keyoption
in ourFakeLDAP.options
dictionary 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) ldap_faker.types.LDAPOptionValue [source]
Get a global python-ldap option. If our code hasn’t set an
option
yet, return the default frompython-ldap
for 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.initialize
call was made with LDAP URI ofuri
.- Parameters
uri – The LDAP URI to look for in our connection history
- Returns
True
if at least one connection touri
was made,False
otherwise.
- get_connections(uri: str) List[FakeLDAPObject] [source]
Return a list of
FakeLDAPObject
connections to LDAP URIuri
.- Parameters
uri – The LDAP URI to look for in our connection history
- Returns
A list of
FakeLDAPObject
objects associated with LDAP URIuri
.
- connection_calls(api_name: Optional[str] = None, uri: Optional[str] = None) CallHistory [source]
Filter our the call history for our connections by function name and optionally LDAP URI.
Args:
- 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
CallHistory
with combined calls from the filtered connections.
- class ldap_faker.FakeLDAPObject(uri: str, store: Optional[ObjectStore] = None)[source]
This class simulates most of the interface of
ldap.ldapobject.LDAPObject
which 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
ObjectStore
can’t be undone without re-copying from its source inServers
.- Parameters
uri – the LDAP URI of the connection
- Keyword Arguments
directory – a populated
ObjectStore
- hostname
port for this connection
- Type
the host
- options: OptionStore
we store data from
set_option
calls here
- store: ObjectStore
our copy of our ObjectStore for this connection
- calls: CallHistory
The method call history
- tls_enabled: bool
Set to True if
start_tls_s
was called
- bound_dn: Optional[str]
Set by
simple_bind_s
to the dn of the user after success
- set_option(option: int, invalue: ldap_faker.types.LDAPOptionValue) None [source]
This method sets the value of the
ldap.ldap.ldapobject.LDAPObject`
option specified byoption
toinvalue
.- Parameters
option – the option
value – the value to set the option to
- Raises
ValueError –
option
is not a validpython-ldap
option
- get_option(option: int) ldap_faker.types.LDAPOptionValue [source]
This method returns the value of the
ldap.ldap.ldapobject.LDAPObject`
option specified byoption
.Note
If your code did not call
FakeLDAPOption.set_option
for this option, we’ll getKeyError
- Parameters
option – the option
- Raises
ValueError –
option
is not a validpython-ldap
optionKeyError –
option
is not a validpython-ldap
option
- Returns
The value of the option
- simple_bind_s(who: str = None, cred: str = None, serverctrls: List[LDAPControl] = None, clientctrls: List[LDAPControl] = None) Optional[Tuple[Union[int, str], List[Tuple[str, Dict[str, List[bytes]]]], int, List[LDAPControl]]] [source]
Perform a bind. This will look in the object store for an object with dn of
who
and comparecred
to theuserPassword
attribute for that object.- Keyword Arguments
who – the dn of the user with which to bind
cred – the password for that user
- Raises
ldap.INVALID_CREDENTIALS – the
who
did not match thecred
- whoami_s() str [source]
This synchronous method 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, attrsonly: int = 0, serverctrls: List[LDAPControl] = None, clientctrls: List[LDAPControl] = None, timeout: int = -1, sizelimit: int = 0) int [source]
- result3(msgid: int = -1, all: int = 1, timeout: int = None) Tuple[Union[int, str], List[Tuple[str, Dict[str, List[bytes]]]], int, List[LDAPControl]] [source]
Retrieve the results of our
FakeLDAPObject.search_ext
call.Note
The
all
andtimeout
keyword arguments are ignored here.- Keyword Arguments
msgid – the
msgid
returned by theFakeLDAPObject.search_ext
callall – if 1, return all results at once; if 0, return them one at a time (ignored)
- Returns
A
ldap.result3
4-tuple.
- search_s(base: str, scope: int, filterstr: str = '(objectClass=*)', attrlist: List[str] = None, attrsonly: int = 0) List[ldap_faker.types.LDAPRecord] [source]
- start_tls_s() None [source]
Negotiate TLS with server.
This sets our
tls_enabled
attribute toTrue
.- Raises
ldap.LOCAL_ERROR –
start_tls_s
was done twice on the same connection
- compare_s(dn: str, attr: str, value: bytes) bool [source]
Perform an LDAP comparison between the attribute named
attr
of 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
dn
exists in our object store- Returns
True
if the values are equal,False
otherwise.
- modify_s(dn, modlist: ldap_faker.types.ModList) Tuple[Union[int, str], List[Tuple[str, Dict[str, List[bytes]]]], int, List[LDAPControl]] [source]
Modify the object with dn of
dn
using the modlistmodlist
.Each element in the list modlist should be a tuple of the form
(mod_op: int, mod_type: str, mod_vals: Union[bytes, List[bytes]])
, wheremod_op
indicates the operation (one ofldap.MOD_ADD
,ldap.MOD_DELETE
, orldap.MOD_REPLACE
,mod_type
is a string indicating the attribute type name, andmod_vals
is either a bytes value or a list of bytes values to add, delete or replace respectively. For the delete operation,mod_vals
may beNone
indicating that all attributes are to be deleted.Note
ldap.modlist.modifyModlist
MAY be your friend here for generating modlists. Do read the note in those docs aboutldap.MOD_DELETE
/ldap.MOD_ADD
vs.ldap.MOD_REPLACE
to 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
dn
exists 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.result3
type 4-tuple.
- delete_s(dn: str) None [source]
Delete the object with dn of
dn
from our object store.Each element in the list modlist should be a tuple of the form
(mod_type: str, mod_vals: List[bytes])
, wheremod_type
is a string indicating the attribute type name, andmod_vals
is either a string value or a list of string values to add, delete or replace respectively. For the delete operation, mod_vals may beNone
indicating 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
dn
exists 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
.modlist
is 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
dn
already 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, delold: int = 1, serverctrls: List[LDAPControl] = None, clientctrls: List[LDAPControl] = 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 parameternewsuperior
is 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.
- Raises
ldap.NO_SUCH_OBJECT – no object with dn of
dn
exists 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]
This class registers
ObjectStore
objects to be used byFakeLDAP.initialize()
in constructingFakeLDAPObject
objects.ObjectStore
objects are named registered here by LDAP uri (in reality, any string).You may do one of two things, but not both:
Configure a default
ObjectStore
that will be used for allldap.initialize
calls regardless ofuri
Assign a specific
ObjectStore
for eachuri
you will be using in your code.
Example
To register a default
ObjectStore
that will be used for everyuri
passed 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 aFakeLDAPObject
configured with acopy.deepcopy
of theObjectStore
store
, no matter whaturi
it passes toldap.initialize()
.To register a different
ObjectStores
that 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 aFakeLDAPObject
configured with acopy.deepcopy
of theObjectStore
objectstore1
, while if it doesldap.initialize('ldap://server2' )
, it will get aFakeLDAPObject
configured with acopy.deepcopy
of theObjectStore
objectstore2
.- load_from_file(filename: str, uri: Optional[str] = None, tags: Optional[List[str]] = 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
ObjectStore
we 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
ObjectStore
with a specificuri
RuntimeWarning – raised if we try to overwrite an already registered object store with our new one
- register(store: ObjectStore, uri: Optional[str] = None) None [source]
Register a new
ObjectStore
to be used as our fake LDAP server for when we run our fakeinitialize
function.- 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
ObjectStore
with a specificuri
RuntimeWarning – raised if we try to overwrite an already registered object store with a new one
- get(uri: str) ObjectStore [source]
Return a
copy.deepcopy
of theObjectStore
identified byuri
.- Parameters
uri – use this uri to look up which
ObjectStore
to use- Raises
ldap.SERVER_DOWN – no
ObjectStore
could be found foruri
- Returns
A
copy.deepcopy
of theObjectStore
- class ldap_faker.ObjectStore(tags: Optional[List[str]] = None)[source]
This class represents our actual simulated LDAP object store. Copies of this will be used to configure
FakeLDAPObject
objects.- raw_objects: ldap_faker.types.RawLDAPObjectStore
LDAP records as they would have been returned by
python-ldap`
- objects: ldap_faker.types.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 aCILDAPData
dict (CaseInsensitiveDict[str, List[str]])
)We need the data dict to have values as
List[str]
so that our filtering works properly –ldap_filter.Filter.match
only 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_objects
is that the records returned bypython-ldap
are of typeTuple[str, Dict[str, List[bytes]]]
but JSON has no concept ofbytes
ortuple
. 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-ldap
itself 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 arestr
and 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
Dict[str, List[bytes]]
- 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 arestr
and 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
dn
exists.- Parameters
dn – the dn of the object to look for
- Keyword Arguments
validate – if
True
, validate thatdn
is a valid dn- Returns
True
if the object exists,False
otherwise.
- 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
dn
exists 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
dn
exists in our object store- Returns
The data for an LDAP object
- set(dn: str, data: ldap_faker.types.LDAPData, bind_dn: Optional[str] = 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: Optional[str] = None) None [source]
Modify the object with dn of
dn
using the modlistmodlist
.Each element in the list modlist should be a tuple of the form
(mod_op: int, mod_type: str, mod_vals: Union[bytes, List[bytes]])
, wheremod_op
indicates the operation (one ofldap.MOD_ADD
,ldap.MOD_DELETE
, orldap.MOD_REPLACE
,mod_type
is a string indicating the attribute type name, andmod_vals
is either a bytes value or a list of bytes values to add, delete or replace respectively. For the delete operation,mod_vals
may beNone
indicating that all attributes are to be deleted.Note
ldap.modlist.modifyModlist
MAY be your friend here for generating modlists. Do read the note in those docs aboutldap.MOD_DELETE
/ldap.MOD_ADD
vs.ldap.MOD_REPLACE
to 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
dn
exists 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: Optional[str] = None) None [source]
Create an object in our store with dn of
dn
.modlist
is 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
dn
already exists in our object storeldap.INSUFFICIENT_ACCESS – you need to do a non-anonymous bind before doing this
- delete(dn: str, bind_dn: Optional[str] = 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: Optional[List[str]] = None) ldap_faker.types.LDAPSearchResult [source]
Do a
ldap.SCOPE_BASE
search. Return the requested attributes of the object in our object store withdn
ofbase
that also matchesfilterstr
.Note
We return a
copy.deepcopy
of 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 –
base
was not a well-formed DNldap.FILTER_ERROR –
filterstr
is has bad filter syntaxldap.NO_SUCH_OBJECT – no object with dn of
base
exists in the object store
- Returns
A list with one element – the object with dn of
base
.
- search_onelevel(base: str, filterstr: str, attrlist: Optional[List[str]] = None) ldap_faker.types.LDAPSearchResult [source]
Do a
ldap.SCOPE_ONELEVEL
search, for objects directly under basednbase
that matchfilterstr
.Note
We return a
copy.deepcopy
of 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 –
base
was not a well-formed DNldap.FILTER_ERROR –
filterstr
is has bad filter syntax
- Returns
A list of LDAP objects – 2-tuples of
(dn, data)
.
- search_subtree(base: str, filterstr: str, attrlist: Optional[List[str]] = None, include_operational_attributes: bool = False) ldap_faker.types.LDAPSearchResult [source]
Do a
ldap.SCOPE_SUBTREE
search, for objects under basednbase
that matchfilterstr
.- Parameters
base – the dn of the object to return
filterstr – the ldap filter string
Note
We return a
copy.deepcopy
of 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 –
base
was not a well-formed DNldap.FILTER_ERROR –
filterstr
is 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
)value – the value we want the option to be set to
- Raises
ValueError –
option
is not a validpython-ldap
option
- get(option: int) ldap_faker.types.LDAPOptionValue [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 –
option
is not a validpython-ldap
option- Returns
The value for the option, or the default.
Hook management
- ldap_faker.hooks = <ldap_faker.hooks.HookRegistry object>
- 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]
- property definitions: List[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, tags: Optional[List[str]] = 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
ObjectStore
methods.Example
To register a hook that updates a an attribute named ``modifyTimestamp` before 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_name
are applied in the order they are registered.- Parameters
hook_name – the name of the known hook to which register this
func
func – the hook function
- Raises
ValueError –
hook_name
is not a known hook
- get(hook_name: str, tags: Optional[List[str]] = None) List[Callable] [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
The central part of internal API.
This represents a generic version of type ‘origin’ with type arguments ‘params’. There are two kind of these aliases: user defined and special. The special ones are wrappers around builtin collections and ABCs in collections.abc. These must have ‘name’ always set. If ‘inst’ is False, then the alias can’t be instantiated, this is used by e.g. typing.List and typing.Dict.
- ldap_faker.types.LDAPData
The central part of internal API.
This represents a generic version of type ‘origin’ with type arguments ‘params’. There are two kind of these aliases: user defined and special. The special ones are wrappers around builtin collections and ABCs in collections.abc. These must have ‘name’ always set. If ‘inst’ is False, then the alias can’t be instantiated, this is used by e.g. typing.List and typing.Dict.
- ldap_faker.types.LDAPRecord
The central part of internal API.
This represents a generic version of type ‘origin’ with type arguments ‘params’. There are two kind of these aliases: user defined and special. The special ones are wrappers around builtin collections and ABCs in collections.abc. These must have ‘name’ always set. If ‘inst’ is False, then the alias can’t be instantiated, this is used by e.g. typing.List and typing.Dict.
- ldap_faker.types.LDAPSearchResult
The central part of internal API.
This represents a generic version of type ‘origin’ with type arguments ‘params’. There are two kind of these aliases: user defined and special. The special ones are wrappers around builtin collections and ABCs in collections.abc. These must have ‘name’ always set. If ‘inst’ is False, then the alias can’t be instantiated, this is used by e.g. typing.List and typing.Dict.
- ldap_faker.types.ModList
The central part of internal API.
This represents a generic version of type ‘origin’ with type arguments ‘params’. There are two kind of these aliases: user defined and special. The special ones are wrappers around builtin collections and ABCs in collections.abc. These must have ‘name’ always set. If ‘inst’ is False, then the alias can’t be instantiated, this is used by e.g. typing.List and typing.Dict.
- ldap_faker.types.AddModList
The central part of internal API.
This represents a generic version of type ‘origin’ with type arguments ‘params’. There are two kind of these aliases: user defined and special. The special ones are wrappers around builtin collections and ABCs in collections.abc. These must have ‘name’ always set. If ‘inst’ is False, then the alias can’t be instantiated, this is used by e.g. typing.List and typing.Dict.
- ldap_faker.types.LDAPFixtureList
The central part of internal API.
This represents a generic version of type ‘origin’ with type arguments ‘params’. There are two kind of these aliases: user defined and special. The special ones are wrappers around builtin collections and ABCs in collections.abc. These must have ‘name’ always set. If ‘inst’ is False, then the alias can’t be instantiated, this is used by e.g. typing.List and typing.Dict.
alias of
Union
[str
,Tuple
[str
,List
[str
]],List
[Tuple
[str
,str
,List
[str
]]]]