mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
plugins/modules/ldap_search: Add support for multipage searches (#6648)
* Add more integration tests for ldap_search * Add new page_size option to ldap_search * Add changelog fragment * Apply suggestions from code review Co-authored-by: Felix Fontein <felix@fontein.de> * Simplify if statement to reduce negatives * Apply suggestions from code review Co-authored-by: Felix Fontein <felix@fontein.de> --------- Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
f3ecf4c7f8
commit
8801463575
6 changed files with 118 additions and 15 deletions
2
changelogs/fragments/6648_ldap_search_page_size.yml
Normal file
2
changelogs/fragments/6648_ldap_search_page_size.yml
Normal file
|
@ -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).
|
|
@ -61,6 +61,14 @@ options:
|
||||||
description:
|
description:
|
||||||
- Set to C(true) to return the full attribute schema of entries, not
|
- Set to C(true) to return the full attribute schema of entries, not
|
||||||
their attribute values. Overrides I(attrs) when provided.
|
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:
|
base64_attributes:
|
||||||
description:
|
description:
|
||||||
- If provided, all attribute values returned that are listed in this option
|
- If provided, all attribute values returned that are listed in this option
|
||||||
|
@ -133,6 +141,7 @@ def main():
|
||||||
filter=dict(type='str', default='(objectClass=*)'),
|
filter=dict(type='str', default='(objectClass=*)'),
|
||||||
attrs=dict(type='list', elements='str'),
|
attrs=dict(type='list', elements='str'),
|
||||||
schema=dict(type='bool', default=False),
|
schema=dict(type='bool', default=False),
|
||||||
|
page_size=dict(type='int', default=0),
|
||||||
base64_attributes=dict(type='list', elements='str'),
|
base64_attributes=dict(type='list', elements='str'),
|
||||||
),
|
),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
@ -181,6 +190,7 @@ class LdapSearch(LdapGeneric):
|
||||||
|
|
||||||
self.filterstr = self.module.params['filter']
|
self.filterstr = self.module.params['filter']
|
||||||
self.attrlist = []
|
self.attrlist = []
|
||||||
|
self.page_size = self.module.params['page_size']
|
||||||
self._load_scope()
|
self._load_scope()
|
||||||
self._load_attrs()
|
self._load_attrs()
|
||||||
self._load_schema()
|
self._load_schema()
|
||||||
|
@ -210,21 +220,31 @@ class LdapSearch(LdapGeneric):
|
||||||
self.module.exit_json(changed=False, results=results)
|
self.module.exit_json(changed=False, results=results)
|
||||||
|
|
||||||
def perform_search(self):
|
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:
|
try:
|
||||||
results = self.connection.search_s(
|
while True:
|
||||||
|
response = self.connection.search_ext(
|
||||||
self.dn,
|
self.dn,
|
||||||
self.scope,
|
self.scope,
|
||||||
filterstr=self.filterstr,
|
filterstr=self.filterstr,
|
||||||
attrlist=self.attrlist,
|
attrlist=self.attrlist,
|
||||||
attrsonly=self.attrsonly
|
attrsonly=self.attrsonly,
|
||||||
|
serverctrls=controls,
|
||||||
)
|
)
|
||||||
ldap_entries = []
|
rtype, results, rmsgid, serverctrls = self.connection.result3(response)
|
||||||
for result in results:
|
for result in results:
|
||||||
if isinstance(result[1], dict):
|
if isinstance(result[1], dict):
|
||||||
if self.schema:
|
if self.schema:
|
||||||
ldap_entries.append(dict(dn=result[0], attrs=list(result[1].keys())))
|
ldap_entries.append(dict(dn=result[0], attrs=list(result[1].keys())))
|
||||||
else:
|
else:
|
||||||
ldap_entries.append(_extract_entry(result[0], result[1], self._base64_attributes))
|
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
|
return ldap_entries
|
||||||
except ldap.NO_SUCH_OBJECT:
|
except ldap.NO_SUCH_OBJECT:
|
||||||
self.module.fail_json(msg="Base not found: {0}".format(self.dn))
|
self.module.fail_json(msg="Base not found: {0}".format(self.dn))
|
||||||
|
|
|
@ -23,3 +23,17 @@
|
||||||
- output is not failed
|
- output is not failed
|
||||||
- output.results | length == 1
|
- output.results | length == 1
|
||||||
- output.results.0.displayName == "LDAP Test"
|
- 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
|
||||||
|
|
24
tests/integration/targets/ldap_search/tasks/tests/pages.yml
Normal file
24
tests/integration/targets/ldap_search/tasks/tests/pages.yml
Normal file
|
@ -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
|
25
tests/integration/targets/ldap_search/tasks/tests/schema.yml
Normal file
25
tests/integration/targets/ldap_search/tasks/tests/schema.yml
Normal file
|
@ -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 }}"
|
|
@ -21,3 +21,21 @@ displayName: LDAP Test
|
||||||
userPassword: test1pass!
|
userPassword: test1pass!
|
||||||
mail: ldap.test@example.com
|
mail: ldap.test@example.com
|
||||||
sn: Test
|
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
|
||||||
|
|
Loading…
Reference in a new issue