prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN 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 -->
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `plugins/doc_fragments/ldap.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 
  3 # Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>
  4 # Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>
  5 # Copyright: (c) 2017-2018 Keller Fuchs (@KellerFuchs) <kellerfuchs@hashbang.sh>
  6 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
  7 
  8 from __future__ import (absolute_import, division, print_function)
  9 __metaclass__ = type
  10 
  11 
  12 class ModuleDocFragment(object):
  13     # Standard LDAP documentation fragment
  14     DOCUMENTATION = r'''
  15 options:
  16   bind_dn:
  17     description:
  18       - A DN to bind with. If this is omitted, we'll try a SASL bind with the EXTERNAL mechanism.
  19       - If this is blank, we'll use an anonymous bind.
  20     type: str
  21   bind_pw:
  22     description:
  23       - The password to use with I(bind_dn).
  24     type: str
  25   dn:
  26     required: true
  27     description:
  28       - The DN of the entry to add or remove.
  29     type: str
  30   server_uri:
  31     description:
  32       - A URI to the LDAP server.
  33       - The default value lets the underlying LDAP client library look for a UNIX domain socket in its default location.
  34     type: str
  35     default: ldapi:///
  36   start_tls:
  37     description:
  38       - If true, we'll use the START_TLS LDAP extension.
  39     type: bool
  40     default: no
  41   validate_certs:
  42     description:
  43       - If set to C(no), SSL certificates will not be validated.
  44       - This should only be used on sites using self-signed certificates.
  45     type: bool
  46     default: yes
  47 '''
  48
  ```
  
  Path: `plugins/module_utils/ldap.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 
  3 # Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>
  4 # Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>
  5 # Copyright: (c) 2017-2018 Keller Fuchs (@KellerFuchs) <kellerfuchs@hashbang.sh>
  6 #
  7 # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
  8 
  9 from __future__ import absolute_import, division, print_function
  10 __metaclass__ = type
  11 
  12 import traceback
  13 from ansible.module_utils._text import to_native
  14 
  15 try:
  16     import ldap
  17     import ldap.sasl
  18 
  19     HAS_LDAP = True
  20 except ImportError:
  21     HAS_LDAP = False
  22 
  23 
  24 def gen_specs(**specs):
  25     specs.update({
  26         'bind_dn': dict(),
  27         'bind_pw': dict(default='', no_log=True),
  28         'dn': dict(required=True),
  29         'server_uri': dict(default='ldapi:///'),
  30         'start_tls': dict(default=False, type='bool'),
  31         'validate_certs': dict(default=True, type='bool'),
  32     })
  33 
  34     return specs
  35 
  36 
  37 class LdapGeneric(object):
  38     def __init__(self, module):
  39         # Shortcuts
  40         self.module = module
  41         self.bind_dn = self.module.params['bind_dn']
  42         self.bind_pw = self.module.params['bind_pw']
  43         self.dn = self.module.params['dn']
  44         self.server_uri = self.module.params['server_uri']
  45         self.start_tls = self.module.params['start_tls']
  46         self.verify_cert = self.module.params['validate_certs']
  47 
  48         # Establish connection
  49         self.connection = self._connect_to_ldap()
  50 
  51     def fail(self, msg, exn):
  52         self.module.fail_json(
  53             msg=msg,
  54             details=to_native(exn),
  55             exception=traceback.format_exc()
  56         )
  57 
  58     def _connect_to_ldap(self):
  59         if not self.verify_cert:
  60             ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
  61 
  62         connection = ldap.initialize(self.server_uri)
  63 
  64         if self.start_tls:
  65             try:
  66                 connection.start_tls_s()
  67             except ldap.LDAPError as e:
  68                 self.fail("Cannot start TLS.", e)
  69 
  70         try:
  71             if self.bind_dn is not None:
  72                 connection.simple_bind_s(self.bind_dn, self.bind_pw)
  73             else:
  74                 connection.sasl_interactive_bind_s('', ldap.sasl.external())
  75         except ldap.LDAPError as e:
  76             self.fail("Cannot bind to the server.", e)
  77 
  78         return connection
  79
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: 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 @@
   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 @@
         - 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 @@
           '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 @@
           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 @@
               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)
  
verification_info: {"golden_diff": "diff --git a/plugins/doc_fragments/ldap.py b/plugins/doc_fragments/ldap.py\n--- a/plugins/doc_fragments/ldap.py\n+++ b/plugins/doc_fragments/ldap.py\n@@ -15,7 +15,7 @@\n options:\n   bind_dn:\n     description:\n-      - A DN to bind with. If this is omitted, we'll try a SASL bind with the EXTERNAL mechanism.\n+      - A DN to bind with. If this is omitted, we'll try a SASL bind with the EXTERNAL mechanism as default.\n       - If this is blank, we'll use an anonymous bind.\n     type: str\n   bind_pw:\n@@ -44,4 +44,12 @@\n       - This should only be used on sites using self-signed certificates.\n     type: bool\n     default: yes\n+  sasl_class:\n+    description:\n+      - The class to use for SASL authentication.\n+      - possible choices are C(external), C(gssapi).\n+    type: str\n+    choices: ['external', 'gssapi']\n+    default: external\n+    version_added: \"2.0.0\"\n '''\ndiff --git a/plugins/module_utils/ldap.py b/plugins/module_utils/ldap.py\n--- a/plugins/module_utils/ldap.py\n+++ b/plugins/module_utils/ldap.py\n@@ -17,6 +17,11 @@\n     import ldap.sasl\n \n     HAS_LDAP = True\n+\n+    SASCL_CLASS = {\n+        'gssapi': ldap.sasl.gssapi,\n+        'external': ldap.sasl.external,\n+    }\n except ImportError:\n     HAS_LDAP = False\n \n@@ -29,6 +34,7 @@\n         'server_uri': dict(default='ldapi:///'),\n         'start_tls': dict(default=False, type='bool'),\n         'validate_certs': dict(default=True, type='bool'),\n+        'sasl_class': dict(choices=['external', 'gssapi'], default='external', type='str'),\n     })\n \n     return specs\n@@ -44,6 +50,7 @@\n         self.server_uri = self.module.params['server_uri']\n         self.start_tls = self.module.params['start_tls']\n         self.verify_cert = self.module.params['validate_certs']\n+        self.sasl_class = self.module.params['sasl_class']\n \n         # Establish connection\n         self.connection = self._connect_to_ldap()\n@@ -71,7 +78,8 @@\n             if self.bind_dn is not None:\n                 connection.simple_bind_s(self.bind_dn, self.bind_pw)\n             else:\n-                connection.sasl_interactive_bind_s('', ldap.sasl.external())\n+                klass = SASCL_CLASS.get(self.sasl_class, ldap.sasl.external)\n+                connection.sasl_interactive_bind_s('', klass())\n         except ldap.LDAPError as e:\n             self.fail(\"Cannot bind to the server.\", e)\n", "issue": "Support GSSAPI authentication for ldap_* module\n<!--- Verify first that your feature was not already discussed on GitHub -->\r\n<!--- Complete *all* sections as described, this form is processed automatically -->\r\n\r\n##### SUMMARY\r\n<!--- Describe the new feature/improvement briefly below -->\r\nAdd the attribute `sasl_class` with values `external` (default) or `gssapi` to `community.general/plugins/module_utils/ldap.py`\r\nto select the SASL authentication algorithm for python-ldap when user name and password are not provide. Currently, the ansible modules default to EXTERNAL.\r\n\r\n##### ISSUE TYPE\r\n- Feature Idea\r\n\r\n##### COMPONENT NAME\r\n<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->\r\nldap_attr\r\nldap_attrs\r\nldap_entry\r\nldap_passwd\r\nldap_search\r\n\r\n##### ADDITIONAL INFORMATION\r\n<!--- Describe how the feature would be used, why it is needed and what it would solve -->\r\nAdding 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. \r\n\r\n<!--- Paste example playbooks or commands between quotes below -->\r\n```yaml\r\n- name: admin group must exist\r\n  community.general.ldap_entry:\r\n    dn: CN=my_admin_group,OU=Groups,OU=Global,DC=dir,DC=example,DC=com\r\n    objectClass: group\r\n    server_uri: \"{{ ad.url }}\"\r\n    sasl_class: gssapi\r\n    state: present\r\n```\r\n\r\n<!--- HINT: You can also paste gist.github.com links for larger files -->\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>\n# Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>\n# Copyright: (c) 2017-2018 Keller Fuchs (@KellerFuchs) <kellerfuchs@hashbang.sh>\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import (absolute_import, division, print_function)\n__metaclass__ = type\n\n\nclass ModuleDocFragment(object):\n    # Standard LDAP documentation fragment\n    DOCUMENTATION = r'''\noptions:\n  bind_dn:\n    description:\n      - A DN to bind with. If this is omitted, we'll try a SASL bind with the EXTERNAL mechanism.\n      - If this is blank, we'll use an anonymous bind.\n    type: str\n  bind_pw:\n    description:\n      - The password to use with I(bind_dn).\n    type: str\n  dn:\n    required: true\n    description:\n      - The DN of the entry to add or remove.\n    type: str\n  server_uri:\n    description:\n      - A URI to the LDAP server.\n      - The default value lets the underlying LDAP client library look for a UNIX domain socket in its default location.\n    type: str\n    default: ldapi:///\n  start_tls:\n    description:\n      - If true, we'll use the START_TLS LDAP extension.\n    type: bool\n    default: no\n  validate_certs:\n    description:\n      - If set to C(no), SSL certificates will not be validated.\n      - This should only be used on sites using self-signed certificates.\n    type: bool\n    default: yes\n'''\n", "path": "plugins/doc_fragments/ldap.py"}, {"content": "# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>\n# Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>\n# Copyright: (c) 2017-2018 Keller Fuchs (@KellerFuchs) <kellerfuchs@hashbang.sh>\n#\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n\nimport traceback\nfrom ansible.module_utils._text import to_native\n\ntry:\n    import ldap\n    import ldap.sasl\n\n    HAS_LDAP = True\nexcept ImportError:\n    HAS_LDAP = False\n\n\ndef gen_specs(**specs):\n    specs.update({\n        'bind_dn': dict(),\n        'bind_pw': dict(default='', no_log=True),\n        'dn': dict(required=True),\n        'server_uri': dict(default='ldapi:///'),\n        'start_tls': dict(default=False, type='bool'),\n        'validate_certs': dict(default=True, type='bool'),\n    })\n\n    return specs\n\n\nclass LdapGeneric(object):\n    def __init__(self, module):\n        # Shortcuts\n        self.module = module\n        self.bind_dn = self.module.params['bind_dn']\n        self.bind_pw = self.module.params['bind_pw']\n        self.dn = self.module.params['dn']\n        self.server_uri = self.module.params['server_uri']\n        self.start_tls = self.module.params['start_tls']\n        self.verify_cert = self.module.params['validate_certs']\n\n        # Establish connection\n        self.connection = self._connect_to_ldap()\n\n    def fail(self, msg, exn):\n        self.module.fail_json(\n            msg=msg,\n            details=to_native(exn),\n            exception=traceback.format_exc()\n        )\n\n    def _connect_to_ldap(self):\n        if not self.verify_cert:\n            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)\n\n        connection = ldap.initialize(self.server_uri)\n\n        if self.start_tls:\n            try:\n                connection.start_tls_s()\n            except ldap.LDAPError as e:\n                self.fail(\"Cannot start TLS.\", e)\n\n        try:\n            if self.bind_dn is not None:\n                connection.simple_bind_s(self.bind_dn, self.bind_pw)\n            else:\n                connection.sasl_interactive_bind_s('', ldap.sasl.external())\n        except ldap.LDAPError as e:\n            self.fail(\"Cannot bind to the server.\", e)\n\n        return connection\n", "path": "plugins/module_utils/ldap.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>\n# Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>\n# Copyright: (c) 2017-2018 Keller Fuchs (@KellerFuchs) <kellerfuchs@hashbang.sh>\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import (absolute_import, division, print_function)\n__metaclass__ = type\n\n\nclass ModuleDocFragment(object):\n    # Standard LDAP documentation fragment\n    DOCUMENTATION = r'''\noptions:\n  bind_dn:\n    description:\n      - A DN to bind with. If this is omitted, we'll try a SASL bind with the EXTERNAL mechanism as default.\n      - If this is blank, we'll use an anonymous bind.\n    type: str\n  bind_pw:\n    description:\n      - The password to use with I(bind_dn).\n    type: str\n  dn:\n    required: true\n    description:\n      - The DN of the entry to add or remove.\n    type: str\n  server_uri:\n    description:\n      - A URI to the LDAP server.\n      - The default value lets the underlying LDAP client library look for a UNIX domain socket in its default location.\n    type: str\n    default: ldapi:///\n  start_tls:\n    description:\n      - If true, we'll use the START_TLS LDAP extension.\n    type: bool\n    default: no\n  validate_certs:\n    description:\n      - If set to C(no), SSL certificates will not be validated.\n      - This should only be used on sites using self-signed certificates.\n    type: bool\n    default: yes\n  sasl_class:\n    description:\n      - The class to use for SASL authentication.\n      - possible choices are C(external), C(gssapi).\n    type: str\n    choices: ['external', 'gssapi']\n    default: external\n    version_added: \"2.0.0\"\n'''\n", "path": "plugins/doc_fragments/ldap.py"}, {"content": "# -*- coding: utf-8 -*-\n\n# Copyright: (c) 2016, Peter Sagerson <psagers@ignorare.net>\n# Copyright: (c) 2016, Jiri Tyr <jiri.tyr@gmail.com>\n# Copyright: (c) 2017-2018 Keller Fuchs (@KellerFuchs) <kellerfuchs@hashbang.sh>\n#\n# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)\n\nfrom __future__ import absolute_import, division, print_function\n__metaclass__ = type\n\nimport traceback\nfrom ansible.module_utils._text import to_native\n\ntry:\n    import ldap\n    import ldap.sasl\n\n    HAS_LDAP = True\n\n    SASCL_CLASS = {\n        'gssapi': ldap.sasl.gssapi,\n        'external': ldap.sasl.external,\n    }\nexcept ImportError:\n    HAS_LDAP = False\n\n\ndef gen_specs(**specs):\n    specs.update({\n        'bind_dn': dict(),\n        'bind_pw': dict(default='', no_log=True),\n        'dn': dict(required=True),\n        'server_uri': dict(default='ldapi:///'),\n        'start_tls': dict(default=False, type='bool'),\n        'validate_certs': dict(default=True, type='bool'),\n        'sasl_class': dict(choices=['external', 'gssapi'], default='external', type='str'),\n    })\n\n    return specs\n\n\nclass LdapGeneric(object):\n    def __init__(self, module):\n        # Shortcuts\n        self.module = module\n        self.bind_dn = self.module.params['bind_dn']\n        self.bind_pw = self.module.params['bind_pw']\n        self.dn = self.module.params['dn']\n        self.server_uri = self.module.params['server_uri']\n        self.start_tls = self.module.params['start_tls']\n        self.verify_cert = self.module.params['validate_certs']\n        self.sasl_class = self.module.params['sasl_class']\n\n        # Establish connection\n        self.connection = self._connect_to_ldap()\n\n    def fail(self, msg, exn):\n        self.module.fail_json(\n            msg=msg,\n            details=to_native(exn),\n            exception=traceback.format_exc()\n        )\n\n    def _connect_to_ldap(self):\n        if not self.verify_cert:\n            ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)\n\n        connection = ldap.initialize(self.server_uri)\n\n        if self.start_tls:\n            try:\n                connection.start_tls_s()\n            except ldap.LDAPError as e:\n                self.fail(\"Cannot start TLS.\", e)\n\n        try:\n            if self.bind_dn is not None:\n                connection.simple_bind_s(self.bind_dn, self.bind_pw)\n            else:\n                klass = SASCL_CLASS.get(self.sasl_class, ldap.sasl.external)\n                connection.sasl_interactive_bind_s('', klass())\n        except ldap.LDAPError as e:\n            self.fail(\"Cannot bind to the server.\", e)\n\n        return connection\n", "path": "plugins/module_utils/ldap.py"}]}