OpenLDAP
Authenticate using OpenLDAP or Active Directory.
Authentication & Identity Management · LDAP Authentication
Details
| ID | OpenLDAP |
|---|---|
| Provider | Open Source |
| Category | Authentication & Identity Management |
| From Version | 5.0.0 |
| Docker Image | demisto/py3-tools:1.0.0.10895515 |
| Supported Modules | Agentix XSIAM |
README
Overview
This integration enables using your OpenLDAP or Active Directory user authentication settings in Cortex XSOAR. Users can log in to Cortex XSOAR with their OpenLDAP or Active Directory username and passwords, and their permissions in Cortex XSOAR will be set according to the groups and mapping set in AD Roles Mapping.
- For connecting to the LDAP server with TLS connection it is recommended to use this integration instead of the server integration
Active Directory Authentication.
Use Cases
Use OpenLDAP or Active Directory user authentication groups to set user roles in Cortex XSOAR.
Configure OpenLDAP on Cortex XSOAR
- Navigate to Settings > Integrations > Servers & Services.
- Search for ‘LDAP Authentication’ (‘OpenLDAP’ or ‘Active Directory Authentication’ should work as well).
- Click Add instance to create and configure a new integration instance.
- Name: a textual name for the integration instance.
- LDAP Server Vendor (OpenLDAP or Active Directory. Use ‘Auto’ option to determine the vendor automatically.)
- Server IP or Host Name (e.g., 192.168.0.1)
- Port. If not specified, default port is 389, or 636 for LDAPS.
- User DN (e.g cn=admin,ou=users,dc=domain,dc=com)
- Base DN (e.g. DC=domain,DC=com)
- Auto populate groups
- Groups Object Class
- Groups Unique Identifier Attribute
- Group Membership Identifier Attribute
- User Object Class
- User Unique Identifier Attribute
- Page size
- Connection Type (None, SSL or Start TLS)
- SSL Version (None, TLS, TLSv1, TLSv1_1, TLSv1_2, TLS_CLIENT)
(The SSL\TLS version to use in SSL or Start TLS connections types. It is recommended to select the TLS_CLIENT option, which auto-negotiate the highest protocol version that both the client and server support, and configure the context client-side connections. For more information please see: ssl.PROTOCOLS). - Trust any certificate (not secure)
- Use system proxy settings
- Click Test to validate the URLs, token, and connection.
Additional Information
Steps required for setting AD roles Mapping: (The steps refer to an OpenLDAP server)
-
Create OpenLDAP child entry of User Account template under wanted Organizational Unit and Posix Group, with uid as part of DN:

-
Create OpenLDAP child entry of Posix Group template, with created account from step 1 as memberUid:

- If using different attributes and class/group templates (different objectClass), customize the following default values in the instance configuration:
- Groups Object Class
- Groups Unique Identifier Attribute
- Group Membership Identifier Attribute
- User Object Class
- User Unique Identifier Attribute
-
Navigate to Settings > USERS AND ROLES > ROLES.
-
Choose the role.
-
Add the created group from step 2 to AD Roles Mapping.

- Login to Cortex XSOAR using uid or full DN and password of the user created in step 1.
Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
ad-authenticate
Performs a simple bind operation on the LDAP server.
Base Command
ad-authenticate
Input
| Argument Name | Description | Required |
|---|---|---|
| username | The username for simple authentication. | Required |
| password | The password for simple authentication. | Required |
Context Output
There is no context output for this command.
Command Example
!ad-authenticate username=user password=secret
Human Readable Output
Done
ad-groups
Fetches LDAP groups under a given base DN.
Base Command
ad-groups
Input
| Argument Name | Description | Required |
|---|---|---|
| specific-groups | A list of group object names to fetch. The list is delimited by a number sign (#). | Optional |
Context Output
There is no context output for this command.
Command Example
!ad-groups
Human Readable Output
{
"Controls": null,
"Entries": [
{
"Attributes": [
{
"Name": "primaryGroupToken",
"Values": [
"111"
]
}
],
"DN": "CN=Admin,CN=Builtin,DC=Test,DC=Test1"
},
{
"Attributes": [
{
"Name": "primaryGroupToken",
"Values": [
"222"
]
}
],
"DN": "CN=Users,CN=Builtin,DC=Test,DC=Test1"
},
],
"Referrals": [
"ldap://domainTest/CN=Test,DC=Test,DC=Test1",
"ldap://domainTest2/CN=Test,DC=Test,DC=Test2"
]
}
ad-authenticate-and-roles
Performs a simple bind operation on the LDAP server and returns the authenticated user’s groups.
Base Command
ad-authenticate-and-roles
Input
| Argument Name | Description | Required |
|---|---|---|
| username | The username for simple authentication. | Required |
| password | The password for simple authentication. | Required |
| attribute-mail-pull | Whether to return the mail attribute. Possible values are: “true”, “false”. Default is “true”. | Optional |
| attribute-mail | Mail attribute to return in the response. Default is “mail”. | Optional |
| attribute-name-pull | Whether to return the name attribute. Possible values are: “true”, “false”. Default is “true”. | Optional |
| attribute-name | Name attribute to return in the response. Default is “name”. | Optional |
| attribute-phone-pull | Whether to return the phone attribute. Possible values are: “true”, “false”. Default is “false”. | Optional |
| attribute-phone | Phone attribute to return in the response. Default is “mobile”. | Optional |
Context Output
There is no context output for this command.
Command Example
!ad-authenticate-and-roles username='username' password='password' attribute-phone-pull=true
Human Readable Output
{
"Controls": [],
"Entries": [
{
"Attributes": [
{
"Name": "memberOf",
"Values": [
"CN=Domain ,CN=Users,DC=Test,DC=Test1"
]
},
{
"Name": "name",
"Values": [
"User Name"
]
},
{
"Name": "primaryGroupID",
"Values": [
"111"
]
},
{
"Name": "mail",
"Values": [
"username@mail.com"
]
},
{
"Name": "mobile",
"Values": [
"555-5555555"
]
}
],
"DN": "CN=User Name,CN=Users,DC=Test,DC=Test1"
}
],
"Referrals": [
"ldap://domainTest/CN=Test,DC=Test,DC=Test1",
"ldap://domainTest2/CN=Test,DC=Test,DC=Test2"
]
}
ad-entries-search
A generic LDAP search command.
Base Command
ad-entries-search
Input
| Argument Name | Description | Required |
|---|---|---|
| search_base | The location in the DIT where the search will start. Default is the provided Base DN in the configuration. | Optional |
| search_filter | A string that describes what you are searching for. When provided with additional filter arguments, the OR join operator is used. If not provided, and no other filter arguments are present, all entries are returned. Search filters are based on assertions. One assertion is a bracketed expression that affirms something about an attribute and its values, as (givenName=John) or (maxRetries>=10). On the server, each assertion resolves to True, False, or Undefined (which is treated as False) for one or more entries in the DIT. Assertions can be grouped in boolean groups where all assertions (and group, specified with &) or at least one assertion (or group, specified with |) must be True. A single assertion can be negated (not group, specified with !). Each group must be bracketed, allowing for recursive filters. Operators allowed in an assertion are = (equal), <= (less than or equal), >= (greater than or equal), =(present), ~= (approximate), and := (extensible). Surprisingly the less than and the greater than operators don’t exist in the LDAP filter syntax. The aproximate and the extensible operators are obscure and seldom used. In an equality filter you can use the character as a wildcard. | Optional |
| search_scope | Specifies how broad the search context. ‘BASE’- retrieves attributes of the entry specified in the search_base. ‘LEVEL’- retrieves attributes of the entries contained in the search_base. The base must reference a container object. ‘SUBTREE’ - retrieves attributes of the entries specified in the search_base and all subordinate containers downward. Possible values are: BASE, LEVEL, SUBTREE. Default is SUBTREE. | Optional |
| attributes | A comma-separated list of attributes to return in the response. If attributes is ‘none’, no attributes will be returned except the dn. If attributes is ‘all_user_attributes’ or ‘all_operational_attributes’, all user attributes or all operational attributes are returned. If attributes is ‘all’, both user and operational attributes are returned. Possible values are: none, all_user_attributes, all_operational_attributes, all. Default is all. | Optional |
| cn | A comma-separated list of CNs to filter the search by (AND operator is used to join them). | Optional |
| uid | A comma-separated list of UIDs to filter the search by (AND operator is used to join them). | Optional |
| object_class | A comma-separated list of objectClasses to filter the search by (AND operator is used to join them). | Optional |
| description | A comma-separated list of descriptions to filter the search by (AND operator is used to join them). | Optional |
| page | Page to return. | Optional |
| page_size | Number of entries per page. Defaults to 50 (in case only page was provided). Maximum entries per page is 2000. | Optional |
| limit | The maximum number of entries to return. Default is 50. | Optional |
Context Output
The context output will vary depending on the LDAP server scheme configuration and the attributes provided in the command.
Command Example
!ad-entries-search base_dn="dc=openldap,dc=demisto,dc=int" object_class=top,posixAccount search_filter="(creatorsName=cn=ldapadm,dc=openldap,dc=demisto,dc=int)" attributes=all_operational_attributes page=1 page_size=2
Human Readable Output
| createTimestamp | creatorsName | dn | entryCSN | entryUUID | hasSubordinates | modifiersName | modifyTimestamp | structuralObjectClass | subschemaSubentry |
|---|---|---|---|---|---|---|---|---|---|
| 2019-12-03 11:23:40+00:00 | cn=ldapadm,dc=openldap,dc=demisto,dc=int | dc=openldap,dc=demisto,dc=int | 20191203112340.454387Z#000000#000, # | 19f7dd04-aa0b-1039-811c-25103214a95a | True | cn=ldapadm,dc=openldap,dc=demisto,dc=int | 2019-12-03 11:23:40+00:00 | domain | cn=Subschema |
| 2019-12-03 11:23:40+00:00 | cn=ldapadm,dc=openldap,dc=demisto,dc=int | cn=ldapadm,dc=openldap,dc=demisto,dc=int | 20240708140845.283421Z#000000#000, # | 19f8d2c2-aa0b-1039-811d-25103214a95a | False | cn=ldapadm,dc=openldap,dc=demisto,dc=int | 2024-07-08 14:08:45+00:00 | organizationalRole | cn=Subschema |
Configuration parameters
ldap_server_vendor— LDAP Server Vendor (required)host— Server IP or Host Name (e.g., 192.168.0.1) (required)port— Port. If not specified, default port is 389, or 636 for LDAPS.credentials— User DN (e.g., cn=admin,ou=users,dc=domain,dc=com) (required)base_dn— Base DN (e.g., DC=domain,DC=com) (required)fetch_groups— Auto populate groupsgroup_filter_class— Groups Object Classgroup_identifier_attribute— Groups Unique Identifier Attributemember_identifier_attribute— Group Membership Identifier Attributeuser_filter_class— User Object Classcustom_attributes— User Defined Attributesuser_identifier_attribute— User Unique Identifier Attributepage_size— Page sizeconnection_type— Connection Type (required)ssl_version— SSL Versioninsecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (4)
-
ad-authenticatePerforms a simple bind operation on the LDAP server.
-
ad-authenticate-and-rolesPerforms a simple bind operation on the LDAP server and returns the authenticated user's groups.
-
ad-entries-searchA generic LDAP search command.
-
ad-groupsFetches LDAP groups under a given base DN.
""" Tests module for the LDAP Authentication integration """ import json import unittest from unittest.mock import MagicMock, patch import pytest from OpenLDAP import LdapClient, entries_paged_search class Entry: def __init__(self): self.value = "OpenLDAProotDSE" class Connection: def __init__(self, vendor): if vendor == OPENLDAP: self.entries = [{"objectClass": Entry()}] else: self.entries = [{}] def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass @staticmethod def search(search_base, search_filter, search_scope, attributes): return None OPENLDAP = "OpenLDAP" ACTIVE_DIRECTORY = "Active Directory" class TestLDAPClient: @pytest.mark.parametrize("vendor", [OPENLDAP, ACTIVE_DIRECTORY]) def test_ldap_vendor(self, mocker, vendor): mocker.patch("OpenLDAP.Connection", return_value=Connection(vendor)) client = LdapClient({"ldap_server_vendor": "Auto", "host": "server_ip"}) assert client._ldap_server_vendor == vendor class TestsActiveDirectory: """ Contains unit tests for functions that deal with Active directory server only. """ def test_parse_ldap_group_entries_and_referrals(self): """ Given: - A raw response of a groups paged search in an Active directory server (received during the execution of ad-groups command). When: - Running the 'parse_ldap_group_entries_and_referrals()' function. Then: - Verify that the raw response parsed correctly and that the referrals and entries lists returned as expected. """ client = LdapClient({"ldap_server_vendor": "Active Directory", "host": "server_ip"}) ldap_group_entries = [ {"uri": ["ldap://domain1/CN=test,DC=demisto,DC=test"], "type": "searchResRef"}, {"uri": ["ldap://domain2/DC=test,DC=demisto,DC=test"], "type": "searchResRef"}, { "raw_dn": b"CN=test,CN=Users,DC=demisto,DC=test", "dn": "CN=test,CN=Users,DC=demisto,DC=test", "raw_attributes": {"primaryGroupToken": [b"11111"]}, "attributes": {"primaryGroupToken": 11111}, "type": "searchResEntry", }, { "raw_dn": b"CN=DisabledUsers,DC=demisto,DC=test", "dn": "CN=DisabledUsers,DC=demisto,DC=test", "raw_attributes": {"primaryGroupToken": [b"22222"]}, "attributes": {"primaryGroupToken": 22222}, "type": "searchResEntry", }, ] expected_referrals = ["ldap://domain1/CN=test,DC=demisto,DC=test", "ldap://domain2/DC=test,DC=demisto,DC=test"] expected_entries = [ {"DN": "CN=test,CN=Users,DC=demisto,DC=test", "Attributes": [{"Name": "primaryGroupToken", "Values": ["11111"]}]}, {"DN": "CN=DisabledUsers,DC=demisto,DC=test", "Attributes": [{"Name": "primaryGroupToken", "Values": ["22222"]}]}, ] referrals, entries = client._parse_ldap_group_entries_and_referrals(ldap_group_entries) assert referrals == expected_referrals assert entries == expected_entries def test_parse_and_authenticate_ldap_group_entries_and_referrals(self, mocker): """ Given: - A raw response of a user paged search in an Active directory server. (received during the execution of ad-authenticate-and-roles command). When: - Running the 'parse_and_authenticate_ldap_group_entries_and_referrals()' function. Then: - Verify that the raw response parsed correctly and that the referrals and entries lists returned as expected. """ client = LdapClient({"ldap_server_vendor": "Active Directory", "host": "server_ip"}) password = "123456" ldap_group_entries = [ {"uri": ["ldap://domain1/CN=test,DC=demisto,DC=test"], "type": "searchResRef"}, {"uri": ["ldap://domain2/DC=test,DC=demisto,DC=test"], "type": "searchResRef"}, { "raw_dn": b"CN=username,CN=Users,DC=demisto,DC=test", "dn": "CN=username,CN=Users,DC=demisto,DC=test", "raw_attributes": { "memberOf": [b"CN=test-group,CN=Users,DC=demisto,DC=test"], "name": [b"username"], "primaryGroupID": [b"111"], "mail": [b"user@mail.com"], "mobile": [b"050-1111111"], }, "attributes": { "memberOf": ["CN=test-group,CN=Users,DC=demisto,DC=test"], "name": "username", "primaryGroupID": 111, "mail": "user@mail.com", "mobile": "050-1111111", }, "type": "searchResEntry", }, ] expected_referrals = ["ldap://domain1/CN=test,DC=demisto,DC=test", "ldap://domain2/DC=test,DC=demisto,DC=test"] expected_entries = [ { "DN": "CN=username,CN=Users,DC=demisto,DC=test", "Attributes": [ {"Name": "memberOf", "Values": ["CN=test-group,CN=Users,DC=demisto,DC=test"]}, {"Name": "name", "Values": ["username"]}, {"Name": "primaryGroupID", "Values": ["111"]}, {"Name": "mail", "Values": ["user@mail.com"]}, {"Name": "mobile", "Values": ["050-1111111"]}, ], } ] mocker.patch("OpenLDAP.LdapClient.authenticate_ldap_user", return_value="Done") referrals, entries = client._parse_and_authenticate_ldap_group_entries_and_referrals(ldap_group_entries, password) assert referrals == expected_referrals assert entries == expected_entries @pytest.mark.parametrize( "user_logon_name, expected_ad_username", [ ("DEMISTO\\test1user", "test1user"), ("test2user@demisto.ad", "test2user"), ("mertuz ", "mertuz"), (" mertuz", "mertuz"), (" mertuz ", "mertuz"), ("DEMISTO\\test1user ", "test1user"), (" DEMISTO\\test1user", "test1user"), ("test2user@demisto.ad ", "test2user"), (" test2user@demisto.ad", "test2user"), ], ) def test_get_ad_username(self, user_logon_name, expected_ad_username): """ Given: - A user logon name (a username to login to XSOAR with), possibly with surrounding whitespace. When: - Running the 'get_ad_username()' function. Then: - Verify that the returned Active Directory username is as expected and free of surrounding whitespace. """ client = LdapClient({"ldap_server_vendor": "Active Directory", "host": "server_ip"}) ad_username = client._get_ad_username(user_logon_name) assert ad_username == expected_ad_username @pytest.mark.parametrize( "connection_type, expected_auto_bind_value", [("Start TLS", "TLS_BEFORE_BIND"), ("SSL", "NO_TLS"), ("None", "NO_TLS")] ) def test_get_auto_bind_value(self, connection_type, expected_auto_bind_value): """ Given: - A connection type: 1. Start TLS 2. SSL 3. None When: - Running the '_get_auto_bind_value()' function. Then: - Verify that the returned auto_bind value is as expected: 1. 'TLS_BEFORE_BIND' - which means that connection should upgrade it's secure level to TLS before the bind itself (STARTTLS command is executed). 2. 'NO_TLS' - The connection is secured from the beginning, thus STARTTLS command shouldn't be executed. 3. 'NO_TLS' - Connection is insecure (cleartext) and shouldn't be upgraded to TLS. """ client = LdapClient({"ldap_server_vendor": "Active Directory", "host": "server_ip", "connection_type": connection_type}) auto_bind_value = client._get_auto_bind_value() assert auto_bind_value == expected_auto_bind_value class TestsOpenLDAP: """ Contains unit tests for functions that deal with OpenLDAP server only. """ @pytest.mark.parametrize( "dn, user_identifier_attribute, expected_result", [ ("uid=user_test,cn=users_test,dc=openldap_test,dc=test,dc=int", "uid", (True, "user_test")), ("not_a_valid_dn_test", "uid", (False, "not_a_valid_dn_test")), ], ) def test_is_valid_dn(self, dn, user_identifier_attribute, expected_result): """ Given: - A DN and a user identifier attribute: 1. A valid DN. 2. Invalid DN. When: - Running the '_is_valid_dn()' function. Then: - Verify that the DN is parsed correctly and that the user returned as expected. """ client = LdapClient( { "ldap_server_vendor": "OpenLDAP", "host": "server_ip", "connection_type": "SSL", "user_identifier_attribute": user_identifier_attribute, } ) actual_result, dn = client._is_valid_dn(dn, client.USER_IDENTIFIER_ATTRIBUTE) assert (actual_result, dn) == expected_result @pytest.mark.parametrize( "dn, user_identifier_attribute", [ ("cn=users_test,dc=openldap_test,dc=test,dc=int", "uid"), ("uid=user_test,cn=users_test,dc=openldap_test,dc=test,dc=int", "new_uid"), ], ) def test_is_valid_dn_user_id_not_in_dn(self, dn, user_identifier_attribute): """ Given: 1. A DN without a user identifier attribute. 2. A DN with a wrong user identifier attribute. When: - Running the '_is_valid_dn()' function. Then: - Verify that the expected err message is raised. """ client = LdapClient( { "ldap_server_vendor": "OpenLDAP", "host": "server_ip", "connection_type": "SSL", "user_identifier_attribute": user_identifier_attribute, } ) with pytest.raises(Exception) as e: client._is_valid_dn(dn, client.USER_IDENTIFIER_ATTRIBUTE) assert e.value.args[0] == f"OpenLDAP {user_identifier_attribute} attribute was not found in user DN : {dn}" class TestLDAPAuthentication: """ Contains unit tests for general functions that deal with both OpenLDAP and Active Directory servers. """ @pytest.mark.parametrize( "ssl_version, expected_ssl_version", [("TLS", 2), ("TLSv1", 3), ("TLSv1_1", 4), ("TLSv1_2", 5), ("TLS_CLIENT", 16), (None, None), ("None", None)], ) def test_get_ssl_version(self, ssl_version, expected_ssl_version): """ Given: - An ssl protocol version: 1. TLS 2. TLSv1 3. TLSv1_1 4. TLSv1_2 5. TLS_CLIENT 6. None 7. 'None' When: - Running the '_get_ssl_version()' function. Then: - Verify that the returned ssl version value is as expected: 1. TLS - 2 2. TLSv1 - 3 3. TLSv1_1 - 4 4. TLSv1_2 - 5 5. TLS_CLIENT - 16 6. None - None 7. 'None' - None """ client = LdapClient( {"ldap_server_vendor": "OpenLDAP", "host": "server_ip", "connection_type": "SSL", "ssl_version": ssl_version} ) ssl_version_value = client._get_ssl_version() assert ssl_version_value == expected_ssl_version @pytest.mark.parametrize( "custom_attributes, expected_formatted_attributes", [("attr1=val1,attr2=val2,attr3=val3", "(attr1=val1)(attr2=val2)(attr3=val3)"), ("", "")], ) def test_get_formatted_custom_attributes(self, custom_attributes, expected_formatted_attributes): """ Given: - Custom attributes: 1. A valid comma separated list of attributes. 2. An empty string of attributes. When: - Running the '_get_formatted_custom_attributes()' function. Then: - Verify that the attributed parsed correctly. """ client = LdapClient( { "ldap_server_vendor": "OpenLDAP", "host": "server_ip", "connection_type": "SSL", "custom_attributes": custom_attributes, } ) formatted_attributes = client._get_formatted_custom_attributes() assert formatted_attributes == expected_formatted_attributes def test_get_formatted_custom_attributes_invalid_attributes_input(self): """ Given: - Invalid Custom attributes. When: - Running the '_get_formatted_custom_attributes()' function. Then: - Verify that the expected error message is raised. """ client = LdapClient( { "ldap_server_vendor": "OpenLDAP", "host": "server_ip", "connection_type": "SSL", "custom_attributes": "attr1val1,attr2=val2,attr3=val3", } ) with pytest.raises(Exception) as e: client._get_formatted_custom_attributes() assert e.value.args[0] == ( f'User defined attributes must be of the form "attrA=valA,attrB=valB,...", but got: {client.CUSTOM_ATTRIBUTE}' ) @pytest.mark.parametrize( "user_logon_name", [ ("test*"), ("test?test"), ], ) def test_has_wildcards_in_user_logon(self, user_logon_name): """ Given: 1. A user logon name contains the "*" symbol. 2. A user logon name contains the "?" symbol. When: - Running the 'has_wildcards_in_user_logon()' function. Then: - Verify that an exception is raised due to the use of wildcards in the logon name. """ client = LdapClient({"ldap_server_vendor": "Active Directory", "host": "server_ip"}) with pytest.raises(Exception) as e: client._has_wildcards_in_user_logon(user_logon_name) assert "Wildcards were detected in the user logon name" in e.value.args[0] assert user_logon_name in e.value.args[0] class TestEntriesPagedSearch(unittest.TestCase): def setUp(self): """ Set up the test by creating a mock connection and search parameters. """ self.connection = MagicMock() self.search_params = {"search_base": "dc=example,dc=com", "search_filter": "(objectClass=person)"} self.page_size = 10 def test_first_page(self): """ when running the entries_paged_search function with a page number of 1 then the search method should be called with the correct parameters and the results should be returned. """ self.connection.search.return_value = [{"name": "John Doe"}] results = entries_paged_search(self.connection, self.search_params, page=1, page_size=self.page_size) self.connection.search.assert_called_once_with(**self.search_params, paged_size=self.page_size) assert results == [{"name": "John Doe"}] def test_subsequent_page(self): """ when running the entries_paged_search function with a page number greater than 1 then the search method should be called with the correct parameters and the results should be returned. The search method should be called twice, once to skip the results and once to get the actual results. """ # Mock the connection's search method for the first search (to skip results) self.connection.search.side_effect = [ None, # First call returns None to simulate the skip search [{"name": "Jane Doe"}], # Second call returns the actual results ] # Mock the result of the first search to include the cookie self.connection.result = {"controls": {"1.2.840.113556.1.4.319": {"value": {"cookie": b"cookie_value"}}}} results = entries_paged_search(self.connection, self.search_params, page=2, page_size=self.page_size) # Assert the search method was called twice with the correct parameters assert self.connection.search.call_count == 2 self.connection.search.assert_any_call(**self.search_params, paged_size=self.page_size * 1) self.connection.search.assert_any_call(**self.search_params, paged_size=self.page_size, paged_cookie=b"cookie_value") assert results == [{"name": "Jane Doe"}] class TestEntriesSearchCommand(unittest.TestCase): """ Test class for the entries_search_command function. """ def setUp(self): """ Set up the test by creating an instance of the LdapClient class and mocking the _get_auto_bind_value method. """ self.instance = LdapClient( { "host": "server_ip", "port": "636", "credentials": {"identifier": "username", "password": "password"}, "base_dn": "dc=example,dc=com", "connection_type": "SSL", "ssl_version": "TLSv1_2", "fetch_groups": True, "insecure": False, "ldap_server_vendor": "OpenLDAP", } ) self.instance._get_auto_bind_value = MagicMock(return_value=True) @patch("OpenLDAP.Connection") @patch("OpenLDAP.create_entries_search_filter", return_value="(objectClass=*)") @patch("OpenLDAP.get_search_attributes", return_value=["cn", "mail"]) def test_entries_search_command_first_page( self, mock_get_search_attributes, mock_create_entries_search_filter, mock_connection ): """ when running the entries_search_command function with a page number of 1 then the search method should be called with the correct parameters and the results should be returned. """ # Mock the LDAP connection mock_conn_instance = mock_connection.return_value.__enter__.return_value mock_conn_instance.entries = [ MagicMock( entry_to_json=MagicMock( return_value=json.dumps( { "attributes": {"cn": ["John Doe"], "mail": ["john.doe@example.com"]}, "dn": "cn=John Doe,dc=example,dc=com", } ) ) ) ] args = {"search_base": "dc=example,dc=com", "search_scope": "SUBTREE", "page": "1", "page_size": "50"} result = self.instance.entries_search_command(args) self.instance._get_auto_bind_value.assert_called_once() mock_create_entries_search_filter.assert_called_once_with(args) mock_get_search_attributes.assert_called_once_with("all") mock_connection.assert_called_once_with( self.instance._ldap_server, self.instance._username, self.instance._password, auto_bind=True ) assert len(result.outputs) == 1 assert result.outputs[0]["cn"] == ["John Doe"] assert result.outputs[0]["mail"] == ["john.doe@example.com"] assert result.outputs[0]["dn"] == "cn=John Doe,dc=example,dc=com" @patch("OpenLDAP.Connection") @patch("OpenLDAP.create_entries_search_filter", return_value="(objectClass=*)") @patch("OpenLDAP.get_search_attributes", return_value=["cn", "mail"]) def test_entries_search_command_subsequent_page( self, mock_get_search_attributes, mock_create_entries_search_filter, mock_connection ): """ when running the entries_search_command function with a page number greater than 1 then the search method should be called with the correct parameters and the results should be returned. The search method should be called twice, once to skip the results and once to get the actual results. """ # Mock the LDAP connection mock_conn_instance = mock_connection.return_value.__enter__.return_value mock_conn_instance.entries = [ MagicMock( entry_to_json=MagicMock( return_value=json.dumps( { "attributes": {"cn": ["Jane Doe"], "mail": ["jane.doe@example.com"]}, "dn": "cn=Jane Doe,dc=example,dc=com", } ) ) ) ] args = {"search_base": "dc=example,dc=com", "search_scope": "SUBTREE", "page": "2", "page_size": "50"} result = self.instance.entries_search_command(args) self.instance._get_auto_bind_value.assert_called_once() mock_create_entries_search_filter.assert_called_once_with(args) mock_get_search_attributes.assert_called_once_with("all") mock_connection.assert_called_once_with( self.instance._ldap_server, self.instance._username, self.instance._password, auto_bind=True ) assert len(result.outputs) == 1 assert result.outputs[0]["cn"] == ["Jane Doe"] assert result.outputs[0]["mail"] == ["jane.doe@example.com"] assert result.outputs[0]["dn"] == "cn=Jane Doe,dc=example,dc=com"