Skip to content

Search

find_ldap_user(login)

Find the LDAP user identified by 'login' in the configured ldap database.

Parameters:

Name Type Description Default
login

The login to find in the LDAP database

required

Returns:

Type Description

None if no user is found, a dictionary defining 'cn', 'username', 'fullname' and 'email' otherwise.

Source code in ckanext/ldap/lib/search.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def find_ldap_user(login):
    """
    Find the LDAP user identified by 'login' in the configured ldap database.

    :param login: The login to find in the LDAP database
    :returns: None if no user is found, a dictionary defining 'cn', 'username',
        'fullname' and 'email' otherwise.
    """
    cnx = ldap.initialize(
        toolkit.config['ckanext.ldap.uri'],
        bytes_mode=False,
        trace_level=toolkit.config['ckanext.ldap.trace_level'],
    )
    cnx.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
    if toolkit.config['ckanext.ldap.ignore_referrals']:
        cnx.set_option(ldap.OPT_REFERRALS, 0)

    if toolkit.config.get('ckanext.ldap.auth.dn'):
        try:
            if toolkit.config['ckanext.ldap.auth.method'] == 'SIMPLE':
                cnx.bind_s(
                    toolkit.config['ckanext.ldap.auth.dn'],
                    toolkit.config['ckanext.ldap.auth.password'],
                )
            elif toolkit.config['ckanext.ldap.auth.method'] == 'SASL':
                if toolkit.config['ckanext.ldap.auth.mechanism'] == 'DIGEST-MD5':
                    auth_tokens = ldap.sasl.digest_md5(
                        toolkit.config['ckanext.ldap.auth.dn'],
                        toolkit.config['ckanext.ldap.auth.password'],
                    )
                    cnx.sasl_interactive_bind_s('', auth_tokens)
                else:
                    log.error(
                        f'SASL mechanism not supported: '
                        f'{toolkit.config["ckanext.ldap.auth.mechanism"]}'
                    )
                    return None
            else:
                log.error(
                    f'LDAP authentication method is not supported: '
                    f'{toolkit.config["ckanext.ldap.auth.method"]}'
                )
                return None
        except ldap.SERVER_DOWN:
            log.error('LDAP server is not reachable')
            return None
        except ldap.INVALID_CREDENTIALS:
            log.error(
                'LDAP server credentials (ckanext.ldap.auth.dn and '
                'ckanext.ldap.auth.password) invalid'
            )
            return None
        except ldap.LDAPError as e:
            log.error(f'Fatal LDAP Error: {e}')
            return None

    filter_str = toolkit.config['ckanext.ldap.search.filter'].format(
        login=ldap.filter.escape_filter_chars(login)
    )
    attributes = [toolkit.config['ckanext.ldap.username']]
    if 'ckanext.ldap.fullname' in toolkit.config:
        attributes.append(toolkit.config['ckanext.ldap.fullname'])
    if 'ckanext.ldap.email' in toolkit.config:
        attributes.append(toolkit.config['ckanext.ldap.email'])
    try:
        ret = ldap_search(cnx, filter_str, attributes, non_unique='log')
        if ret is None and 'ckanext.ldap.search.alt' in toolkit.config:
            filter_str = toolkit.config['ckanext.ldap.search.alt'].format(
                login=ldap.filter.escape_filter_chars(login)
            )
            ret = ldap_search(cnx, filter_str, attributes, non_unique='raise')
    finally:
        cnx.unbind()
    return ret

Helper function to perform the actual LDAP search.

Parameters:

Name Type Description Default
cnx

The LDAP connection object

required
filter_str

The LDAP filter string

required
attributes

The LDAP attributes to fetch. This must include self.ldap_username

required
non_unique

What to do when there is more than one result. Can be either 'log' (log an error and return None - used to indicate that this is a configuration problem that needs to be address by the site admin, not by the current user) or 'raise' (raise an exception with a message that will be displayed to the current user - such as 'please use your unique id instead'). Other values will silently ignore the error. (Default value = 'raise')

'raise'

Returns:

Type Description

A dictionary defining 'cn', self.ldap_username and any other attributes that were defined in attributes; or None if no user was found.

Source code in ckanext/ldap/lib/search.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def ldap_search(cnx, filter_str, attributes, non_unique='raise'):
    """
    Helper function to perform the actual LDAP search.

    :param cnx: The LDAP connection object
    :param filter_str: The LDAP filter string
    :param attributes: The LDAP attributes to fetch. This *must* include self.ldap_username
    :param non_unique: What to do when there is more than one result. Can be either
                       'log' (log an error and return None - used to indicate that this
                       is a configuration problem that needs to be address by the site
                       admin, not by the current user) or 'raise' (raise an exception
                       with a message that will be displayed to the current user - such
                       as 'please use your unique id instead'). Other values will
                       silently ignore the error. (Default value = 'raise')
    :returns: A dictionary defining 'cn', self.ldap_username and any other attributes
              that were defined in attributes; or None if no user was found.
    """
    try:
        res = cnx.search_s(
            toolkit.config['ckanext.ldap.base_dn'],
            ldap.SCOPE_SUBTREE,
            filterstr=filter_str,
            attrlist=attributes,
        )
        if toolkit.config['ckanext.ldap.ignore_referrals']:
            res = [x for x in res if x[0] is not None]
    except ldap.SERVER_DOWN:
        log.error('LDAP server is not reachable')
        return None
    except ldap.OPERATIONS_ERROR as e:
        log.error(
            f'LDAP query failed. Maybe you need auth credentials for performing searches? '
            f'Error returned by the server: {e}'
        )
        return None
    except (ldap.NO_SUCH_OBJECT, ldap.REFERRAL) as e:
        log.error(
            'LDAP distinguished name (ckanext.ldap.base_dn) is malformed or does not exist.'
        )
        return None
    except ldap.FILTER_ERROR:
        log.error('LDAP filter (ckanext.ldap.search) is malformed')
        return None
    if len(res) > 1:
        if non_unique == 'log':
            log.error(
                'LDAP search.filter search returned more than one entry, ignoring. Fix the '
                'search to return only 1 or 0 results.'
            )
        elif non_unique == 'raise':
            raise MultipleMatchError(toolkit.config['ckanext.ldap.search.alt_msg'])
        return None
    elif len(res) == 1:
        cn = res[0][0]
        attr = res[0][1]
        ret = {
            'cn': cn,
        }

        # Check required fields
        for i in ['username', 'email']:
            cname = 'ckanext.ldap.' + i
            if toolkit.config[cname] not in attr or not attr[toolkit.config[cname]]:
                log.error('LDAP search did not return a {}.'.format(i))
                return None
        # Set return dict
        for i in ['username', 'fullname', 'email', 'about']:
            cname = f'ckanext.ldap.{i}'
            if cname in toolkit.config and toolkit.config[cname] in attr:
                v = attr[toolkit.config[cname]]
                if v:
                    ret[i] = helpers.decode_str(v[0])
        return ret
    else:
        return None