Skip to content

Plugin

LdapPlugin

Bases: SingletonPlugin

LdapPlugin.

This plugin provides Ldap authentication by implementing the IAuthenticator interface.

Source code in ckanext/ldap/plugin.py
 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
 92
 93
 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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
class LdapPlugin(SingletonPlugin):
    """
    LdapPlugin.

    This plugin provides Ldap authentication by implementing the IAuthenticator
    interface.
    """

    implements(interfaces.IAuthenticator, inherit=True)
    implements(interfaces.IConfigurable)
    implements(interfaces.IConfigurer)
    implements(interfaces.IBlueprint, inherit=True)
    implements(interfaces.IAuthFunctions)
    implements(interfaces.ITemplateHelpers, inherit=True)
    implements(interfaces.IClick)

    ## IClick
    def get_commands(self):
        return cli.get_commands()

    ## IConfigurer
    def update_config(self, config):
        """
        Implement IConfiguer.update_config.

        :param config:
        """
        # Add our custom template to the list of templates so we can override the login
        # form.
        toolkit.add_template_directory(config, 'theme/templates')

        # Our own config schema, defines required items, default values and transform
        # functions
        schema = {
            'ckanext.ldap.uri': {'required': True},
            'ckanext.ldap.base_dn': {'required': True},
            'ckanext.ldap.search.filter': {'required': True},
            'ckanext.ldap.username': {'required': True},
            'ckanext.ldap.email': {'required': True},
            'ckanext.ldap.auth.dn': {},
            'ckanext.ldap.auth.password': {'required_if': 'ckanext.ldap.auth.dn'},
            'ckanext.ldap.auth.method': {
                'default': 'SIMPLE',
                'validate': _allowed_auth_methods,
            },
            'ckanext.ldap.auth.mechanism': {
                'default': 'DIGEST-MD5',
                'validate': _allowed_auth_mechanisms,
            },
            'ckanext.ldap.search.alt': {},
            'ckanext.ldap.search.alt_msg': {'required_if': 'ckanext.ldap.search.alt'},
            'ckanext.ldap.fullname': {},
            'ckanext.ldap.about': {},
            'ckanext.ldap.organization.id': {},
            'ckanext.ldap.organization.role': {
                'default': 'member',
                'validate': _allowed_roles,
            },
            'ckanext.ldap.ckan_fallback': {'default': False, 'parse': toolkit.asbool},
            'ckanext.ldap.prevent_edits': {'default': False, 'parse': toolkit.asbool},
            'ckanext.ldap.migrate': {'default': False, 'parse': toolkit.asbool},
            'ckanext.ldap.debug_level': {'default': 0, 'parse': toolkit.asint},
            'ckanext.ldap.trace_level': {'default': 0, 'parse': toolkit.asint},
            'ckanext.ldap.ignore_referrals': {
                'default': False,
                'parse': toolkit.asbool,
            },
        }
        errors = []
        for key, options in schema.items():
            config_value = config.get(key, None)

            if config_value:
                if 'parse' in options:
                    config_value = (options['parse'])(config_value)
                try:
                    if 'validate' in options:
                        (options['validate'])(config_value)
                    config[key] = config_value
                except ConfigError as e:
                    errors.append(str(e))
            elif options.get('required', False):
                errors.append(f'Configuration parameter {key} is required')
            elif 'required_if' in options and options['required_if'] in config:
                errors.append(
                    f'Configuration parameter {key} is required '
                    f'when {options["required_if"]} is present'
                )
            elif 'default' in options:
                config[key] = options['default']

        if len(errors):
            raise ConfigError('\n'.join(errors))

    ## IBlueprint
    def get_blueprint(self):
        return routes.blueprints

    ## IAuthFunctions
    def get_auth_functions(self):
        """
        Implements IAuthFunctions.get_auth_functions.
        """
        return {
            'user_update': user_update,
            'user_create': user_create,
            'user_reset': user_reset,
        }

    ## IConfigurable
    def configure(self, config):
        """
        Implementation of IConfigurable.configure.

        :param config:
        """
        ldap.set_option(ldap.OPT_DEBUG_LEVEL, config['ckanext.ldap.debug_level'])

    # IAuthenticator
    def login(self):
        """
        We don't need to do anything here as we override the form & implement our own
        controller action.
        """
        pass

    # IAuthenticator
    def identify(self):
        """
        Identify which user (if any) is logged in via this plugin.
        """
        # FIXME: This breaks if the current user changes their own user name.
        user = session.get('ckanext-ldap-user')
        if user:
            toolkit.c.user = user
        else:
            # add the 'user' attribute to the context to avoid issue #4247
            toolkit.c.user = None

    # IAuthenticator
    def logout(self):
        # Delete session items managed by ckanext-ldap
        self._delete_session_items()

        # In CKAN 2.10.0+, we also need to invoke the toolkit's
        # logout_user() command to clean up anything remaining
        # on the CKAN side.
        if toolkit.check_ckan_version(min_version='2.10.0'):
            toolkit.logout_user()

    # IAuthenticator
    def abort(self, status_code, detail, headers, comment):
        return status_code, detail, headers, comment

    def _delete_session_items(self):
        """
        Delete user details stored in the session by this plugin.
        """
        if 'ckanext-ldap-user' in session:
            del session['ckanext-ldap-user']
            session.save()

    def get_helpers(self):
        return {'is_ldap_user': is_ldap_user, 'get_login_action': get_login_action}

configure(config)

Implementation of IConfigurable.configure.

Parameters:

Name Type Description Default
config
required
Source code in ckanext/ldap/plugin.py
134
135
136
137
138
139
140
def configure(self, config):
    """
    Implementation of IConfigurable.configure.

    :param config:
    """
    ldap.set_option(ldap.OPT_DEBUG_LEVEL, config['ckanext.ldap.debug_level'])

get_auth_functions()

Implements IAuthFunctions.get_auth_functions.

Source code in ckanext/ldap/plugin.py
123
124
125
126
127
128
129
130
131
def get_auth_functions(self):
    """
    Implements IAuthFunctions.get_auth_functions.
    """
    return {
        'user_update': user_update,
        'user_create': user_create,
        'user_reset': user_reset,
    }

identify()

Identify which user (if any) is logged in via this plugin.

Source code in ckanext/ldap/plugin.py
151
152
153
154
155
156
157
158
159
160
161
def identify(self):
    """
    Identify which user (if any) is logged in via this plugin.
    """
    # FIXME: This breaks if the current user changes their own user name.
    user = session.get('ckanext-ldap-user')
    if user:
        toolkit.c.user = user
    else:
        # add the 'user' attribute to the context to avoid issue #4247
        toolkit.c.user = None

login()

We don't need to do anything here as we override the form & implement our own controller action.

Source code in ckanext/ldap/plugin.py
143
144
145
146
147
148
def login(self):
    """
    We don't need to do anything here as we override the form & implement our own
    controller action.
    """
    pass

update_config(config)

Implement IConfiguer.update_config.

Parameters:

Name Type Description Default
config
required
Source code in ckanext/ldap/plugin.py
 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
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def update_config(self, config):
    """
    Implement IConfiguer.update_config.

    :param config:
    """
    # Add our custom template to the list of templates so we can override the login
    # form.
    toolkit.add_template_directory(config, 'theme/templates')

    # Our own config schema, defines required items, default values and transform
    # functions
    schema = {
        'ckanext.ldap.uri': {'required': True},
        'ckanext.ldap.base_dn': {'required': True},
        'ckanext.ldap.search.filter': {'required': True},
        'ckanext.ldap.username': {'required': True},
        'ckanext.ldap.email': {'required': True},
        'ckanext.ldap.auth.dn': {},
        'ckanext.ldap.auth.password': {'required_if': 'ckanext.ldap.auth.dn'},
        'ckanext.ldap.auth.method': {
            'default': 'SIMPLE',
            'validate': _allowed_auth_methods,
        },
        'ckanext.ldap.auth.mechanism': {
            'default': 'DIGEST-MD5',
            'validate': _allowed_auth_mechanisms,
        },
        'ckanext.ldap.search.alt': {},
        'ckanext.ldap.search.alt_msg': {'required_if': 'ckanext.ldap.search.alt'},
        'ckanext.ldap.fullname': {},
        'ckanext.ldap.about': {},
        'ckanext.ldap.organization.id': {},
        'ckanext.ldap.organization.role': {
            'default': 'member',
            'validate': _allowed_roles,
        },
        'ckanext.ldap.ckan_fallback': {'default': False, 'parse': toolkit.asbool},
        'ckanext.ldap.prevent_edits': {'default': False, 'parse': toolkit.asbool},
        'ckanext.ldap.migrate': {'default': False, 'parse': toolkit.asbool},
        'ckanext.ldap.debug_level': {'default': 0, 'parse': toolkit.asint},
        'ckanext.ldap.trace_level': {'default': 0, 'parse': toolkit.asint},
        'ckanext.ldap.ignore_referrals': {
            'default': False,
            'parse': toolkit.asbool,
        },
    }
    errors = []
    for key, options in schema.items():
        config_value = config.get(key, None)

        if config_value:
            if 'parse' in options:
                config_value = (options['parse'])(config_value)
            try:
                if 'validate' in options:
                    (options['validate'])(config_value)
                config[key] = config_value
            except ConfigError as e:
                errors.append(str(e))
        elif options.get('required', False):
            errors.append(f'Configuration parameter {key} is required')
        elif 'required_if' in options and options['required_if'] in config:
            errors.append(
                f'Configuration parameter {key} is required '
                f'when {options["required_if"]} is present'
            )
        elif 'default' in options:
            config[key] = options['default']

    if len(errors):
        raise ConfigError('\n'.join(errors))