Repository: ansible-collections/community.general

Issue:
Support GSSAPI authentication for ldap_* module
<!--- Verify first that your feature was not already discussed on GitHub -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Describe the new feature/improvement briefly below -->
Add the attribute `sasl_class` with values `external` (default) or `gssapi` to `community.general/plugins/module_utils/ldap.py`
to select the SASL authentication algorithm for python-ldap when user name and password are not provide. Currently, the ansible modules default to EXTERNAL.

##### ISSUE TYPE
- Feature Idea

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->
ldap_attr
ldap_attrs
ldap_entry
ldap_passwd
ldap_search

##### ADDITIONAL INFORMATION
<!--- Describe how the feature would be used, why it is needed and what it would solve -->
Adding the attribute `sasl_class` would allow to switch the default SASL authentication mechanism from `EXTERNAL` to `GSSAPI` which would help windows-based shops to transparently authenticate to Active Directory and use the LDAP modules to query and adjust their AD servers even from non-Windows servers. 

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: admin group must exist
  community.general.ldap_entry:
    dn: CN=my_admin_group,OU=Groups,OU=Global,DC=dir,DC=example,DC=com
    objectClass: group
    server_uri: "{{ ad.url }}"
    sasl_class: gssapi
    state: present
```

<!--- HINT: You can also paste gist.github.com links for larger files -->



Discussion:
Files identified in the description:
None

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
!component ldap_entry
Files identified in the description:
* [`plugins/modules/net_tools/ldap/ldap_attr.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/net_tools/ldap/ldap_attr.py)
* [`plugins/modules/net_tools/ldap/ldap_attrs.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/net_tools/ldap/ldap_attrs.py)
* [`plugins/modules/net_tools/ldap/ldap_entry.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/net_tools/ldap/ldap_entry.py)
* [`plugins/modules/net_tools/ldap/ldap_passwd.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/net_tools/ldap/ldap_passwd.py)
* [`plugins/modules/net_tools/ldap/ldap_search.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/net_tools/ldap/ldap_search.py)

If these files are inaccurate, 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/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @KellerFuchs @drybjed @eryx12o45 @jtyr @nerzhul @noles
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/plugins/doc_fragments/ldap.py b/plugins/doc_fragments/ldap.py
--- a/plugins/doc_fragments/ldap.py
+++ b/plugins/doc_fragments/ldap.py
@@ -15,7 +15,7 @@ class ModuleDocFragment(object):
 options:
   bind_dn:
     description:
-      - A DN to bind with. If this is omitted, we'll try a SASL bind with the EXTERNAL mechanism.
+      - A DN to bind with. If this is omitted, we'll try a SASL bind with the EXTERNAL mechanism as default.
       - If this is blank, we'll use an anonymous bind.
     type: str
   bind_pw:
@@ -44,4 +44,12 @@ class ModuleDocFragment(object):
       - This should only be used on sites using self-signed certificates.
     type: bool
     default: yes
+  sasl_class:
+    description:
+      - The class to use for SASL authentication.
+      - possible choices are C(external), C(gssapi).
+    type: str
+    choices: ['external', 'gssapi']
+    default: external
+    version_added: "2.0.0"
 '''
diff --git a/plugins/module_utils/ldap.py b/plugins/module_utils/ldap.py
--- a/plugins/module_utils/ldap.py
+++ b/plugins/module_utils/ldap.py
@@ -17,6 +17,11 @@
     import ldap.sasl
 
     HAS_LDAP = True
+
+    SASCL_CLASS = {
+        'gssapi': ldap.sasl.gssapi,
+        'external': ldap.sasl.external,
+    }
 except ImportError:
     HAS_LDAP = False
 
@@ -29,6 +34,7 @@ def gen_specs(**specs):
         'server_uri': dict(default='ldapi:///'),
         'start_tls': dict(default=False, type='bool'),
         'validate_certs': dict(default=True, type='bool'),
+        'sasl_class': dict(choices=['external', 'gssapi'], default='external', type='str'),
     })
 
     return specs
@@ -44,6 +50,7 @@ def __init__(self, module):
         self.server_uri = self.module.params['server_uri']
         self.start_tls = self.module.params['start_tls']
         self.verify_cert = self.module.params['validate_certs']
+        self.sasl_class = self.module.params['sasl_class']
 
         # Establish connection
         self.connection = self._connect_to_ldap()
@@ -71,7 +78,8 @@ def _connect_to_ldap(self):
             if self.bind_dn is not None:
                 connection.simple_bind_s(self.bind_dn, self.bind_pw)
             else:
-                connection.sasl_interactive_bind_s('', ldap.sasl.external())
+                klass = SASCL_CLASS.get(self.sasl_class, ldap.sasl.external)
+                connection.sasl_interactive_bind_s('', klass())
         except ldap.LDAPError as e:
             self.fail("Cannot bind to the server.", e)
 


Test patch: