sciapp / sampledb

Sample and Measurement Metadata Database
https://scientific-it-systems.iffgit.fz-juelich.de/SampleDB/
MIT License
21 stars 11 forks source link

LDAP setup #55

Closed johannes-michael closed 1 year ago

johannes-michael commented 1 year ago

I can't seem to get LDAP to work. It certainly has to do with the configuration. Maybe someone can help me out.

This is my current configuration:

# LDAP settings
- SAMPLEDB_LDAP_NAME="LDAP University of Rostock"
- SAMPLEDB_LDAP_SERVER=ldaps://my.ldap.server
- SAMPLEDB_LDAP_CONNECT_TIMEOUT=10
- SAMPLEDB_LDAP_USER_BASE_DN="ou=people,o=uni-rostock,c=de"
- SAMPLEDB_LDAP_NAME_ATTRIBUTE=gecos
- SAMPLEDB_LDAP_MAIL_ATTRIBUTE=mail
- SAMPLEDB_LDAP_USER_DN="uid=MY_READER,ou=proxies,o=uni-rostock,c=de"
- SAMPLEDB_LDAP_PASSWORD="<REDACTED>"

- SAMPLEDB_LDAP_UID_FILTER="(uid={})"
- SAMPLEDB_LDAP_OBJECT_DEF=

I'm not entirely sure what to set for UID_FILTER and OBJECT_DEF. I'm used to just providing the LDAP Server, the user for the search, the base_dn and the uid attribute (which is 'uid' in my case). Also, should the server address contain the "ldaps://" prefix?

maltedeckers commented 1 year ago

I am not that deep into this topic, so this might not be completely accurate.

The OBJECT_DEF is the LDAP objectClass to look for (e.g. person, inetOrgPerson, User, ...). The objectClass is an attribute of the LDAP object, one entry could have multiple objectClasses. I think using a wild-card objectclass=* or leaving it empty is not possible in SampleDB, so you might need to figure out a correct objectClass to search for in your LDAP, e.g. by browsing the LDAP/having a look at a user object.

The uid filter looks fine to me if the uid should be used for login.

The other configuration looks good at first glance.

johannes-michael commented 1 year ago

Ok I figured out that the objectClass is person, but LDAP login still won't work. Maybe it has to do with formatting, in how SampleDB expects the variables to be set, but the documentation is not detailed enough in that regard.

I can get LDAP to work perfectly fine in a python script using this setup, but SampleDB does not work. Are there any log files one could look at? Or maybe someone can post their working version to compare against.

FlorianRhiem commented 1 year ago

@johannes-michael Here is a simplified version (I mostly just removed captching exceptions and replaced checks that simply return False if they fail with asserts, to help find the issue) of the LDAP code. I hope this helps you find the issue, as your configuration looks correct to me. Please try this code with your configuration and let me know whether that helps or you are still running into issues.

import typing

import ldap3
import ldap3.core.exceptions
import ldap3.utils.conv

LDAP_SERVER = ...
CONNECT_TIMEOUT = ...
LDAP_USER_BASE_DN = ...
LDAP_UID_FILTER = ...
LDAP_OBJECT_DEF = ...
LDAP_USER_DN = ...
LDAP_PASSWORD = ...
LDAP_MAIL_ATTRIBUTE = ...

user_ldap_uid = ...
password = ...

def _get_ldap_server_pool() -> ldap3.ServerPool:
    ldap_server_urls = LDAP_SERVER
    connect_timeout = CONNECT_TIMEOUT
    servers = []
    for ldap_server_url in ldap_server_urls.split(','):
        ldap_server_url = ldap_server_url.strip()
        if ldap_server_url:
            servers.append(ldap3.Server(ldap_server_url, use_ssl=True, get_info=ldap3.ALL, connect_timeout=connect_timeout))
    return ldap3.ServerPool(servers=servers, pool_strategy=ldap3.ROUND_ROBIN, active=True, exhaust=True)

def _get_user_dn_and_attributes(user_ldap_uid: str, attributes: typing.Sequence[str] = ()) -> typing.Optional[typing.Sequence[typing.Any]]:
    user_base_dn = LDAP_USER_BASE_DN
    uid_filter = LDAP_UID_FILTER
    object_def = LDAP_OBJECT_DEF
    user_dn = LDAP_USER_DN
    password = LDAP_PASSWORD
    server_pool = _get_ldap_server_pool()
    connection = ldap3.Connection(server_pool, user=user_dn, password=password, auto_bind=ldap3.AUTO_BIND_NO_TLS, client_strategy=ldap3.SAFE_SYNC)
    object_def = ldap3.ObjectDef(object_def, connection)
    reader = ldap3.Reader(connection, object_def, user_base_dn, uid_filter.format(ldap3.utils.conv.escape_filter_chars(user_ldap_uid)))
    reader.search(attributes)
    # search if uid matches exactly one user, not more
    if len(reader) != 1:
        return None
    user_attributes = [reader[0].entry_dn]
    for attribute in attributes:
        value = getattr(reader[0], attribute, None)
        if value:
            user_attributes.append(value[0])
        else:
            user_attributes.append(None)
    return user_attributes

mail_attribute = LDAP_MAIL_ATTRIBUTE
user = _get_user_dn_and_attributes(user_ldap_uid, [mail_attribute])
assert user
user_dn, mail = user
assert mail
# try to bind with user credentials if a matching user exists
server_pool = _get_ldap_server_pool()
connection = ldap3.Connection(server_pool, user=user_dn, password=password, client_strategy=ldap3.SAFE_SYNC)
assert bool(connection.bind()[0])
johannes-michael commented 1 year ago

Thanks for in the input. I figured it out. There were two problems.

I played around with your simplified version and also had a closer look at the objectClass. Turns out, the mail attribute is for some reason not part of the object class person in my case. I needed to set it to organizationalPerson. Due to this, I also needed to change the name attribute, since gecos is not part of organizationalPerson. I just used cn instead.

Secondly, and this is a really dumb mistake, because I haven't worked with Docker a lot, the environment variables in the docker-compose.yml can not be quoted. As can be seen in my original post, I quoted some variables that contained spaces or other equal signs.

Now the LDAP login works as expected. Here is the docker-compose.yml excerpt from the final working example:

container_name: sampledb
environment:
        # LDAP settings
        - SAMPLEDB_LDAP_NAME=LDAP University of Rostock
        - SAMPLEDB_LDAP_SERVER=ldaps://my.ldap.server
        - SAMPLEDB_LDAP_CONNECT_TIMEOUT=10
        - SAMPLEDB_LDAP_USER_BASE_DN=ou=people,o=uni-rostock,c=de
        - SAMPLEDB_LDAP_UID_FILTER=(uid={})
        - SAMPLEDB_LDAP_NAME_ATTRIBUTE=cn
        - SAMPLEDB_LDAP_MAIL_ATTRIBUTE=mail
        - SAMPLEDB_LDAP_OBJECT_DEF=organizationalPerson
        - SAMPLEDB_LDAP_USER_DN=uid=myreader,ou=proxies,o=uni-rostock,c=de
        - SAMPLEDB_LDAP_PASSWORD=<REDACTED>
johannes-michael commented 1 year ago

Closing.

FlorianRhiem commented 1 year ago

Good to hear! If you encounter further issues or need help setting up actions/schemas, let us know.