Repository: ansible-collections/community.general

Issue:
ldap_search still crashing with surrogates not allowed in 7.4.0
### Summary

This is a followup on issue #5704 which was supposedly fixed by PR #6475. This PR apparently didn't fix it because `_normalize_string` only operates on string values, but `python-ldap` always returns `bytes` values by design. <https://www.python-ldap.org/en/python-ldap-3.4.3/bytes_mode.html>

https://github.com/ansible-collections/community.general/blob/1beb38ceff80b9e1d1437cab2ba27deff6a73124/plugins/modules/ldap_search.py#L161-L162

`_normalize_string` always returns `val` unchanged as it is never an instance of `six.string_types`.

Thus `base64_attributes` has no effect, and additionally no output is converted to UTF-8, nor does it have illegal characters replaced.

As a consequence any schema containing objectSid or objectGUID (which contain binary data) will probably crash on search. There doesn't appear to be any workaround for this, except to specify every attribute you want explicitly, omitting `objectSid` and `objectGUID`.

### Issue Type

Bug Report

### Component Name

ldap_search

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.14.2]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.11/site-packages/ansible
  ansible collection location = /home/user/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.11.2 (main, Jun 14 2023, 13:00:29) [GCC 8.5.0 20210514 (Red Hat 8.5.0-18.0.2)] (/usr/bin/python3.11)
  jinja version = 3.1.2
  libyaml = True
```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
Collection        Version
----------------- -------
community.general 7.4.0
```

### Configuration

```console (paste below)
$ ansible-config dump --only-changed
CONFIG_FILE() = /etc/ansible/ansible.cfg
DEFAULT_REMOTE_USER(env: ANSIBLE_REMOTE_USER) = <redacted>
```


### OS / Environment

RHEL 8

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
    - name: Search entry
      community.general.ldap_search:
        dn: 'CN=username,{{ lcl_base }}'
        scope: base
        bind_dn: '{{ ldap_bind_dn }}'
        bind_pw: '{{ ldap_bind_pw }}'
        server_uri: '{{ ldap_admin_uri }}'
        base64_attributes:
          - '*'
          - objectGUID
          - objectSid
```


### Expected Results

Successfully retrieve attributes of DN

### Actual Results

Crashes with 'surrogates not allowed' due to binary data in `objectGUID` and `objectSid`. `base64_attributes` has no effect, even on `*`. Additionally even when `base64_attributes` is omitted, any UTF-8 should be encoded with 'replace' but that doesn't happen either.

```console (paste below)
TASK [Search entry] ******************************************************************************************
[WARNING]: Failure using method (v2_runner_on_ok) in callback plugin
(<ansible.plugins.callback.default.CallbackModule object at 0x7fb0d8c1d750>): 'utf-8' codec can't encode character
'\udcc8' in position 3131: surrogates not allowed
Callback Exception:
  File "/usr/lib/python3.11/site-packages/ansible/executor/task_queue_manager.py", line 450, in send_callback
    method(*new_args, **kwargs)
   File "/usr/lib/python3.11/site-packages/ansible/plugins/callback/default.py", line 106, in v2_runner_on_ok
    self._display.display(msg, color=color)
   File "/usr/lib/python3.11/site-packages/ansible/utils/display.py", line 289, in display
    fileobj.write(msg2)
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/ldap_search.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/ldap_search.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @eryx12o45 @jtyr
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/modules/ldap_search.py b/plugins/modules/ldap_search.py
--- a/plugins/modules/ldap_search.py
+++ b/plugins/modules/ldap_search.py
@@ -120,7 +120,7 @@
 
 from ansible.module_utils.basic import AnsibleModule, missing_required_lib
 from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
-from ansible.module_utils.six import string_types, text_type
+from ansible.module_utils.six import binary_type, string_types, text_type
 from ansible_collections.community.general.plugins.module_utils.ldap import LdapGeneric, gen_specs, ldap_required_together
 
 LDAP_IMP_ERR = None
@@ -159,7 +159,7 @@ def main():
 
 
 def _normalize_string(val, convert_to_base64):
-    if isinstance(val, string_types):
+    if isinstance(val, (string_types, binary_type)):
         if isinstance(val, text_type):
             val = to_bytes(val, encoding='utf-8')
         if convert_to_base64:


Test patch: