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}
|