Skip to content

feat(ldapobject): add asynchronous SASL bind support - #668

Open
spaceone wants to merge 1 commit into
mainfrom
feat/asynchronous-sasl-binds
Open

spaceone wants to merge 1 commit into
mainfrom
feat/asynchronous-sasl-binds

Conversation

@spaceone

@spaceone spaceone commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Introduce LDAPObject.sasl_bind() for asynchronous SASL bind using libldap ldap_sasl_bind().

Issue: #577

@spaceone
spaceone changed the base branch from ci/format-and-linting to main October 9, 2026 19:26
Introduce `LDAPObject.sasl_bind()` for asynchronous SASL bind using
libldap `ldap_sasl_bind()`.

Issue: #577
@spaceone
spaceone force-pushed the feat/asynchronous-sasl-binds branch from a05d3e2 to 9f2c55b Compare October 9, 2026 19:28
@spaceone
spaceone requested a review from mistotebe October 9, 2026 19:28
Comment thread Doc/reference/ldap.rst
.. py:method:: LDAPObject.sasl_bind(dn, mechanism, cred[, serverctrls=None [, clientctrls=None]]) -> int

Send a SASL bind request asynchronously and return its message ID. Use
:py:meth:`result()` with the message ID to retrieve the bind response.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentionally limited to exchanges that do not need server SASL tokens, such as EXTERNAL? In a working CRAM-MD5 setup, result()/result4() consume the response and raise SASL_BIND_IN_PROGRESS without the challenge.

I'd leave @mistotebe to check this as he's a bit more proficient in the area, I think.
I'm not sure if we just should document the intended limit, or expose serverSaslCreds before freeing the response if challenge/response is in scope...

@spaceone

Copy link
Copy Markdown
Contributor Author

storage for after-pytest-migration:

diff --git Tests/test_cext.py Tests/test_cext.py
index 4508fa5e..4c416877 100644
--- Tests/test_cext.py
+++ Tests/test_cext.py
@@ -833,6 +833,8 @@ class TestLdapCExtension(SlapdTestCase):
         self.check_invalid_controls(l.extop, 'oid', 'value')
         if hasattr(l, 'sasl_bind_s'):
             self.check_invalid_controls(l.sasl_bind_s, 'dn', 'MECH', 'CRED')
+        if hasattr(l, 'sasl_bind'):
+            self.check_invalid_controls(l.sasl_bind, 'dn', 'MECH', 'CRED')
         if hasattr(l, 'sasl_interactive_bind_s'):
             self.check_invalid_controls(l.sasl_interactive_bind_s, 'who', 'SASLObject', post=(1,))
         self.check_invalid_controls(l.unbind_ext)
diff --git Tests/test_ldap_sasl.py Tests/test_ldap_sasl.py
index d797807f..6f3b0ec6 100644
--- Tests/test_ldap_sasl.py
+++ Tests/test_ldap_sasl.py
@@ -73,6 +73,15 @@ class TestSasl(SlapdTestCase):
         ldap_conn.sasl_interactive_bind_s('', auth)
         assert ldap_conn.whoami_s().lower() == f'dn:{self.server.root_dn.lower()}'
 
+    @requires_ldapi()
+    def test_external_ldapi_async(self):
+        ldap_conn = self.ldap_object_class(self.server.ldapi_uri)
+
+        msgid = ldap_conn.sasl_bind('', 'EXTERNAL', '')
+        assert isinstance(msgid, int)
+        ldap_conn.result(msgid, all=1)
+        assert ldap_conn.whoami_s().lower() == f'dn:{self.server.root_dn.lower()}'
+
     @requires_tls()
     def test_external_tlscert(self):
         ldap_conn = self.ldap_object_class(self.server.ldap_uri)

@spaceone spaceone added this to the 3.5.0 milestone Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants