diff --git a/changelogs/fragments/6648_ldap_search_page_size.yml b/changelogs/fragments/6648_ldap_search_page_size.yml new file mode 100644 index 0000000000..361186db97 --- /dev/null +++ b/changelogs/fragments/6648_ldap_search_page_size.yml @@ -0,0 +1,2 @@ +minor_changes: + - ldap_search - add a new ``page_size`` option to enable paged searches (https://github.com/ansible-collections/community.general/pull/6648). diff --git a/plugins/modules/ldap_search.py b/plugins/modules/ldap_search.py index c6dbac4bf9..d8fa2efd07 100644 --- a/plugins/modules/ldap_search.py +++ b/plugins/modules/ldap_search.py @@ -61,6 +61,14 @@ options: description: - Set to C(true) to return the full attribute schema of entries, not their attribute values. Overrides I(attrs) when provided. + page_size: + default: 0 + type: int + description: + - The page size when performing a simple paged result search (RFC 2696). + This setting can be tuned to reduce issues with timeouts and server limits. + - Setting the page size to V(0) (default) disables paged searching. + version_added: 7.1.0 base64_attributes: description: - If provided, all attribute values returned that are listed in this option @@ -133,6 +141,7 @@ def main(): filter=dict(type='str', default='(objectClass=*)'), attrs=dict(type='list', elements='str'), schema=dict(type='bool', default=False), + page_size=dict(type='int', default=0), base64_attributes=dict(type='list', elements='str'), ), supports_check_mode=True, @@ -181,6 +190,7 @@ class LdapSearch(LdapGeneric): self.filterstr = self.module.params['filter'] self.attrlist = [] + self.page_size = self.module.params['page_size'] self._load_scope() self._load_attrs() self._load_schema() @@ -210,22 +220,32 @@ class LdapSearch(LdapGeneric): self.module.exit_json(changed=False, results=results) def perform_search(self): + ldap_entries = [] + controls = [] + if self.page_size > 0: + controls.append(ldap.controls.libldap.SimplePagedResultsControl(True, size=self.page_size, cookie='')) try: - results = self.connection.search_s( - self.dn, - self.scope, - filterstr=self.filterstr, - attrlist=self.attrlist, - attrsonly=self.attrsonly - ) - ldap_entries = [] - for result in results: - if isinstance(result[1], dict): - if self.schema: - ldap_entries.append(dict(dn=result[0], attrs=list(result[1].keys()))) - else: - ldap_entries.append(_extract_entry(result[0], result[1], self._base64_attributes)) - return ldap_entries + while True: + response = self.connection.search_ext( + self.dn, + self.scope, + filterstr=self.filterstr, + attrlist=self.attrlist, + attrsonly=self.attrsonly, + serverctrls=controls, + ) + rtype, results, rmsgid, serverctrls = self.connection.result3(response) + for result in results: + if isinstance(result[1], dict): + if self.schema: + ldap_entries.append(dict(dn=result[0], attrs=list(result[1].keys()))) + else: + ldap_entries.append(_extract_entry(result[0], result[1], self._base64_attributes)) + cookies = [c.cookie for c in serverctrls if c.controlType == ldap.controls.libldap.SimplePagedResultsControl.controlType] + if self.page_size > 0 and cookies and cookies[0]: + controls[0].cookie = cookies[0] + else: + return ldap_entries except ldap.NO_SUCH_OBJECT: self.module.fail_json(msg="Base not found: {0}".format(self.dn)) diff --git a/tests/integration/targets/ldap_search/tasks/tests/basic.yml b/tests/integration/targets/ldap_search/tasks/tests/basic.yml index 36d245d396..11e5d6562c 100644 --- a/tests/integration/targets/ldap_search/tasks/tests/basic.yml +++ b/tests/integration/targets/ldap_search/tasks/tests/basic.yml @@ -23,3 +23,17 @@ - output is not failed - output.results | length == 1 - output.results.0.displayName == "LDAP Test" + +- name: Test simple search for a user with no results + ldap_search: + dn: "ou=users,dc=example,dc=com" + scope: "onelevel" + filter: "(uid=nonexistent)" + ignore_errors: true + register: output + +- name: assert that the output is empty + assert: + that: + - output is not failed + - output.results | length == 0 diff --git a/tests/integration/targets/ldap_search/tasks/tests/pages.yml b/tests/integration/targets/ldap_search/tasks/tests/pages.yml new file mode 100644 index 0000000000..32575854ba --- /dev/null +++ b/tests/integration/targets/ldap_search/tasks/tests/pages.yml @@ -0,0 +1,24 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- debug: + msg: Running tests/pages.yml + +#################################################################### +## Search ########################################################## +#################################################################### +- name: Test paged search for all users + ldap_search: + dn: "ou=users,dc=example,dc=com" + scope: "onelevel" + page_size: 1 + ignore_errors: true + register: output + +- name: assert that the right number of results are returned + assert: + that: + - output is not failed + - output.results | length == 2 diff --git a/tests/integration/targets/ldap_search/tasks/tests/schema.yml b/tests/integration/targets/ldap_search/tasks/tests/schema.yml new file mode 100644 index 0000000000..892eac3cb3 --- /dev/null +++ b/tests/integration/targets/ldap_search/tasks/tests/schema.yml @@ -0,0 +1,25 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- debug: + msg: Running tests/schema.yml + +#################################################################### +## Search ########################################################## +#################################################################### +- name: Test for ldap schema output + ldap_search: + dn: "ou=users,dc=example,dc=com" + scope: "onelevel" + schema: true + ignore_errors: true + register: output + +- name: Assert that the schema output is correct + assert: + that: + - output is not failed + - output.results | length >= 1 + - "{{ 'displayName' in output.results.0.attrs }}" diff --git a/tests/integration/targets/setup_openldap/files/initial_config.ldif b/tests/integration/targets/setup_openldap/files/initial_config.ldif index 6f2c940c15..cb21f2cfdb 100644 --- a/tests/integration/targets/setup_openldap/files/initial_config.ldif +++ b/tests/integration/targets/setup_openldap/files/initial_config.ldif @@ -21,3 +21,21 @@ displayName: LDAP Test userPassword: test1pass! mail: ldap.test@example.com sn: Test + +dn: uid=second,ou=users,dc=example,dc=com +uid: second +uidNumber: 1112 +gidNUmber: 102 +objectClass: top +objectClass: posixAccount +objectClass: shadowAccount +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +loginShell: /bin/sh +homeDirectory: /home/second +cn: Second Test +gecos: Second Test +displayName: Second Test +mail: second.test@example.com +sn: Test