##// END OF EJS Templates
auth: use our own simpler hybrid property....
marcink -
r1961:4409087f default
parent child Browse files
Show More
@@ -1,672 +1,697 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Authentication modules
22 Authentication modules
23 """
23 """
24
24
25 import colander
25 import colander
26 import copy
26 import copy
27 import logging
27 import logging
28 import time
28 import time
29 import traceback
29 import traceback
30 import warnings
30 import warnings
31 import functools
31
32
32 from pyramid.threadlocal import get_current_registry
33 from pyramid.threadlocal import get_current_registry
33 from sqlalchemy.ext.hybrid import hybrid_property
34
34
35 from rhodecode.authentication.interface import IAuthnPluginRegistry
35 from rhodecode.authentication.interface import IAuthnPluginRegistry
36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.lib import caches
37 from rhodecode.lib import caches
38 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
38 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
39 from rhodecode.lib.utils2 import md5_safe, safe_int
39 from rhodecode.lib.utils2 import md5_safe, safe_int
40 from rhodecode.lib.utils2 import safe_str
40 from rhodecode.lib.utils2 import safe_str
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42 from rhodecode.model.meta import Session
42 from rhodecode.model.meta import Session
43 from rhodecode.model.settings import SettingsModel
43 from rhodecode.model.settings import SettingsModel
44 from rhodecode.model.user import UserModel
44 from rhodecode.model.user import UserModel
45 from rhodecode.model.user_group import UserGroupModel
45 from rhodecode.model.user_group import UserGroupModel
46
46
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50 # auth types that authenticate() function can receive
50 # auth types that authenticate() function can receive
51 VCS_TYPE = 'vcs'
51 VCS_TYPE = 'vcs'
52 HTTP_TYPE = 'http'
52 HTTP_TYPE = 'http'
53
53
54
54
55 class hybrid_property(object):
56 """
57 a property decorator that works both for instance and class
58 """
59 def __init__(self, fget, fset=None, fdel=None, expr=None):
60 self.fget = fget
61 self.fset = fset
62 self.fdel = fdel
63 self.expr = expr or fget
64 functools.update_wrapper(self, fget)
65
66 def __get__(self, instance, owner):
67 if instance is None:
68 return self.expr(owner)
69 else:
70 return self.fget(instance)
71
72 def __set__(self, instance, value):
73 self.fset(instance, value)
74
75 def __delete__(self, instance):
76 self.fdel(instance)
77
78
79
55 class LazyFormencode(object):
80 class LazyFormencode(object):
56 def __init__(self, formencode_obj, *args, **kwargs):
81 def __init__(self, formencode_obj, *args, **kwargs):
57 self.formencode_obj = formencode_obj
82 self.formencode_obj = formencode_obj
58 self.args = args
83 self.args = args
59 self.kwargs = kwargs
84 self.kwargs = kwargs
60
85
61 def __call__(self, *args, **kwargs):
86 def __call__(self, *args, **kwargs):
62 from inspect import isfunction
87 from inspect import isfunction
63 formencode_obj = self.formencode_obj
88 formencode_obj = self.formencode_obj
64 if isfunction(formencode_obj):
89 if isfunction(formencode_obj):
65 # case we wrap validators into functions
90 # case we wrap validators into functions
66 formencode_obj = self.formencode_obj(*args, **kwargs)
91 formencode_obj = self.formencode_obj(*args, **kwargs)
67 return formencode_obj(*self.args, **self.kwargs)
92 return formencode_obj(*self.args, **self.kwargs)
68
93
69
94
70 class RhodeCodeAuthPluginBase(object):
95 class RhodeCodeAuthPluginBase(object):
71 # cache the authentication request for N amount of seconds. Some kind
96 # cache the authentication request for N amount of seconds. Some kind
72 # of authentication methods are very heavy and it's very efficient to cache
97 # of authentication methods are very heavy and it's very efficient to cache
73 # the result of a call. If it's set to None (default) cache is off
98 # the result of a call. If it's set to None (default) cache is off
74 AUTH_CACHE_TTL = None
99 AUTH_CACHE_TTL = None
75 AUTH_CACHE = {}
100 AUTH_CACHE = {}
76
101
77 auth_func_attrs = {
102 auth_func_attrs = {
78 "username": "unique username",
103 "username": "unique username",
79 "firstname": "first name",
104 "firstname": "first name",
80 "lastname": "last name",
105 "lastname": "last name",
81 "email": "email address",
106 "email": "email address",
82 "groups": '["list", "of", "groups"]',
107 "groups": '["list", "of", "groups"]',
83 "extern_name": "name in external source of record",
108 "extern_name": "name in external source of record",
84 "extern_type": "type of external source of record",
109 "extern_type": "type of external source of record",
85 "admin": 'True|False defines if user should be RhodeCode super admin',
110 "admin": 'True|False defines if user should be RhodeCode super admin',
86 "active":
111 "active":
87 'True|False defines active state of user internally for RhodeCode',
112 'True|False defines active state of user internally for RhodeCode',
88 "active_from_extern":
113 "active_from_extern":
89 "True|False\None, active state from the external auth, "
114 "True|False\None, active state from the external auth, "
90 "None means use definition from RhodeCode extern_type active value"
115 "None means use definition from RhodeCode extern_type active value"
91 }
116 }
92 # set on authenticate() method and via set_auth_type func.
117 # set on authenticate() method and via set_auth_type func.
93 auth_type = None
118 auth_type = None
94
119
95 # set on authenticate() method and via set_calling_scope_repo, this is a
120 # set on authenticate() method and via set_calling_scope_repo, this is a
96 # calling scope repository when doing authentication most likely on VCS
121 # calling scope repository when doing authentication most likely on VCS
97 # operations
122 # operations
98 acl_repo_name = None
123 acl_repo_name = None
99
124
100 # List of setting names to store encrypted. Plugins may override this list
125 # List of setting names to store encrypted. Plugins may override this list
101 # to store settings encrypted.
126 # to store settings encrypted.
102 _settings_encrypted = []
127 _settings_encrypted = []
103
128
104 # Mapping of python to DB settings model types. Plugins may override or
129 # Mapping of python to DB settings model types. Plugins may override or
105 # extend this mapping.
130 # extend this mapping.
106 _settings_type_map = {
131 _settings_type_map = {
107 colander.String: 'unicode',
132 colander.String: 'unicode',
108 colander.Integer: 'int',
133 colander.Integer: 'int',
109 colander.Boolean: 'bool',
134 colander.Boolean: 'bool',
110 colander.List: 'list',
135 colander.List: 'list',
111 }
136 }
112
137
113 # list of keys in settings that are unsafe to be logged, should be passwords
138 # list of keys in settings that are unsafe to be logged, should be passwords
114 # or other crucial credentials
139 # or other crucial credentials
115 _settings_unsafe_keys = []
140 _settings_unsafe_keys = []
116
141
117 def __init__(self, plugin_id):
142 def __init__(self, plugin_id):
118 self._plugin_id = plugin_id
143 self._plugin_id = plugin_id
119
144
120 def __str__(self):
145 def __str__(self):
121 return self.get_id()
146 return self.get_id()
122
147
123 def _get_setting_full_name(self, name):
148 def _get_setting_full_name(self, name):
124 """
149 """
125 Return the full setting name used for storing values in the database.
150 Return the full setting name used for storing values in the database.
126 """
151 """
127 # TODO: johbo: Using the name here is problematic. It would be good to
152 # TODO: johbo: Using the name here is problematic. It would be good to
128 # introduce either new models in the database to hold Plugin and
153 # introduce either new models in the database to hold Plugin and
129 # PluginSetting or to use the plugin id here.
154 # PluginSetting or to use the plugin id here.
130 return 'auth_{}_{}'.format(self.name, name)
155 return 'auth_{}_{}'.format(self.name, name)
131
156
132 def _get_setting_type(self, name):
157 def _get_setting_type(self, name):
133 """
158 """
134 Return the type of a setting. This type is defined by the SettingsModel
159 Return the type of a setting. This type is defined by the SettingsModel
135 and determines how the setting is stored in DB. Optionally the suffix
160 and determines how the setting is stored in DB. Optionally the suffix
136 `.encrypted` is appended to instruct SettingsModel to store it
161 `.encrypted` is appended to instruct SettingsModel to store it
137 encrypted.
162 encrypted.
138 """
163 """
139 schema_node = self.get_settings_schema().get(name)
164 schema_node = self.get_settings_schema().get(name)
140 db_type = self._settings_type_map.get(
165 db_type = self._settings_type_map.get(
141 type(schema_node.typ), 'unicode')
166 type(schema_node.typ), 'unicode')
142 if name in self._settings_encrypted:
167 if name in self._settings_encrypted:
143 db_type = '{}.encrypted'.format(db_type)
168 db_type = '{}.encrypted'.format(db_type)
144 return db_type
169 return db_type
145
170
146 def is_enabled(self):
171 def is_enabled(self):
147 """
172 """
148 Returns true if this plugin is enabled. An enabled plugin can be
173 Returns true if this plugin is enabled. An enabled plugin can be
149 configured in the admin interface but it is not consulted during
174 configured in the admin interface but it is not consulted during
150 authentication.
175 authentication.
151 """
176 """
152 auth_plugins = SettingsModel().get_auth_plugins()
177 auth_plugins = SettingsModel().get_auth_plugins()
153 return self.get_id() in auth_plugins
178 return self.get_id() in auth_plugins
154
179
155 def is_active(self):
180 def is_active(self):
156 """
181 """
157 Returns true if the plugin is activated. An activated plugin is
182 Returns true if the plugin is activated. An activated plugin is
158 consulted during authentication, assumed it is also enabled.
183 consulted during authentication, assumed it is also enabled.
159 """
184 """
160 return self.get_setting_by_name('enabled')
185 return self.get_setting_by_name('enabled')
161
186
162 def get_id(self):
187 def get_id(self):
163 """
188 """
164 Returns the plugin id.
189 Returns the plugin id.
165 """
190 """
166 return self._plugin_id
191 return self._plugin_id
167
192
168 def get_display_name(self):
193 def get_display_name(self):
169 """
194 """
170 Returns a translation string for displaying purposes.
195 Returns a translation string for displaying purposes.
171 """
196 """
172 raise NotImplementedError('Not implemented in base class')
197 raise NotImplementedError('Not implemented in base class')
173
198
174 def get_settings_schema(self):
199 def get_settings_schema(self):
175 """
200 """
176 Returns a colander schema, representing the plugin settings.
201 Returns a colander schema, representing the plugin settings.
177 """
202 """
178 return AuthnPluginSettingsSchemaBase()
203 return AuthnPluginSettingsSchemaBase()
179
204
180 def get_setting_by_name(self, name, default=None):
205 def get_setting_by_name(self, name, default=None):
181 """
206 """
182 Returns a plugin setting by name.
207 Returns a plugin setting by name.
183 """
208 """
184 full_name = self._get_setting_full_name(name)
209 full_name = self._get_setting_full_name(name)
185 db_setting = SettingsModel().get_setting_by_name(full_name)
210 db_setting = SettingsModel().get_setting_by_name(full_name)
186 return db_setting.app_settings_value if db_setting else default
211 return db_setting.app_settings_value if db_setting else default
187
212
188 def create_or_update_setting(self, name, value):
213 def create_or_update_setting(self, name, value):
189 """
214 """
190 Create or update a setting for this plugin in the persistent storage.
215 Create or update a setting for this plugin in the persistent storage.
191 """
216 """
192 full_name = self._get_setting_full_name(name)
217 full_name = self._get_setting_full_name(name)
193 type_ = self._get_setting_type(name)
218 type_ = self._get_setting_type(name)
194 db_setting = SettingsModel().create_or_update_setting(
219 db_setting = SettingsModel().create_or_update_setting(
195 full_name, value, type_)
220 full_name, value, type_)
196 return db_setting.app_settings_value
221 return db_setting.app_settings_value
197
222
198 def get_settings(self):
223 def get_settings(self):
199 """
224 """
200 Returns the plugin settings as dictionary.
225 Returns the plugin settings as dictionary.
201 """
226 """
202 settings = {}
227 settings = {}
203 for node in self.get_settings_schema():
228 for node in self.get_settings_schema():
204 settings[node.name] = self.get_setting_by_name(node.name)
229 settings[node.name] = self.get_setting_by_name(node.name)
205 return settings
230 return settings
206
231
207 def log_safe_settings(self, settings):
232 def log_safe_settings(self, settings):
208 """
233 """
209 returns a log safe representation of settings, without any secrets
234 returns a log safe representation of settings, without any secrets
210 """
235 """
211 settings_copy = copy.deepcopy(settings)
236 settings_copy = copy.deepcopy(settings)
212 for k in self._settings_unsafe_keys:
237 for k in self._settings_unsafe_keys:
213 if k in settings_copy:
238 if k in settings_copy:
214 del settings_copy[k]
239 del settings_copy[k]
215 return settings_copy
240 return settings_copy
216
241
217 @property
242 @property
218 def validators(self):
243 def validators(self):
219 """
244 """
220 Exposes RhodeCode validators modules
245 Exposes RhodeCode validators modules
221 """
246 """
222 # this is a hack to overcome issues with pylons threadlocals and
247 # this is a hack to overcome issues with pylons threadlocals and
223 # translator object _() not being registered properly.
248 # translator object _() not being registered properly.
224 class LazyCaller(object):
249 class LazyCaller(object):
225 def __init__(self, name):
250 def __init__(self, name):
226 self.validator_name = name
251 self.validator_name = name
227
252
228 def __call__(self, *args, **kwargs):
253 def __call__(self, *args, **kwargs):
229 from rhodecode.model import validators as v
254 from rhodecode.model import validators as v
230 obj = getattr(v, self.validator_name)
255 obj = getattr(v, self.validator_name)
231 # log.debug('Initializing lazy formencode object: %s', obj)
256 # log.debug('Initializing lazy formencode object: %s', obj)
232 return LazyFormencode(obj, *args, **kwargs)
257 return LazyFormencode(obj, *args, **kwargs)
233
258
234 class ProxyGet(object):
259 class ProxyGet(object):
235 def __getattribute__(self, name):
260 def __getattribute__(self, name):
236 return LazyCaller(name)
261 return LazyCaller(name)
237
262
238 return ProxyGet()
263 return ProxyGet()
239
264
240 @hybrid_property
265 @hybrid_property
241 def name(self):
266 def name(self):
242 """
267 """
243 Returns the name of this authentication plugin.
268 Returns the name of this authentication plugin.
244
269
245 :returns: string
270 :returns: string
246 """
271 """
247 raise NotImplementedError("Not implemented in base class")
272 raise NotImplementedError("Not implemented in base class")
248
273
249 def get_url_slug(self):
274 def get_url_slug(self):
250 """
275 """
251 Returns a slug which should be used when constructing URLs which refer
276 Returns a slug which should be used when constructing URLs which refer
252 to this plugin. By default it returns the plugin name. If the name is
277 to this plugin. By default it returns the plugin name. If the name is
253 not suitable for using it in an URL the plugin should override this
278 not suitable for using it in an URL the plugin should override this
254 method.
279 method.
255 """
280 """
256 return self.name
281 return self.name
257
282
258 @property
283 @property
259 def is_headers_auth(self):
284 def is_headers_auth(self):
260 """
285 """
261 Returns True if this authentication plugin uses HTTP headers as
286 Returns True if this authentication plugin uses HTTP headers as
262 authentication method.
287 authentication method.
263 """
288 """
264 return False
289 return False
265
290
266 @hybrid_property
291 @hybrid_property
267 def is_container_auth(self):
292 def is_container_auth(self):
268 """
293 """
269 Deprecated method that indicates if this authentication plugin uses
294 Deprecated method that indicates if this authentication plugin uses
270 HTTP headers as authentication method.
295 HTTP headers as authentication method.
271 """
296 """
272 warnings.warn(
297 warnings.warn(
273 'Use is_headers_auth instead.', category=DeprecationWarning)
298 'Use is_headers_auth instead.', category=DeprecationWarning)
274 return self.is_headers_auth
299 return self.is_headers_auth
275
300
276 @hybrid_property
301 @hybrid_property
277 def allows_creating_users(self):
302 def allows_creating_users(self):
278 """
303 """
279 Defines if Plugin allows users to be created on-the-fly when
304 Defines if Plugin allows users to be created on-the-fly when
280 authentication is called. Controls how external plugins should behave
305 authentication is called. Controls how external plugins should behave
281 in terms if they are allowed to create new users, or not. Base plugins
306 in terms if they are allowed to create new users, or not. Base plugins
282 should not be allowed to, but External ones should be !
307 should not be allowed to, but External ones should be !
283
308
284 :return: bool
309 :return: bool
285 """
310 """
286 return False
311 return False
287
312
288 def set_auth_type(self, auth_type):
313 def set_auth_type(self, auth_type):
289 self.auth_type = auth_type
314 self.auth_type = auth_type
290
315
291 def set_calling_scope_repo(self, acl_repo_name):
316 def set_calling_scope_repo(self, acl_repo_name):
292 self.acl_repo_name = acl_repo_name
317 self.acl_repo_name = acl_repo_name
293
318
294 def allows_authentication_from(
319 def allows_authentication_from(
295 self, user, allows_non_existing_user=True,
320 self, user, allows_non_existing_user=True,
296 allowed_auth_plugins=None, allowed_auth_sources=None):
321 allowed_auth_plugins=None, allowed_auth_sources=None):
297 """
322 """
298 Checks if this authentication module should accept a request for
323 Checks if this authentication module should accept a request for
299 the current user.
324 the current user.
300
325
301 :param user: user object fetched using plugin's get_user() method.
326 :param user: user object fetched using plugin's get_user() method.
302 :param allows_non_existing_user: if True, don't allow the
327 :param allows_non_existing_user: if True, don't allow the
303 user to be empty, meaning not existing in our database
328 user to be empty, meaning not existing in our database
304 :param allowed_auth_plugins: if provided, users extern_type will be
329 :param allowed_auth_plugins: if provided, users extern_type will be
305 checked against a list of provided extern types, which are plugin
330 checked against a list of provided extern types, which are plugin
306 auth_names in the end
331 auth_names in the end
307 :param allowed_auth_sources: authentication type allowed,
332 :param allowed_auth_sources: authentication type allowed,
308 `http` or `vcs` default is both.
333 `http` or `vcs` default is both.
309 defines if plugin will accept only http authentication vcs
334 defines if plugin will accept only http authentication vcs
310 authentication(git/hg) or both
335 authentication(git/hg) or both
311 :returns: boolean
336 :returns: boolean
312 """
337 """
313 if not user and not allows_non_existing_user:
338 if not user and not allows_non_existing_user:
314 log.debug('User is empty but plugin does not allow empty users,'
339 log.debug('User is empty but plugin does not allow empty users,'
315 'not allowed to authenticate')
340 'not allowed to authenticate')
316 return False
341 return False
317
342
318 expected_auth_plugins = allowed_auth_plugins or [self.name]
343 expected_auth_plugins = allowed_auth_plugins or [self.name]
319 if user and (user.extern_type and
344 if user and (user.extern_type and
320 user.extern_type not in expected_auth_plugins):
345 user.extern_type not in expected_auth_plugins):
321 log.debug(
346 log.debug(
322 'User `%s` is bound to `%s` auth type. Plugin allows only '
347 'User `%s` is bound to `%s` auth type. Plugin allows only '
323 '%s, skipping', user, user.extern_type, expected_auth_plugins)
348 '%s, skipping', user, user.extern_type, expected_auth_plugins)
324
349
325 return False
350 return False
326
351
327 # by default accept both
352 # by default accept both
328 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
353 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
329 if self.auth_type not in expected_auth_from:
354 if self.auth_type not in expected_auth_from:
330 log.debug('Current auth source is %s but plugin only allows %s',
355 log.debug('Current auth source is %s but plugin only allows %s',
331 self.auth_type, expected_auth_from)
356 self.auth_type, expected_auth_from)
332 return False
357 return False
333
358
334 return True
359 return True
335
360
336 def get_user(self, username=None, **kwargs):
361 def get_user(self, username=None, **kwargs):
337 """
362 """
338 Helper method for user fetching in plugins, by default it's using
363 Helper method for user fetching in plugins, by default it's using
339 simple fetch by username, but this method can be custimized in plugins
364 simple fetch by username, but this method can be custimized in plugins
340 eg. headers auth plugin to fetch user by environ params
365 eg. headers auth plugin to fetch user by environ params
341
366
342 :param username: username if given to fetch from database
367 :param username: username if given to fetch from database
343 :param kwargs: extra arguments needed for user fetching.
368 :param kwargs: extra arguments needed for user fetching.
344 """
369 """
345 user = None
370 user = None
346 log.debug(
371 log.debug(
347 'Trying to fetch user `%s` from RhodeCode database', username)
372 'Trying to fetch user `%s` from RhodeCode database', username)
348 if username:
373 if username:
349 user = User.get_by_username(username)
374 user = User.get_by_username(username)
350 if not user:
375 if not user:
351 log.debug('User not found, fallback to fetch user in '
376 log.debug('User not found, fallback to fetch user in '
352 'case insensitive mode')
377 'case insensitive mode')
353 user = User.get_by_username(username, case_insensitive=True)
378 user = User.get_by_username(username, case_insensitive=True)
354 else:
379 else:
355 log.debug('provided username:`%s` is empty skipping...', username)
380 log.debug('provided username:`%s` is empty skipping...', username)
356 if not user:
381 if not user:
357 log.debug('User `%s` not found in database', username)
382 log.debug('User `%s` not found in database', username)
358 else:
383 else:
359 log.debug('Got DB user:%s', user)
384 log.debug('Got DB user:%s', user)
360 return user
385 return user
361
386
362 def user_activation_state(self):
387 def user_activation_state(self):
363 """
388 """
364 Defines user activation state when creating new users
389 Defines user activation state when creating new users
365
390
366 :returns: boolean
391 :returns: boolean
367 """
392 """
368 raise NotImplementedError("Not implemented in base class")
393 raise NotImplementedError("Not implemented in base class")
369
394
370 def auth(self, userobj, username, passwd, settings, **kwargs):
395 def auth(self, userobj, username, passwd, settings, **kwargs):
371 """
396 """
372 Given a user object (which may be null), username, a plaintext
397 Given a user object (which may be null), username, a plaintext
373 password, and a settings object (containing all the keys needed as
398 password, and a settings object (containing all the keys needed as
374 listed in settings()), authenticate this user's login attempt.
399 listed in settings()), authenticate this user's login attempt.
375
400
376 Return None on failure. On success, return a dictionary of the form:
401 Return None on failure. On success, return a dictionary of the form:
377
402
378 see: RhodeCodeAuthPluginBase.auth_func_attrs
403 see: RhodeCodeAuthPluginBase.auth_func_attrs
379 This is later validated for correctness
404 This is later validated for correctness
380 """
405 """
381 raise NotImplementedError("not implemented in base class")
406 raise NotImplementedError("not implemented in base class")
382
407
383 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
408 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
384 """
409 """
385 Wrapper to call self.auth() that validates call on it
410 Wrapper to call self.auth() that validates call on it
386
411
387 :param userobj: userobj
412 :param userobj: userobj
388 :param username: username
413 :param username: username
389 :param passwd: plaintext password
414 :param passwd: plaintext password
390 :param settings: plugin settings
415 :param settings: plugin settings
391 """
416 """
392 auth = self.auth(userobj, username, passwd, settings, **kwargs)
417 auth = self.auth(userobj, username, passwd, settings, **kwargs)
393 if auth:
418 if auth:
394 # check if hash should be migrated ?
419 # check if hash should be migrated ?
395 new_hash = auth.get('_hash_migrate')
420 new_hash = auth.get('_hash_migrate')
396 if new_hash:
421 if new_hash:
397 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
422 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
398 return self._validate_auth_return(auth)
423 return self._validate_auth_return(auth)
399 return auth
424 return auth
400
425
401 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
426 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
402 new_hash_cypher = _RhodeCodeCryptoBCrypt()
427 new_hash_cypher = _RhodeCodeCryptoBCrypt()
403 # extra checks, so make sure new hash is correct.
428 # extra checks, so make sure new hash is correct.
404 password_encoded = safe_str(password)
429 password_encoded = safe_str(password)
405 if new_hash and new_hash_cypher.hash_check(
430 if new_hash and new_hash_cypher.hash_check(
406 password_encoded, new_hash):
431 password_encoded, new_hash):
407 cur_user = User.get_by_username(username)
432 cur_user = User.get_by_username(username)
408 cur_user.password = new_hash
433 cur_user.password = new_hash
409 Session().add(cur_user)
434 Session().add(cur_user)
410 Session().flush()
435 Session().flush()
411 log.info('Migrated user %s hash to bcrypt', cur_user)
436 log.info('Migrated user %s hash to bcrypt', cur_user)
412
437
413 def _validate_auth_return(self, ret):
438 def _validate_auth_return(self, ret):
414 if not isinstance(ret, dict):
439 if not isinstance(ret, dict):
415 raise Exception('returned value from auth must be a dict')
440 raise Exception('returned value from auth must be a dict')
416 for k in self.auth_func_attrs:
441 for k in self.auth_func_attrs:
417 if k not in ret:
442 if k not in ret:
418 raise Exception('Missing %s attribute from returned data' % k)
443 raise Exception('Missing %s attribute from returned data' % k)
419 return ret
444 return ret
420
445
421
446
422 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
447 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
423
448
424 @hybrid_property
449 @hybrid_property
425 def allows_creating_users(self):
450 def allows_creating_users(self):
426 return True
451 return True
427
452
428 def use_fake_password(self):
453 def use_fake_password(self):
429 """
454 """
430 Return a boolean that indicates whether or not we should set the user's
455 Return a boolean that indicates whether or not we should set the user's
431 password to a random value when it is authenticated by this plugin.
456 password to a random value when it is authenticated by this plugin.
432 If your plugin provides authentication, then you will generally
457 If your plugin provides authentication, then you will generally
433 want this.
458 want this.
434
459
435 :returns: boolean
460 :returns: boolean
436 """
461 """
437 raise NotImplementedError("Not implemented in base class")
462 raise NotImplementedError("Not implemented in base class")
438
463
439 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
464 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
440 # at this point _authenticate calls plugin's `auth()` function
465 # at this point _authenticate calls plugin's `auth()` function
441 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
466 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
442 userobj, username, passwd, settings, **kwargs)
467 userobj, username, passwd, settings, **kwargs)
443 if auth:
468 if auth:
444 # maybe plugin will clean the username ?
469 # maybe plugin will clean the username ?
445 # we should use the return value
470 # we should use the return value
446 username = auth['username']
471 username = auth['username']
447
472
448 # if external source tells us that user is not active, we should
473 # if external source tells us that user is not active, we should
449 # skip rest of the process. This can prevent from creating users in
474 # skip rest of the process. This can prevent from creating users in
450 # RhodeCode when using external authentication, but if it's
475 # RhodeCode when using external authentication, but if it's
451 # inactive user we shouldn't create that user anyway
476 # inactive user we shouldn't create that user anyway
452 if auth['active_from_extern'] is False:
477 if auth['active_from_extern'] is False:
453 log.warning(
478 log.warning(
454 "User %s authenticated against %s, but is inactive",
479 "User %s authenticated against %s, but is inactive",
455 username, self.__module__)
480 username, self.__module__)
456 return None
481 return None
457
482
458 cur_user = User.get_by_username(username, case_insensitive=True)
483 cur_user = User.get_by_username(username, case_insensitive=True)
459 is_user_existing = cur_user is not None
484 is_user_existing = cur_user is not None
460
485
461 if is_user_existing:
486 if is_user_existing:
462 log.debug('Syncing user `%s` from '
487 log.debug('Syncing user `%s` from '
463 '`%s` plugin', username, self.name)
488 '`%s` plugin', username, self.name)
464 else:
489 else:
465 log.debug('Creating non existing user `%s` from '
490 log.debug('Creating non existing user `%s` from '
466 '`%s` plugin', username, self.name)
491 '`%s` plugin', username, self.name)
467
492
468 if self.allows_creating_users:
493 if self.allows_creating_users:
469 log.debug('Plugin `%s` allows to '
494 log.debug('Plugin `%s` allows to '
470 'create new users', self.name)
495 'create new users', self.name)
471 else:
496 else:
472 log.debug('Plugin `%s` does not allow to '
497 log.debug('Plugin `%s` does not allow to '
473 'create new users', self.name)
498 'create new users', self.name)
474
499
475 user_parameters = {
500 user_parameters = {
476 'username': username,
501 'username': username,
477 'email': auth["email"],
502 'email': auth["email"],
478 'firstname': auth["firstname"],
503 'firstname': auth["firstname"],
479 'lastname': auth["lastname"],
504 'lastname': auth["lastname"],
480 'active': auth["active"],
505 'active': auth["active"],
481 'admin': auth["admin"],
506 'admin': auth["admin"],
482 'extern_name': auth["extern_name"],
507 'extern_name': auth["extern_name"],
483 'extern_type': self.name,
508 'extern_type': self.name,
484 'plugin': self,
509 'plugin': self,
485 'allow_to_create_user': self.allows_creating_users,
510 'allow_to_create_user': self.allows_creating_users,
486 }
511 }
487
512
488 if not is_user_existing:
513 if not is_user_existing:
489 if self.use_fake_password():
514 if self.use_fake_password():
490 # Randomize the PW because we don't need it, but don't want
515 # Randomize the PW because we don't need it, but don't want
491 # them blank either
516 # them blank either
492 passwd = PasswordGenerator().gen_password(length=16)
517 passwd = PasswordGenerator().gen_password(length=16)
493 user_parameters['password'] = passwd
518 user_parameters['password'] = passwd
494 else:
519 else:
495 # Since the password is required by create_or_update method of
520 # Since the password is required by create_or_update method of
496 # UserModel, we need to set it explicitly.
521 # UserModel, we need to set it explicitly.
497 # The create_or_update method is smart and recognises the
522 # The create_or_update method is smart and recognises the
498 # password hashes as well.
523 # password hashes as well.
499 user_parameters['password'] = cur_user.password
524 user_parameters['password'] = cur_user.password
500
525
501 # we either create or update users, we also pass the flag
526 # we either create or update users, we also pass the flag
502 # that controls if this method can actually do that.
527 # that controls if this method can actually do that.
503 # raises NotAllowedToCreateUserError if it cannot, and we try to.
528 # raises NotAllowedToCreateUserError if it cannot, and we try to.
504 user = UserModel().create_or_update(**user_parameters)
529 user = UserModel().create_or_update(**user_parameters)
505 Session().flush()
530 Session().flush()
506 # enforce user is just in given groups, all of them has to be ones
531 # enforce user is just in given groups, all of them has to be ones
507 # created from plugins. We store this info in _group_data JSON
532 # created from plugins. We store this info in _group_data JSON
508 # field
533 # field
509 try:
534 try:
510 groups = auth['groups'] or []
535 groups = auth['groups'] or []
511 UserGroupModel().enforce_groups(user, groups, self.name)
536 UserGroupModel().enforce_groups(user, groups, self.name)
512 except Exception:
537 except Exception:
513 # for any reason group syncing fails, we should
538 # for any reason group syncing fails, we should
514 # proceed with login
539 # proceed with login
515 log.error(traceback.format_exc())
540 log.error(traceback.format_exc())
516 Session().commit()
541 Session().commit()
517 return auth
542 return auth
518
543
519
544
520 def loadplugin(plugin_id):
545 def loadplugin(plugin_id):
521 """
546 """
522 Loads and returns an instantiated authentication plugin.
547 Loads and returns an instantiated authentication plugin.
523 Returns the RhodeCodeAuthPluginBase subclass on success,
548 Returns the RhodeCodeAuthPluginBase subclass on success,
524 or None on failure.
549 or None on failure.
525 """
550 """
526 # TODO: Disusing pyramids thread locals to retrieve the registry.
551 # TODO: Disusing pyramids thread locals to retrieve the registry.
527 authn_registry = get_authn_registry()
552 authn_registry = get_authn_registry()
528 plugin = authn_registry.get_plugin(plugin_id)
553 plugin = authn_registry.get_plugin(plugin_id)
529 if plugin is None:
554 if plugin is None:
530 log.error('Authentication plugin not found: "%s"', plugin_id)
555 log.error('Authentication plugin not found: "%s"', plugin_id)
531 return plugin
556 return plugin
532
557
533
558
534 def get_authn_registry(registry=None):
559 def get_authn_registry(registry=None):
535 registry = registry or get_current_registry()
560 registry = registry or get_current_registry()
536 authn_registry = registry.getUtility(IAuthnPluginRegistry)
561 authn_registry = registry.getUtility(IAuthnPluginRegistry)
537 return authn_registry
562 return authn_registry
538
563
539
564
540 def get_auth_cache_manager(custom_ttl=None):
565 def get_auth_cache_manager(custom_ttl=None):
541 return caches.get_cache_manager(
566 return caches.get_cache_manager(
542 'auth_plugins', 'rhodecode.authentication', custom_ttl)
567 'auth_plugins', 'rhodecode.authentication', custom_ttl)
543
568
544
569
545 def authenticate(username, password, environ=None, auth_type=None,
570 def authenticate(username, password, environ=None, auth_type=None,
546 skip_missing=False, registry=None, acl_repo_name=None):
571 skip_missing=False, registry=None, acl_repo_name=None):
547 """
572 """
548 Authentication function used for access control,
573 Authentication function used for access control,
549 It tries to authenticate based on enabled authentication modules.
574 It tries to authenticate based on enabled authentication modules.
550
575
551 :param username: username can be empty for headers auth
576 :param username: username can be empty for headers auth
552 :param password: password can be empty for headers auth
577 :param password: password can be empty for headers auth
553 :param environ: environ headers passed for headers auth
578 :param environ: environ headers passed for headers auth
554 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
579 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
555 :param skip_missing: ignores plugins that are in db but not in environment
580 :param skip_missing: ignores plugins that are in db but not in environment
556 :returns: None if auth failed, plugin_user dict if auth is correct
581 :returns: None if auth failed, plugin_user dict if auth is correct
557 """
582 """
558 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
583 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
559 raise ValueError('auth type must be on of http, vcs got "%s" instead'
584 raise ValueError('auth type must be on of http, vcs got "%s" instead'
560 % auth_type)
585 % auth_type)
561 headers_only = environ and not (username and password)
586 headers_only = environ and not (username and password)
562
587
563 authn_registry = get_authn_registry(registry)
588 authn_registry = get_authn_registry(registry)
564 for plugin in authn_registry.get_plugins_for_authentication():
589 for plugin in authn_registry.get_plugins_for_authentication():
565 plugin.set_auth_type(auth_type)
590 plugin.set_auth_type(auth_type)
566 plugin.set_calling_scope_repo(acl_repo_name)
591 plugin.set_calling_scope_repo(acl_repo_name)
567
592
568 if headers_only and not plugin.is_headers_auth:
593 if headers_only and not plugin.is_headers_auth:
569 log.debug('Auth type is for headers only and plugin `%s` is not '
594 log.debug('Auth type is for headers only and plugin `%s` is not '
570 'headers plugin, skipping...', plugin.get_id())
595 'headers plugin, skipping...', plugin.get_id())
571 continue
596 continue
572
597
573 # load plugin settings from RhodeCode database
598 # load plugin settings from RhodeCode database
574 plugin_settings = plugin.get_settings()
599 plugin_settings = plugin.get_settings()
575 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
600 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
576 log.debug('Plugin settings:%s', plugin_sanitized_settings)
601 log.debug('Plugin settings:%s', plugin_sanitized_settings)
577
602
578 log.debug('Trying authentication using ** %s **', plugin.get_id())
603 log.debug('Trying authentication using ** %s **', plugin.get_id())
579 # use plugin's method of user extraction.
604 # use plugin's method of user extraction.
580 user = plugin.get_user(username, environ=environ,
605 user = plugin.get_user(username, environ=environ,
581 settings=plugin_settings)
606 settings=plugin_settings)
582 display_user = user.username if user else username
607 display_user = user.username if user else username
583 log.debug(
608 log.debug(
584 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
609 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
585
610
586 if not plugin.allows_authentication_from(user):
611 if not plugin.allows_authentication_from(user):
587 log.debug('Plugin %s does not accept user `%s` for authentication',
612 log.debug('Plugin %s does not accept user `%s` for authentication',
588 plugin.get_id(), display_user)
613 plugin.get_id(), display_user)
589 continue
614 continue
590 else:
615 else:
591 log.debug('Plugin %s accepted user `%s` for authentication',
616 log.debug('Plugin %s accepted user `%s` for authentication',
592 plugin.get_id(), display_user)
617 plugin.get_id(), display_user)
593
618
594 log.info('Authenticating user `%s` using %s plugin',
619 log.info('Authenticating user `%s` using %s plugin',
595 display_user, plugin.get_id())
620 display_user, plugin.get_id())
596
621
597 _cache_ttl = 0
622 _cache_ttl = 0
598
623
599 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
624 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
600 # plugin cache set inside is more important than the settings value
625 # plugin cache set inside is more important than the settings value
601 _cache_ttl = plugin.AUTH_CACHE_TTL
626 _cache_ttl = plugin.AUTH_CACHE_TTL
602 elif plugin_settings.get('cache_ttl'):
627 elif plugin_settings.get('cache_ttl'):
603 _cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
628 _cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
604
629
605 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
630 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
606
631
607 # get instance of cache manager configured for a namespace
632 # get instance of cache manager configured for a namespace
608 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
633 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
609
634
610 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
635 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
611 plugin.get_id(), plugin_cache_active, _cache_ttl)
636 plugin.get_id(), plugin_cache_active, _cache_ttl)
612
637
613 # for environ based password can be empty, but then the validation is
638 # for environ based password can be empty, but then the validation is
614 # on the server that fills in the env data needed for authentication
639 # on the server that fills in the env data needed for authentication
615 _password_hash = md5_safe(plugin.name + username + (password or ''))
640 _password_hash = md5_safe(plugin.name + username + (password or ''))
616
641
617 # _authenticate is a wrapper for .auth() method of plugin.
642 # _authenticate is a wrapper for .auth() method of plugin.
618 # it checks if .auth() sends proper data.
643 # it checks if .auth() sends proper data.
619 # For RhodeCodeExternalAuthPlugin it also maps users to
644 # For RhodeCodeExternalAuthPlugin it also maps users to
620 # Database and maps the attributes returned from .auth()
645 # Database and maps the attributes returned from .auth()
621 # to RhodeCode database. If this function returns data
646 # to RhodeCode database. If this function returns data
622 # then auth is correct.
647 # then auth is correct.
623 start = time.time()
648 start = time.time()
624 log.debug('Running plugin `%s` _authenticate method', plugin.get_id())
649 log.debug('Running plugin `%s` _authenticate method', plugin.get_id())
625
650
626 def auth_func():
651 def auth_func():
627 """
652 """
628 This function is used internally in Cache of Beaker to calculate
653 This function is used internally in Cache of Beaker to calculate
629 Results
654 Results
630 """
655 """
631 return plugin._authenticate(
656 return plugin._authenticate(
632 user, username, password, plugin_settings,
657 user, username, password, plugin_settings,
633 environ=environ or {})
658 environ=environ or {})
634
659
635 if plugin_cache_active:
660 if plugin_cache_active:
636 plugin_user = cache_manager.get(
661 plugin_user = cache_manager.get(
637 _password_hash, createfunc=auth_func)
662 _password_hash, createfunc=auth_func)
638 else:
663 else:
639 plugin_user = auth_func()
664 plugin_user = auth_func()
640
665
641 auth_time = time.time() - start
666 auth_time = time.time() - start
642 log.debug('Authentication for plugin `%s` completed in %.3fs, '
667 log.debug('Authentication for plugin `%s` completed in %.3fs, '
643 'expiration time of fetched cache %.1fs.',
668 'expiration time of fetched cache %.1fs.',
644 plugin.get_id(), auth_time, _cache_ttl)
669 plugin.get_id(), auth_time, _cache_ttl)
645
670
646 log.debug('PLUGIN USER DATA: %s', plugin_user)
671 log.debug('PLUGIN USER DATA: %s', plugin_user)
647
672
648 if plugin_user:
673 if plugin_user:
649 log.debug('Plugin returned proper authentication data')
674 log.debug('Plugin returned proper authentication data')
650 return plugin_user
675 return plugin_user
651 # we failed to Auth because .auth() method didn't return proper user
676 # we failed to Auth because .auth() method didn't return proper user
652 log.debug("User `%s` failed to authenticate against %s",
677 log.debug("User `%s` failed to authenticate against %s",
653 display_user, plugin.get_id())
678 display_user, plugin.get_id())
654 return None
679 return None
655
680
656
681
657 def chop_at(s, sub, inclusive=False):
682 def chop_at(s, sub, inclusive=False):
658 """Truncate string ``s`` at the first occurrence of ``sub``.
683 """Truncate string ``s`` at the first occurrence of ``sub``.
659
684
660 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
685 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
661
686
662 >>> chop_at("plutocratic brats", "rat")
687 >>> chop_at("plutocratic brats", "rat")
663 'plutoc'
688 'plutoc'
664 >>> chop_at("plutocratic brats", "rat", True)
689 >>> chop_at("plutocratic brats", "rat", True)
665 'plutocrat'
690 'plutocrat'
666 """
691 """
667 pos = s.find(sub)
692 pos = s.find(sub)
668 if pos == -1:
693 if pos == -1:
669 return s
694 return s
670 if inclusive:
695 if inclusive:
671 return s[:pos+len(sub)]
696 return s[:pos+len(sub)]
672 return s[:pos]
697 return s[:pos]
@@ -1,109 +1,109 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 External module for testing plugins
22 External module for testing plugins
23
23
24 rhodecode.tests.auth_external_test
24 rhodecode.tests.auth_external_test
25
25
26 """
26 """
27 import logging
27 import logging
28 import traceback
28 import traceback
29
29
30 from rhodecode.authentication.base import RhodeCodeExternalAuthPlugin
30 from rhodecode.authentication.base import (
31 from sqlalchemy.ext.hybrid import hybrid_property
31 RhodeCodeExternalAuthPlugin, hybrid_property)
32 from rhodecode.model.db import User
32 from rhodecode.model.db import User
33 from rhodecode.lib.ext_json import formatted_json
33 from rhodecode.lib.ext_json import formatted_json
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
38 class RhodeCodeAuthPlugin(RhodeCodeExternalAuthPlugin):
39 def __init__(self):
39 def __init__(self):
40 self._logger = logging.getLogger(__name__)
40 self._logger = logging.getLogger(__name__)
41
41
42 @hybrid_property
42 @hybrid_property
43 def allows_creating_users(self):
43 def allows_creating_users(self):
44 return True
44 return True
45
45
46 @hybrid_property
46 @hybrid_property
47 def name(self):
47 def name(self):
48 return "external_test"
48 return "external_test"
49
49
50 def settings(self):
50 def settings(self):
51 settings = [
51 settings = [
52 ]
52 ]
53 return settings
53 return settings
54
54
55 def use_fake_password(self):
55 def use_fake_password(self):
56 return True
56 return True
57
57
58 def user_activation_state(self):
58 def user_activation_state(self):
59 def_user_perms = User.get_default_user().AuthUser.permissions['global']
59 def_user_perms = User.get_default_user().AuthUser.permissions['global']
60 return 'hg.extern_activate.auto' in def_user_perms
60 return 'hg.extern_activate.auto' in def_user_perms
61
61
62 def auth(self, userobj, username, password, settings, **kwargs):
62 def auth(self, userobj, username, password, settings, **kwargs):
63 """
63 """
64 Given a user object (which may be null), username, a plaintext password,
64 Given a user object (which may be null), username, a plaintext password,
65 and a settings object (containing all the keys needed as listed in settings()),
65 and a settings object (containing all the keys needed as listed in settings()),
66 authenticate this user's login attempt.
66 authenticate this user's login attempt.
67
67
68 Return None on failure. On success, return a dictionary of the form:
68 Return None on failure. On success, return a dictionary of the form:
69
69
70 see: RhodeCodeAuthPluginBase.auth_func_attrs
70 see: RhodeCodeAuthPluginBase.auth_func_attrs
71 This is later validated for correctness
71 This is later validated for correctness
72 """
72 """
73
73
74 if not username or not password:
74 if not username or not password:
75 log.debug('Empty username or password skipping...')
75 log.debug('Empty username or password skipping...')
76 return None
76 return None
77
77
78 try:
78 try:
79 user_dn = username
79 user_dn = username
80
80
81 # # old attrs fetched from RhodeCode database
81 # # old attrs fetched from RhodeCode database
82 admin = getattr(userobj, 'admin', False)
82 admin = getattr(userobj, 'admin', False)
83 active = getattr(userobj, 'active', True)
83 active = getattr(userobj, 'active', True)
84 email = getattr(userobj, 'email', '')
84 email = getattr(userobj, 'email', '')
85 firstname = getattr(userobj, 'firstname', '')
85 firstname = getattr(userobj, 'firstname', '')
86 lastname = getattr(userobj, 'lastname', '')
86 lastname = getattr(userobj, 'lastname', '')
87 extern_type = getattr(userobj, 'extern_type', '')
87 extern_type = getattr(userobj, 'extern_type', '')
88 #
88 #
89 user_attrs = {
89 user_attrs = {
90 'username': username,
90 'username': username,
91 'firstname': firstname,
91 'firstname': firstname,
92 'lastname': lastname,
92 'lastname': lastname,
93 'groups': [],
93 'groups': [],
94 'email': '%s@rhodecode.com' % username,
94 'email': '%s@rhodecode.com' % username,
95 'admin': admin,
95 'admin': admin,
96 'active': active,
96 'active': active,
97 "active_from_extern": None,
97 "active_from_extern": None,
98 'extern_name': user_dn,
98 'extern_name': user_dn,
99 'extern_type': extern_type,
99 'extern_type': extern_type,
100 }
100 }
101
101
102 log.debug('EXTERNAL user: \n%s' % formatted_json(user_attrs))
102 log.debug('EXTERNAL user: \n%s' % formatted_json(user_attrs))
103 log.info('user %s authenticated correctly' % user_attrs['username'])
103 log.info('user %s authenticated correctly' % user_attrs['username'])
104
104
105 return user_attrs
105 return user_attrs
106
106
107 except (Exception,):
107 except (Exception,):
108 log.error(traceback.format_exc())
108 log.error(traceback.format_exc())
109 return None
109 return None
General Comments 0
You need to be logged in to leave comments. Login now