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 patch ldap.initialize to use our FakeLDAP.initialize fake function instead, which will return FakeLDAPObject objects instead of ldap.ldapobject.LDAPObject objects.

ldap_modules is a list of python module paths in which we should patch ldap.initialize with our FakeLDAP.initialize method. For example:

class TestMyStuff(LDAPFakerMixin, unittest.TestCase):

    ldap_modules = ['myapp.module']

will cause LDAPFakerMixin to patch myapp.module.ldap.initialize.

ldap_fixtures names one or more JSON files containing LDAP records to load into a ObjectStore via ObjectStore.load_objects. ldap_fixtures can be either a single string, a Tuple[str, List[str]], or a list of Tuple[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 default ObjectStore with the contents of myfixture.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 default ObjectStore with the contents of myfixture.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 two ObjectStore objects. The first will have the data from server1.json and will be used with uri ldap://server1. The second will be used with uri ldap://server2 and have the data from with the contents of server2.json loaded in, and will have the tag 389 applied to it.

Note

The tags are used when configuring behavior for our ObjectStore`. The 389 tag tells the ObjectStore to emulate a 389 type LDAP server (Redhat Directory Server).

ldap_modules: List[str] = []

The list of python paths to modules that import ldap

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 our setUpClass

fake_ldap: FakeLDAP

the FakeLDAP instance created by setUp

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. If filename 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 more ObjectStore objects by looking at ldap_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 associated ObjectStore

Note

If you want to populate your LDAPServerFactory in a different way than loading directly from the JSON files listed in ldap_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 the server_factory that our setUpClass created, and patch ldap.initialize in each of the modules named in ldap_modules. Save the FakeLDAP instance to our fake_ldap attribute for later use in our test code.

tearDown()[source]

Undo the patches we made in setUp

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 the arguments 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 specific FakeLDAPObject 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
    }
)
api_name: str

the name LDAP api call

args: Dict[str, Any]

the args and kwargs dict

class ldap_faker.CallHistory(calls: Optional[List[LDAPCallRecord]] = None)[source]

This class records the python-ldap call history for a particular FakeLDAPObject as LDAPCallRecord objects. It works in conjunction with the @record_call decorator. An CallHistory object will be configured on each FakeLDAPObject and on each FakeLDAP 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 a Dict[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 particulary

Example

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 of OptionStore objects to construct new FakeLDAPObject 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 our FakeLDAP.server_factory factory for the ObjectStore most appropriate for the LDAP uri uri, create a FakeLDAPObject with a copy.deepcopy of that ObjectStore, and return the FakeLDAPObject.

Note

Of all the arguments in our signature, we only actually use uri. The other arguments are recorded in our FakeLDAP.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 for uri

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 key option in our FakeLDAP.options dictionary and set its value to value.

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 from python-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 of uri.

Parameters

uri – The LDAP URI to look for in our connection history

Returns

True if at least one connection to uri was made, False otherwise.

get_connections(uri: str) List[FakeLDAPObject][source]

Return a list of FakeLDAPObject connections to LDAP URI uri.

Parameters

uri – The LDAP URI to look for in our connection history

Returns

A list of FakeLDAPObject objects associated with LDAP URI uri.

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 call ldap.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 in Servers.

Parameters

uri – the LDAP URI of the connection

Keyword Arguments

directory – a populated ObjectStore

uri: str

the LDAP URI for this connection

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

deref: int

Controls whether aliases are automatically dereferenced

protocol_version: int

Version of LDAP in use (always ldap.VERSION3`)

sizelimit: int

Limit on size of message to receive from server

network_timeout: int

Limit on waiting for a network response, in seconds.

timelimit: int

Limit on waiting for any response, in seconds.

timeout: int

Limit on waiting for any response, in seconds.

set_option(option: int, invalue: ldap_faker.types.LDAPOptionValue) None[source]

This method sets the value of the ldap.ldap.ldapobject.LDAPObject` option specified by option to invalue.

Parameters
  • option – the option

  • value – the value to set the option to

Raises

ValueErroroption is not a valid python-ldap option

get_option(option: int) ldap_faker.types.LDAPOptionValue[source]

This method returns the value of the ldap.ldap.ldapobject.LDAPObject` option specified by option.

Note

If your code did not call FakeLDAPOption.set_option for this option, we’ll get KeyError

Parameters

option – the option

Raises
  • ValueErroroption is not a valid python-ldap option

  • KeyErroroption is not a valid python-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 compare cred to the userPassword 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 the cred

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 and timeout keyword arguments are ignored here.

Keyword Arguments
  • msgid – the msgid returned by the FakeLDAPObject.search_ext call

  • all – 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 to True.

Raises

ldap.LOCAL_ERRORstart_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 entry dn, and the value value. For multi-valued attributes, the test is whether any of the values match value.

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 modlist modlist.

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]]), where mod_op indicates the operation (one of ldap.MOD_ADD, ldap.MOD_DELETE, or ldap.MOD_REPLACE, mod_type is a string indicating the attribute type name, and mod_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 be None 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 about ldap.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
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]), where mod_type is a string indicating the attribute type name, and mod_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 be None indicating that all attributes are to be deleted.

Parameters

dn – the dn of the object to delete

Raises
add_s(dn: str, modlist: ldap_faker.types.AddModList) None[source]

Add an object with dn of dn.

modlist is similar the one passed to modify_s, except that the operation integer is omitted from the tuples in modlist. 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
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, and newrdn, the new RDN to give to the entry. The optional parameter newsuperior 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
unbind_s() None[source]

Unbind from the server.

This sets our bound_dn to None.

LDAP Server like objects

class ldap_faker.LDAPServerFactory[source]

This class registers ObjectStore objects to be used by FakeLDAP.initialize() in constructing FakeLDAPObject objects. ObjectStore objects are named registered here by LDAP uri (in reality, any string).

You may do one of two things, but not both:

Example

To register a default ObjectStore that will be used for every uri passed to FakeLDAP.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 a FakeLDAPObject configured with a copy.deepcopy of the ObjectStore store, no matter what uri it passes to ldap.initialize().

To register a different ObjectStores that will be used for specific uris:

>>> 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 a FakeLDAPObject configured with a copy.deepcopy of the ObjectStore object store1, while if it does ldap.initialize('ldap://server2' ), it will get a FakeLDAPObject configured with a copy.deepcopy of the ObjectStore object store2.

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 new ObjectStore, load it with that JSON File and register it with uri of uri.

Parameters

filename – the full path to our JSON file

Keyword Arguments
  • uri – the uri to assign to the ObjectStore we create

  • tags – 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 specific uri

  • 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 fake initialize 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 specific uri

  • 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 the ObjectStore identified by uri.

Parameters

uri – use this uri to look up which ObjectStore to use

Raises

ldap.SERVER_DOWN – no ObjectStore could be found for uri

Returns

A copy.deepcopy of the ObjectStore

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

tags: List[str]

used when filtering hooks to apply

controls: Dict[str, Any]

can be used by hooks to store state

operational_attributes: Set[str]

list of attributes that have to be specifically requested

convert_LDAPData(data: ldap_faker.types.LDAPData) ldap_faker.types.CILDAPData[source]

Convert an incoming LDAPData` dict (``Dict[str, List[bytes]]) to a CILDAPData 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 by python-ldap are of type Tuple[str, Dict[str, List[bytes]]] but JSON has no concept of bytes or tuple. Thus we will expect the LDAP records in the file to have type List[str, Dict[str, List[str]]] and we will convert them to Tuple[str, Dict[str, List[bytes]]] before saving to raw_objects

Parameters

filename – the path to the JSON file to load

Raises
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 (a str) and the second element is a dict where the keys are str and the values are lists of bytes.

Raises
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 (a str) and the second element is a dict where the keys are str and the values are lists of bytes.

Raises
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 that dn 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
update(dn: str, modlist: ldap_faker.types.ModList, bind_dn: Optional[str] = None) None[source]

Modify the object with dn of dn using the modlist modlist.

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]]), where mod_op indicates the operation (one of ldap.MOD_ADD, ldap.MOD_DELETE, or ldap.MOD_REPLACE, mod_type is a string indicating the attribute type name, and mod_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 be None 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 about ldap.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
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 to modify_s, except that the operation integer is omitted from the tuples in modlist. 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
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 with dn of base that also matches filterstr.

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
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 basedn base that match filterstr.

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
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 basedn base that match filterstr.

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
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

ValueErroroption is not a valid python-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

ValueErroroption is not a valid python-ldap option

Returns

The value for the option, or the default.

Hook management

ldap_faker.hooks = <ldap_faker.hooks.HookRegistry object>
class ldap_faker.Hook(func: Callable, tags: List[str])[source]
func: Callable
tags: List[str]
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]"
name

the name of the hook, e.g. “pre_save”

Type

str

signature

the python type annotation signature that the hook should implement, e.g. “Callable[[ObjectStore, LDAPRecord], None]”

Type

str

name: str
signature: str
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

ValueErrorhook_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.

alias of Union[int, str]

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.

alias of Dict[str, List[bytes]]

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.

alias of Tuple[str, Dict[str, List[bytes]]]

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.

alias of List[Tuple[str, Dict[str, List[bytes]]]]

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.

alias of List[Tuple[int, str, List[bytes]]]

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.

alias of List[Tuple[str, List[bytes]]]

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]]]]