Mocking
You can mock ldap3 in your project using the MockSyncStrategy or the MockAsyncStrategy. Both of these strategies are based on the MockBaseStreategy that emulates a simple LDAP server and can be used to test the LDAP functionalities in your project. You can even define a specific kind of LDAP server to emulate and MockBaseStrategy will provide a suitable schema and the relevant DSA info.
To populate the DIT (the Directory Information Tree, the hierarchical database that contains the LDAP data) you can provide data from an actual LDAP server via a JSON data file or you can create at runtime only the entries and the attributes needed for your test suite.
Anyway if you want to use Simple Bind authentication you must provide users and passwords needed by the test. No ACL or rights control is done by the MockBaseStrategy.
To mock the ldap3 library in your project you must define a fake Server object and set the client_strategy attribute to MOCK_SYNC or MOCK_ASYNC while defining the Connection object:
from ldap3 import Server, Connection, MOCK_SYNC
server = Server('my_fake_server')
connection = Connection(server, user='cn=my_user,ou=test,o=lab', password='my_password', client_strategy=MOCK_SYNC)
then you can load the json entries file to add data to the DIT:
connection.strategy.entries_from_json('my_entries.json')
or add entries dynamically at runtime:
connection.strategy.add_entry('cn=user0,ou=test,o=lab', {'userPassword': 'test0000', 'sn': 'user0_sn', 'revision': 0})
connection.strategy.add_entry('cn=user1,ou=test,o=lab', {'userPassword': 'test1111', 'sn': 'user1_sn', 'revision': 0})
connection.strategy.add_entry('cn=user2,ou=test,o=lab', {'userPassword': 'test2222', 'sn': 'user2_sn', 'revision': 0})
Note
MockBaseStrategy doesn’t check the validity of the added entries against the schema, so you can just add the entries and the attribute values needed to perform your tests.
Then you can use the mock connection as a normal connection to an LDAP server.
Note
The MockBaseStrategy provides only Simple Authentication bind. You can bind to any object in the dict that has a userPassword attribute
(either single or multi-valued). The password must be stored as cleartext. You cannot use the auto_bind
parameter because the DIT is
populated after the creation of the Connection object.
MockBaseStrategy supports the Bind, Unbind, Add, Modify, ModifyDn, Compare, Delete and Search operations (except for the extensible match). Abandon and Extended are not supported.
Note
You can replicate the DIT of a real server (or just the portions of the Tree that you need in your tests) using the response_to_file()
method
of the Connection object with raw output. Just perform a SUBTREE search with ALL_ATTRIBUTES (or the attributes needed in your tests) with
the needed base and a filter as (objectclass=*)
that captures every object in the DIT:
from ldap3 import Server, Connection, ALL_ATTRIBUTES
server = Server('my_real_server')
connection = Connection(server, 'cn=my_real_user,ou=test,o=lab', 'my_real_password', auto_bind=True)
if connection.search('ou=test,o=lab', '(objectclass=*)', attributes=ALL_ATTRIBUTES):
connection.response_to_file('my_entries.json', raw=True)
The my_entries.json file can then be used in the entries_from_json()
method of the MockBaseStrategy.
While defining the mock server you can specify a predefined schema with the get_info
parameter:
from ldap3 import Server, Connection, OFFLINE_SLAPD_2_4
server = Server('my_fake_server', get_info=OFFLINE_SLAPD_2_4)
The available offline schemas are OFFLINE_SLAPD_2_4 (OpenLDAP), OFFLINE_EDIR_8_8_8 (eDirectory8), OFFLINE_EDIR_9_1_4 (eDirectory9), OFFLINE_AD_2012_R2 (Active Directory) and OFFLINE_DS389_1_3_3 (389 Directory Server).
You can also specify a previously saved schema and info retrieved from a real server:
from ldap3 import Server, Connection, ALL
# Retrieve server info and schema from a real server
server = Server('my_real_server', get_info=ALL)
connection = Connection(server, 'cn=my_user,ou=test,o=lab', 'my_real_password', auto_bind=True)
# Store server info and schema to json files
server.info.to_file('my_real_server_info.json')
server.schema.to_file('my_real_server_schema.json')
Full-featured fake server
You can create a full-featured fake server with Server.from_definition(), a static method of the Server object with the following parameters:
host: name of the fake server
dsa_info: DsaInfo preloaded object or a json formatted string or a file name
dsa_schema: SchemaInfo preloaded object or a json formatted string or a file name
port: fake port
use_ssl: fake ssl connection
formatter: custom formatters
A complete example
The following code retrieves the schema and the server info from a real server, then read the entries from a portion of the DIT and store them in a json file. Then a fake server is created and loaded with the previoulsy saved schema, server info and entries and a fake user is defined for simple binding:
from ldap3 import Server, Connection, ALL, ALL_ATTRIBUTES, MOCK_SYNC
REAL_SERVER = 'my_real_server'
REAL_USER = 'cn=my_real_user,ou=test,o=lab'
REAL_PASSWORD = 'my_real_password'
# Retrieve server info and schema from a real server
server = Server(REAL_SERVER, get_info=ALL)
connection = Connection(server, REAL_USER, REAL_PASSWORD, auto_bind=True)
# Store server info and schema to json files
server.info.to_file('my_real_server_info.json')
server.schema.to_file('my_real_server_schema.json')
# Read entries from a portion of the DIT from real server and store them in a json file
if connection.search('ou=test,o=lab', '(objectclass=*)', attributes=ALL_ATTRIBUTES):
connection.response_to_file('my_real_server_entries.json', raw=True)
# Close the connection to the real server
connection.unbind()
# Create a fake server from the info and schema json files
fake_server = Server.from_definition('my_fake_server', 'my_real_server_info.json', 'my_real_server_schema.json')
# Create a MockSyncStrategy connection to the fake server
fake_connection = Connection(fake_server, user='cn=my_user,ou=test,o=lab', password='my_password', client_strategy=MOCK_SYNC)
# Populate the DIT of the fake server
fake_connection.strategy.entries_from_json('my_real_server_entries.json')
# Add a fake user for Simple binding
fake_connection.strategy.add_entry('cn=my_user,ou=test,o=lab', {'userPassword': 'my_password', 'sn': 'user_sn', 'revision': 0})
# Bind to the fake server
fake_connection.bind()
Then the connection is ready to be used in your tests.