Moved LDAP configuration entry to BACKENDS.LDAP

This commit is contained in:
Éloi Rivard 2023-04-10 20:31:54 +02:00
parent cc45ed4be9
commit e2b96af1ee
17 changed files with 97 additions and 71 deletions

View file

@ -11,11 +11,12 @@ Changed
- Renamed user model attributes to match SCIM naming convention. :pr:`123` - Renamed user model attributes to match SCIM naming convention. :pr:`123`
- Moved OIDC related configuration entries in ``OIDC`` - Moved OIDC related configuration entries in ``OIDC``
- Moved ``LDAP`` configuration entry to ``BACKENDS.LDAP``
Fixed Fixed
***** *****
- ``OIDC.JWT.MAPPING`` is really optional now. - ``OIDC.JWT.MAPPING`` configuration entry is really optional now.
[0.0.24] - 2023-04-07 [0.0.24] - 2023-04-07
===================== =====================

View file

@ -25,7 +25,9 @@ def profile_hash(*args):
def login_placeholder(): def login_placeholder():
user_filter = current_app.config["LDAP"].get("USER_FILTER", User.DEFAULT_FILTER) user_filter = current_app.config["BACKENDS"]["LDAP"].get(
"USER_FILTER", User.DEFAULT_FILTER
)
placeholders = [] placeholders = []
if "cn={login}" in user_filter: if "cn={login}" in user_filter:

View file

@ -55,7 +55,7 @@ SECRET_KEY = "change me before you go in production"
# written in the standard error output. # written in the standard error output.
# PATH = "" # PATH = ""
[LDAP] [BACKENDS.LDAP]
URI = "ldap://ldap" URI = "ldap://ldap"
ROOT_DN = "dc=mydomain,dc=tld" ROOT_DN = "dc=mydomain,dc=tld"
BIND_DN = "cn=admin,dc=mydomain,dc=tld" BIND_DN = "cn=admin,dc=mydomain,dc=tld"

View file

@ -45,7 +45,7 @@ class User(LDAPObject):
def get_from_login(cls, login=None, **kwargs): def get_from_login(cls, login=None, **kwargs):
filter = ( filter = (
( (
current_app.config["LDAP"] current_app.config["BACKENDS"]["LDAP"]
.get("USER_FILTER", User.DEFAULT_FILTER) .get("USER_FILTER", User.DEFAULT_FILTER)
.format(login=ldap.filter.escape_filter_chars(login)) .format(login=ldap.filter.escape_filter_chars(login))
) )
@ -65,7 +65,7 @@ class User(LDAPObject):
def load_groups(self): def load_groups(self):
group_filter = ( group_filter = (
current_app.config["LDAP"] current_app.config["BACKENDS"]["LDAP"]
.get("GROUP_USER_FILTER", Group.DEFAULT_USER_FILTER) .get("GROUP_USER_FILTER", Group.DEFAULT_USER_FILTER)
.format(user=self) .format(user=self)
) )
@ -107,10 +107,11 @@ class User(LDAPObject):
return bool(self.password) return bool(self.password)
def check_password(self, password): def check_password(self, password):
conn = ldap.initialize(current_app.config["LDAP"]["URI"]) conn = ldap.initialize(current_app.config["BACKENDS"]["LDAP"]["URI"])
conn.set_option( conn.set_option(
ldap.OPT_NETWORK_TIMEOUT, current_app.config["LDAP"].get("TIMEOUT") ldap.OPT_NETWORK_TIMEOUT,
current_app.config["BACKENDS"]["LDAP"].get("TIMEOUT"),
) )
try: try:
@ -213,7 +214,7 @@ class Group(LDAPObject):
@property @property
def display_name(self): def display_name(self):
attribute = current_app.config["LDAP"].get( attribute = current_app.config["BACKENDS"]["LDAP"].get(
"GROUP_NAME_ATTRIBUTE", Group.DEFAULT_NAME_ATTRIBUTE "GROUP_NAME_ATTRIBUTE", Group.DEFAULT_NAME_ATTRIBUTE
) )
return self[attribute][0] return self[attribute][0]

View file

@ -14,28 +14,34 @@ def setup_ldap_models(config):
from canaille.core.models import Group from canaille.core.models import Group
from canaille.core.models import User from canaille.core.models import User
LDAPObject.root_dn = config["LDAP"]["ROOT_DN"] LDAPObject.root_dn = config["BACKENDS"]["LDAP"]["ROOT_DN"]
user_base = config["LDAP"]["USER_BASE"].replace(f',{config["LDAP"]["ROOT_DN"]}', "") user_base = config["BACKENDS"]["LDAP"]["USER_BASE"].replace(
f',{config["BACKENDS"]["LDAP"]["ROOT_DN"]}', ""
)
User.base = user_base User.base = user_base
User.rdn_attribute = config["LDAP"].get( User.rdn_attribute = config["BACKENDS"]["LDAP"].get(
"USER_ID_ATTRIBUTE", User.DEFAULT_ID_ATTRIBUTE "USER_ID_ATTRIBUTE", User.DEFAULT_ID_ATTRIBUTE
) )
object_class = config["LDAP"].get("USER_CLASS", User.DEFAULT_OBJECT_CLASS) object_class = config["BACKENDS"]["LDAP"].get(
"USER_CLASS", User.DEFAULT_OBJECT_CLASS
)
User.ldap_object_class = ( User.ldap_object_class = (
object_class if isinstance(object_class, list) else [object_class] object_class if isinstance(object_class, list) else [object_class]
) )
group_base = ( group_base = (
config["LDAP"] config["BACKENDS"]["LDAP"]
.get("GROUP_BASE", "") .get("GROUP_BASE", "")
.replace(f',{config["LDAP"]["ROOT_DN"]}', "") .replace(f',{config["BACKENDS"]["LDAP"]["ROOT_DN"]}', "")
) )
Group.base = group_base or None Group.base = group_base or None
Group.rdn_attribute = config["LDAP"].get( Group.rdn_attribute = config["BACKENDS"]["LDAP"].get(
"GROUP_ID_ATTRIBUTE", Group.DEFAULT_ID_ATTRIBUTE "GROUP_ID_ATTRIBUTE", Group.DEFAULT_ID_ATTRIBUTE
) )
object_class = config["LDAP"].get("GROUP_CLASS", Group.DEFAULT_OBJECT_CLASS) object_class = config["BACKENDS"]["LDAP"].get(
"GROUP_CLASS", Group.DEFAULT_OBJECT_CLASS
)
Group.ldap_object_class = ( Group.ldap_object_class = (
object_class if isinstance(object_class, list) else [object_class] object_class if isinstance(object_class, list) else [object_class]
) )
@ -49,17 +55,18 @@ def setup_backend(app):
pass pass
try: try:
g.ldap_connection = ldap.initialize(app.config["LDAP"]["URI"]) g.ldap_connection = ldap.initialize(app.config["BACKENDS"]["LDAP"]["URI"])
g.ldap_connection.set_option( g.ldap_connection.set_option(
ldap.OPT_NETWORK_TIMEOUT, app.config["LDAP"].get("TIMEOUT") ldap.OPT_NETWORK_TIMEOUT, app.config["BACKENDS"]["LDAP"].get("TIMEOUT")
) )
g.ldap_connection.simple_bind_s( g.ldap_connection.simple_bind_s(
app.config["LDAP"]["BIND_DN"], app.config["LDAP"]["BIND_PW"] app.config["BACKENDS"]["LDAP"]["BIND_DN"],
app.config["BACKENDS"]["LDAP"]["BIND_PW"],
) )
except ldap.SERVER_DOWN: except ldap.SERVER_DOWN:
message = _("Could not connect to the LDAP server '{uri}'").format( message = _("Could not connect to the LDAP server '{uri}'").format(
uri=app.config["LDAP"]["URI"] uri=app.config["BACKENDS"]["LDAP"]["URI"]
) )
logging.error(message) logging.error(message)
return ( return (
@ -75,7 +82,7 @@ def setup_backend(app):
except ldap.INVALID_CREDENTIALS: except ldap.INVALID_CREDENTIALS:
message = _("LDAP authentication failed with user '{user}'").format( message = _("LDAP authentication failed with user '{user}'").format(
user=app.config["LDAP"]["BIND_DN"] user=app.config["BACKENDS"]["LDAP"]["BIND_DN"]
) )
logging.error(message) logging.error(message)
return ( return (
@ -116,18 +123,22 @@ def validate_configuration(config):
from canaille.core.models import User from canaille.core.models import User
try: try:
conn = ldap.initialize(config["LDAP"]["URI"]) conn = ldap.initialize(config["BACKENDS"]["LDAP"]["URI"])
conn.set_option(ldap.OPT_NETWORK_TIMEOUT, config["LDAP"].get("TIMEOUT")) conn.set_option(
conn.simple_bind_s(config["LDAP"]["BIND_DN"], config["LDAP"]["BIND_PW"]) ldap.OPT_NETWORK_TIMEOUT, config["BACKENDS"]["LDAP"].get("TIMEOUT")
)
conn.simple_bind_s(
config["BACKENDS"]["LDAP"]["BIND_DN"], config["BACKENDS"]["LDAP"]["BIND_PW"]
)
except ldap.SERVER_DOWN as exc: except ldap.SERVER_DOWN as exc:
raise ConfigurationException( raise ConfigurationException(
f'Could not connect to the LDAP server \'{config["LDAP"]["URI"]}\'' f'Could not connect to the LDAP server \'{config["BACKENDS"]["LDAP"]["URI"]}\''
) from exc ) from exc
except ldap.INVALID_CREDENTIALS as exc: except ldap.INVALID_CREDENTIALS as exc:
raise ConfigurationException( raise ConfigurationException(
f'LDAP authentication failed with user \'{config["LDAP"]["BIND_DN"]}\'' f'LDAP authentication failed with user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\''
) from exc ) from exc
try: try:
@ -144,8 +155,8 @@ def validate_configuration(config):
except ldap.INSUFFICIENT_ACCESS as exc: except ldap.INSUFFICIENT_ACCESS as exc:
raise ConfigurationException( raise ConfigurationException(
f'LDAP user \'{config["LDAP"]["BIND_DN"]}\' cannot create ' f'LDAP user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\' cannot create '
f'users at \'{config["LDAP"]["USER_BASE"]}\'' f'users at \'{config["BACKENDS"]["LDAP"]["USER_BASE"]}\''
) from exc ) from exc
try: try:
@ -169,8 +180,8 @@ def validate_configuration(config):
except ldap.INSUFFICIENT_ACCESS as exc: except ldap.INSUFFICIENT_ACCESS as exc:
raise ConfigurationException( raise ConfigurationException(
f'LDAP user \'{config["LDAP"]["BIND_DN"]}\' cannot create ' f'LDAP user \'{config["BACKENDS"]["LDAP"]["BIND_DN"]}\' cannot create '
f'groups at \'{config["LDAP"]["GROUP_BASE"]}\'' f'groups at \'{config["BACKENDS"]["LDAP"]["GROUP_BASE"]}\''
) from exc ) from exc
finally: finally:

View file

@ -6,9 +6,11 @@ import ldif
@contextmanager @contextmanager
def ldap_connection(config): def ldap_connection(config):
conn = ldap.initialize(config["LDAP"]["URI"]) conn = ldap.initialize(config["BACKENDS"]["LDAP"]["URI"])
conn.set_option(ldap.OPT_NETWORK_TIMEOUT, config["LDAP"].get("TIMEOUT")) conn.set_option(ldap.OPT_NETWORK_TIMEOUT, config["BACKENDS"]["LDAP"].get("TIMEOUT"))
conn.simple_bind_s(config["LDAP"]["BIND_DN"], config["LDAP"]["BIND_PW"]) conn.simple_bind_s(
config["BACKENDS"]["LDAP"]["BIND_DN"], config["BACKENDS"]["LDAP"]["BIND_PW"]
)
try: try:
yield conn yield conn
@ -31,5 +33,5 @@ def install_schema(config, schema_path):
except ldap.INSUFFICIENT_ACCESS as exc: except ldap.INSUFFICIENT_ACCESS as exc:
raise InstallationException( raise InstallationException(
f"The user '{config['LDAP']['BIND_DN']}' has insufficient permissions to install LDAP schemas." f"The user '{config['BACKENDS']['LDAP']['BIND_DN']}' has insufficient permissions to install LDAP schemas."
) from exc ) from exc

View file

@ -56,7 +56,7 @@ LEVEL = "DEBUG"
# written in the standard error output. # written in the standard error output.
# PATH = "" # PATH = ""
[LDAP] [BACKENDS.LDAP]
URI = "ldap://ldap:389" URI = "ldap://ldap:389"
ROOT_DN = "dc=mydomain,dc=tld" ROOT_DN = "dc=mydomain,dc=tld"
BIND_DN = "cn=admin,dc=mydomain,dc=tld" BIND_DN = "cn=admin,dc=mydomain,dc=tld"

View file

@ -56,7 +56,7 @@ LEVEL = "DEBUG"
# written in the standard error output. # written in the standard error output.
# PATH = "" # PATH = ""
[LDAP] [BACKENDS.LDAP]
URI = "ldap://127.0.0.1:5389" URI = "ldap://127.0.0.1:5389"
ROOT_DN = "dc=mydomain,dc=tld" ROOT_DN = "dc=mydomain,dc=tld"
BIND_DN = "cn=admin,dc=mydomain,dc=tld" BIND_DN = "cn=admin,dc=mydomain,dc=tld"

View file

@ -60,8 +60,8 @@ LOGGING
:PATH: :PATH:
*Optional.* The log file path. If not set, logs are written in the standard error output. *Optional.* The log file path. If not set, logs are written in the standard error output.
LDAP BACKENDS.LDAP
---- -------------
:URI: :URI:
**Required.** The URI to the LDAP server. **Required.** The URI to the LDAP server.

View file

@ -8,7 +8,7 @@ def test_check_command(testclient):
def test_check_command_fail(testclient): def test_check_command_fail(testclient):
testclient.app.config["LDAP"]["URI"] = "ldap://invalid-ldap.com" testclient.app.config["BACKENDS"]["LDAP"]["URI"] = "ldap://invalid-ldap.com"
runner = testclient.app.test_cli_runner() runner = testclient.app.test_cli_runner()
res = runner.invoke(cli, ["check"]) res = runner.invoke(cli, ["check"])
assert res.exit_code == 1, res.stdout assert res.exit_code == 1, res.stdout

View file

@ -18,7 +18,7 @@ def test_ldap_connection_remote(testclient, configuration, slapd_connection):
def test_ldap_connection_remote_ldap_unreachable(testclient, configuration): def test_ldap_connection_remote_ldap_unreachable(testclient, configuration):
configuration["LDAP"]["URI"] = "ldap://invalid-ldap.com" configuration["BACKENDS"]["LDAP"]["URI"] = "ldap://invalid-ldap.com"
with pytest.raises( with pytest.raises(
ConfigurationException, ConfigurationException,
match=r"Could not connect to the LDAP server", match=r"Could not connect to the LDAP server",
@ -27,7 +27,7 @@ def test_ldap_connection_remote_ldap_unreachable(testclient, configuration):
def test_ldap_connection_remote_ldap_wrong_credentials(testclient, configuration): def test_ldap_connection_remote_ldap_wrong_credentials(testclient, configuration):
configuration["LDAP"]["BIND_PW"] = "invalid-password" configuration["BACKENDS"]["LDAP"]["BIND_PW"] = "invalid-password"
with pytest.raises( with pytest.raises(
ConfigurationException, ConfigurationException,
match=r"LDAP authentication failed with user", match=r"LDAP authentication failed with user",

View file

@ -1,7 +1,7 @@
def test_ldap_connection_remote_ldap_unreachable(testclient): def test_ldap_connection_remote_ldap_unreachable(testclient):
testclient.app.config["TESTING"] = False testclient.app.config["TESTING"] = False
testclient.app.config["LDAP"]["URI"] = "ldap://invalid-ldap.com" testclient.app.config["BACKENDS"]["LDAP"]["URI"] = "ldap://invalid-ldap.com"
testclient.app.config["DEBUG"] = True testclient.app.config["DEBUG"] = True
res = testclient.get("/", status=500, expect_errors=True) res = testclient.get("/", status=500, expect_errors=True)
@ -15,7 +15,7 @@ def test_ldap_connection_remote_ldap_unreachable(testclient):
def test_ldap_connection_remote_ldap_wrong_credentials(testclient): def test_ldap_connection_remote_ldap_wrong_credentials(testclient):
testclient.app.config["TESTING"] = False testclient.app.config["TESTING"] = False
testclient.app.config["LDAP"]["BIND_PW"] = "invalid-password" testclient.app.config["BACKENDS"]["LDAP"]["BIND_PW"] = "invalid-password"
testclient.app.config["DEBUG"] = True testclient.app.config["DEBUG"] = True
res = testclient.get("/", status=500, expect_errors=True) res = testclient.get("/", status=500, expect_errors=True)

View file

@ -33,7 +33,7 @@ def test_environment_configuration(slapd_server, configuration, tmp_path):
os.environ["CONFIG"] = config_path os.environ["CONFIG"] = config_path
app = create_app() app = create_app()
assert app.config["LDAP"]["ROOT_DN"] == slapd_server.suffix assert app.config["BACKENDS"]["LDAP"]["ROOT_DN"] == slapd_server.suffix
del os.environ["CONFIG"] del os.environ["CONFIG"]
os.remove(config_path) os.remove(config_path)

View file

@ -78,15 +78,17 @@ def configuration(slapd_server, smtpd):
conf = { conf = {
"SECRET_KEY": gen_salt(24), "SECRET_KEY": gen_salt(24),
"LOGO": "/static/img/canaille-head.png", "LOGO": "/static/img/canaille-head.png",
"LDAP": { "BACKENDS": {
"ROOT_DN": slapd_server.suffix, "LDAP": {
"URI": slapd_server.ldap_uri, "ROOT_DN": slapd_server.suffix,
"BIND_DN": slapd_server.root_dn, "URI": slapd_server.ldap_uri,
"BIND_PW": slapd_server.root_pw, "BIND_DN": slapd_server.root_dn,
"USER_BASE": "ou=users", "BIND_PW": slapd_server.root_pw,
"USER_FILTER": "(|(uid={login})(cn={login}))", "USER_BASE": "ou=users",
"GROUP_BASE": "ou=groups", "USER_FILTER": "(|(uid={login})(cn={login}))",
"TIMEOUT": 0.1, "GROUP_BASE": "ou=groups",
"TIMEOUT": 0.1,
},
}, },
"ACL": { "ACL": {
"DEFAULT": { "DEFAULT": {

View file

@ -334,18 +334,20 @@ def test_user_self_deletion(testclient, slapd_connection):
def test_login_placeholder(testclient): def test_login_placeholder(testclient):
testclient.app.config["LDAP"]["USER_FILTER"] = "(uid={login})" testclient.app.config["BACKENDS"]["LDAP"]["USER_FILTER"] = "(uid={login})"
placeholder = testclient.get("/login").form["login"].attrs["placeholder"] placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
assert placeholder == "jdoe" assert placeholder == "jdoe"
testclient.app.config["LDAP"]["USER_FILTER"] = "(cn={login})" testclient.app.config["BACKENDS"]["LDAP"]["USER_FILTER"] = "(cn={login})"
placeholder = testclient.get("/login").form["login"].attrs["placeholder"] placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
assert placeholder == "John Doe" assert placeholder == "John Doe"
testclient.app.config["LDAP"]["USER_FILTER"] = "(mail={login})" testclient.app.config["BACKENDS"]["LDAP"]["USER_FILTER"] = "(mail={login})"
placeholder = testclient.get("/login").form["login"].attrs["placeholder"] placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
assert placeholder == "john@doe.com" assert placeholder == "john@doe.com"
testclient.app.config["LDAP"]["USER_FILTER"] = "(|(uid={login})(mail={login}))" testclient.app.config["BACKENDS"]["LDAP"][
"USER_FILTER"
] = "(|(uid={login})(mail={login}))"
placeholder = testclient.get("/login").form["login"].attrs["placeholder"] placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
assert placeholder == "jdoe or john@doe.com" assert placeholder == "jdoe or john@doe.com"

View file

@ -194,7 +194,7 @@ def test_guess_object_from_dn(slapd_connection, testclient, foo_group):
def test_object_class_update(slapd_connection, testclient): def test_object_class_update(slapd_connection, testclient):
testclient.app.config["LDAP"]["USER_CLASS"] = ["inetOrgPerson"] testclient.app.config["BACKENDS"]["LDAP"]["USER_CLASS"] = ["inetOrgPerson"]
setup_ldap_models(testclient.app.config) setup_ldap_models(testclient.app.config)
user1 = User(cn="foo1", sn="bar1") user1 = User(cn="foo1", sn="bar1")
@ -203,7 +203,10 @@ def test_object_class_update(slapd_connection, testclient):
assert user1.objectClass == ["inetOrgPerson"] assert user1.objectClass == ["inetOrgPerson"]
assert User.get(id=user1.id).objectClass == ["inetOrgPerson"] assert User.get(id=user1.id).objectClass == ["inetOrgPerson"]
testclient.app.config["LDAP"]["USER_CLASS"] = ["inetOrgPerson", "extensibleObject"] testclient.app.config["BACKENDS"]["LDAP"]["USER_CLASS"] = [
"inetOrgPerson",
"extensibleObject",
]
setup_ldap_models(testclient.app.config) setup_ldap_models(testclient.app.config)
user2 = User(cn="foo2", sn="bar2") user2 = User(cn="foo2", sn="bar2")

View file

@ -61,10 +61,10 @@ def test_install_keypair(configuration, tmpdir):
def test_install_schemas(configuration, slapd_server): def test_install_schemas(configuration, slapd_server):
configuration["LDAP"]["ROOT_DN"] = slapd_server.suffix configuration["BACKENDS"]["LDAP"]["ROOT_DN"] = slapd_server.suffix
configuration["LDAP"]["URI"] = slapd_server.ldap_uri configuration["BACKENDS"]["LDAP"]["URI"] = slapd_server.ldap_uri
configuration["LDAP"]["BIND_DN"] = slapd_server.root_dn configuration["BACKENDS"]["LDAP"]["BIND_DN"] = slapd_server.root_dn
configuration["LDAP"]["BIND_PW"] = slapd_server.root_pw configuration["BACKENDS"]["LDAP"]["BIND_PW"] = slapd_server.root_pw
conn = ldap.ldapobject.SimpleLDAPObject(slapd_server.ldap_uri) conn = ldap.ldapobject.SimpleLDAPObject(slapd_server.ldap_uri)
conn.protocol_version = 3 conn.protocol_version = 3
@ -81,10 +81,12 @@ def test_install_schemas(configuration, slapd_server):
def test_install_no_permissions_to_install_schemas(configuration, slapd_server): def test_install_no_permissions_to_install_schemas(configuration, slapd_server):
configuration["LDAP"]["ROOT_DN"] = slapd_server.suffix configuration["BACKENDS"]["LDAP"]["ROOT_DN"] = slapd_server.suffix
configuration["LDAP"]["URI"] = slapd_server.ldap_uri configuration["BACKENDS"]["LDAP"]["URI"] = slapd_server.ldap_uri
configuration["LDAP"]["BIND_DN"] = "uid=admin,ou=users,dc=mydomain,dc=tld" configuration["BACKENDS"]["LDAP"][
configuration["LDAP"]["BIND_PW"] = "admin" "BIND_DN"
] = "uid=admin,ou=users,dc=mydomain,dc=tld"
configuration["BACKENDS"]["LDAP"]["BIND_PW"] = "admin"
conn = ldap.ldapobject.SimpleLDAPObject(slapd_server.ldap_uri) conn = ldap.ldapobject.SimpleLDAPObject(slapd_server.ldap_uri)
conn.protocol_version = 3 conn.protocol_version = 3
@ -102,10 +104,10 @@ def test_install_no_permissions_to_install_schemas(configuration, slapd_server):
def test_install_schemas_command(configuration, slapd_server): def test_install_schemas_command(configuration, slapd_server):
configuration["LDAP"]["ROOT_DN"] = slapd_server.suffix configuration["BACKENDS"]["LDAP"]["ROOT_DN"] = slapd_server.suffix
configuration["LDAP"]["URI"] = slapd_server.ldap_uri configuration["BACKENDS"]["LDAP"]["URI"] = slapd_server.ldap_uri
configuration["LDAP"]["BIND_DN"] = slapd_server.root_dn configuration["BACKENDS"]["LDAP"]["BIND_DN"] = slapd_server.root_dn
configuration["LDAP"]["BIND_PW"] = slapd_server.root_pw configuration["BACKENDS"]["LDAP"]["BIND_PW"] = slapd_server.root_pw
conn = ldap.ldapobject.SimpleLDAPObject(slapd_server.ldap_uri) conn = ldap.ldapobject.SimpleLDAPObject(slapd_server.ldap_uri)
conn.protocol_version = 3 conn.protocol_version = 3