python-ldap
python-ldap provides a low-level API for implementing LDAP clients, wrapping the OpenLDAP C library. It supports LDAPv3 and offers comprehensive features for interacting with LDAP directories. The current version is 3.4.5, with minor releases for bug fixes and security updates, and major versions typically bringing Python version compatibility and API changes.
Warnings
- breaking python-ldap 3.x is Python 3 ONLY. Version 3.0.0 dropped support for Python 2.x entirely. Attempts to use python-ldap 3.x in a Python 2 environment will fail.
- breaking All string data in python-ldap 3.x is handled as bytes. Inputs for DNs, filters, attribute names/values, and outputs from search operations are bytes. This is a fundamental change from python-ldap 2.x and is a common source of `TypeError` or unexpected behavior.
- deprecated The `ldap.open()` and `ldap.init()` functions were deprecated and completely removed in version 3.1.0.
- deprecated The `OPT_X_TLS` option was removed in 3.4.2, brought back as deprecated in 3.4.3, and is slated for final removal in version 3.5.0. Relying on this option may cause future breaking changes.
- gotcha The return type of `LDAPObject.compare_s()` and `LDAPObject.compare_ext_s()` changed from an integer (0 or 1) to a boolean (`False` or `True`) in version 3.1.0. Code expecting an integer return value may behave incorrectly.
- gotcha Versions prior to 3.4.5 had security vulnerabilities (CVE-2025-61911, CVE-2025-61912) related to improper escaping of input in `ldap.filter.escape_filter_chars` and `ldap.dn.escape_dn_chars`.
Install
-
pip install python-ldap -
sudo apt-get install libldap2-dev libsasl2-dev # Debian/Ubuntu sudo dnf install openldap-devel cyrus-sasl-devel # Fedora brew install openldap # macOS pip install python-ldap
Imports
- ldap
import ldap
- modlist
from ldap import modlist
- ldap.dn
import ldap.dn
- ldap.filter
import ldap.filter
Quickstart
import ldap
import os
# Configure LDAP server details
LDAP_SERVER_URI = os.environ.get('LDAP_SERVER_URI', 'ldap://localhost:389')
LDAP_BIND_DN = os.environ.get('LDAP_BIND_DN', 'cn=admin,dc=example,dc=org')
LDAP_BIND_PASSWORD = os.environ.get('LDAP_BIND_PASSWORD', 'adminpassword')
LDAP_SEARCH_BASE = os.environ.get('LDAP_SEARCH_BASE', 'dc=example,dc=org')
LDAP_SEARCH_FILTER = os.environ.get('LDAP_SEARCH_FILTER', '(objectClass=person)')
LDAP_SEARCH_ATTRIBUTES = ['cn', 'mail']
try:
# Initialize LDAP connection
l = ldap.initialize(LDAP_SERVER_URI)
l.set_option(ldap.OPT_REFERRALS, 0)
l.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
# Bind to the directory
l.simple_bind_s(LDAP_BIND_DN.encode('utf-8'), LDAP_BIND_PASSWORD.encode('utf-8'))
print(f"Successfully bound to {LDAP_SERVER_URI}")
# Search the directory
result_id = l.search(
LDAP_SEARCH_BASE.encode('utf-8'),
ldap.SCOPE_SUBTREE,
LDAP_SEARCH_FILTER.encode('utf-8'),
LDAP_SEARCH_ATTRIBUTES
)
results = []
while True:
result_type, result_data = l.result(result_id, 0)
if not result_data:
break
if result_type == ldap.RES_SEARCH_ENTRY:
for dn, entry in result_data:
results.append((dn.decode('utf-8'), {k.decode('utf-8'): [v.decode('utf-8') for v in val] for k, val in entry.items()}))
print(f"Found {len(results)} entries:")
for dn, entry in results:
print(f"DN: {dn}")
print(f" CN: {entry.get('cn')}")
print(f" Mail: {entry.get('mail')}")
except ldap.SERVER_DOWN as e:
print(f"LDAP server down or connection failed: {e}")
except ldap.LDAPError as e:
print(f"LDAP Error: {e}")
finally:
# Unbind from the directory
if 'l' in locals() and l:
try:
l.unbind_s()
print("Unbound from LDAP server.")
except ldap.LDAPError as e:
print(f"Error during unbind: {e}")