##// END OF EJS Templates
auth: fixed docstring
marcink -
r3267:487f26ba default
parent child Browse files
Show More
@@ -1,797 +1,797 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 import socket
24 import socket
25 import string
25 import string
26 import colander
26 import colander
27 import copy
27 import copy
28 import logging
28 import logging
29 import time
29 import time
30 import traceback
30 import traceback
31 import warnings
31 import warnings
32 import functools
32 import functools
33
33
34 from pyramid.threadlocal import get_current_registry
34 from pyramid.threadlocal import get_current_registry
35
35
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
36 from rhodecode.authentication.interface import IAuthnPluginRegistry
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
37 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
38 from rhodecode.lib import rc_cache
38 from rhodecode.lib import rc_cache
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
39 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
40 from rhodecode.lib.utils2 import safe_int, safe_str
40 from rhodecode.lib.utils2 import safe_int, safe_str
41 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
41 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
42 LdapPasswordError
42 LdapPasswordError
43 from rhodecode.model.db import User
43 from rhodecode.model.db import User
44 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
45 from rhodecode.model.settings import SettingsModel
45 from rhodecode.model.settings import SettingsModel
46 from rhodecode.model.user import UserModel
46 from rhodecode.model.user import UserModel
47 from rhodecode.model.user_group import UserGroupModel
47 from rhodecode.model.user_group import UserGroupModel
48
48
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52 # auth types that authenticate() function can receive
52 # auth types that authenticate() function can receive
53 VCS_TYPE = 'vcs'
53 VCS_TYPE = 'vcs'
54 HTTP_TYPE = 'http'
54 HTTP_TYPE = 'http'
55
55
56 external_auth_session_key = 'rhodecode.external_auth'
56 external_auth_session_key = 'rhodecode.external_auth'
57
57
58
58
59 class hybrid_property(object):
59 class hybrid_property(object):
60 """
60 """
61 a property decorator that works both for instance and class
61 a property decorator that works both for instance and class
62 """
62 """
63 def __init__(self, fget, fset=None, fdel=None, expr=None):
63 def __init__(self, fget, fset=None, fdel=None, expr=None):
64 self.fget = fget
64 self.fget = fget
65 self.fset = fset
65 self.fset = fset
66 self.fdel = fdel
66 self.fdel = fdel
67 self.expr = expr or fget
67 self.expr = expr or fget
68 functools.update_wrapper(self, fget)
68 functools.update_wrapper(self, fget)
69
69
70 def __get__(self, instance, owner):
70 def __get__(self, instance, owner):
71 if instance is None:
71 if instance is None:
72 return self.expr(owner)
72 return self.expr(owner)
73 else:
73 else:
74 return self.fget(instance)
74 return self.fget(instance)
75
75
76 def __set__(self, instance, value):
76 def __set__(self, instance, value):
77 self.fset(instance, value)
77 self.fset(instance, value)
78
78
79 def __delete__(self, instance):
79 def __delete__(self, instance):
80 self.fdel(instance)
80 self.fdel(instance)
81
81
82
82
83 class LazyFormencode(object):
83 class LazyFormencode(object):
84 def __init__(self, formencode_obj, *args, **kwargs):
84 def __init__(self, formencode_obj, *args, **kwargs):
85 self.formencode_obj = formencode_obj
85 self.formencode_obj = formencode_obj
86 self.args = args
86 self.args = args
87 self.kwargs = kwargs
87 self.kwargs = kwargs
88
88
89 def __call__(self, *args, **kwargs):
89 def __call__(self, *args, **kwargs):
90 from inspect import isfunction
90 from inspect import isfunction
91 formencode_obj = self.formencode_obj
91 formencode_obj = self.formencode_obj
92 if isfunction(formencode_obj):
92 if isfunction(formencode_obj):
93 # case we wrap validators into functions
93 # case we wrap validators into functions
94 formencode_obj = self.formencode_obj(*args, **kwargs)
94 formencode_obj = self.formencode_obj(*args, **kwargs)
95 return formencode_obj(*self.args, **self.kwargs)
95 return formencode_obj(*self.args, **self.kwargs)
96
96
97
97
98 class RhodeCodeAuthPluginBase(object):
98 class RhodeCodeAuthPluginBase(object):
99 # UID is used to register plugin to the registry
99 # UID is used to register plugin to the registry
100 uid = None
100 uid = None
101
101
102 # cache the authentication request for N amount of seconds. Some kind
102 # cache the authentication request for N amount of seconds. Some kind
103 # of authentication methods are very heavy and it's very efficient to cache
103 # of authentication methods are very heavy and it's very efficient to cache
104 # the result of a call. If it's set to None (default) cache is off
104 # the result of a call. If it's set to None (default) cache is off
105 AUTH_CACHE_TTL = None
105 AUTH_CACHE_TTL = None
106 AUTH_CACHE = {}
106 AUTH_CACHE = {}
107
107
108 auth_func_attrs = {
108 auth_func_attrs = {
109 "username": "unique username",
109 "username": "unique username",
110 "firstname": "first name",
110 "firstname": "first name",
111 "lastname": "last name",
111 "lastname": "last name",
112 "email": "email address",
112 "email": "email address",
113 "groups": '["list", "of", "groups"]',
113 "groups": '["list", "of", "groups"]',
114 "user_group_sync":
114 "user_group_sync":
115 'True|False defines if returned user groups should be synced',
115 'True|False defines if returned user groups should be synced',
116 "extern_name": "name in external source of record",
116 "extern_name": "name in external source of record",
117 "extern_type": "type of external source of record",
117 "extern_type": "type of external source of record",
118 "admin": 'True|False defines if user should be RhodeCode super admin',
118 "admin": 'True|False defines if user should be RhodeCode super admin',
119 "active":
119 "active":
120 'True|False defines active state of user internally for RhodeCode',
120 'True|False defines active state of user internally for RhodeCode',
121 "active_from_extern":
121 "active_from_extern":
122 "True|False\None, active state from the external auth, "
122 "True|False|None, active state from the external auth, "
123 "None means use definition from RhodeCode extern_type active value"
123 "None means use definition from RhodeCode extern_type active value"
124
124
125 }
125 }
126 # set on authenticate() method and via set_auth_type func.
126 # set on authenticate() method and via set_auth_type func.
127 auth_type = None
127 auth_type = None
128
128
129 # set on authenticate() method and via set_calling_scope_repo, this is a
129 # set on authenticate() method and via set_calling_scope_repo, this is a
130 # calling scope repository when doing authentication most likely on VCS
130 # calling scope repository when doing authentication most likely on VCS
131 # operations
131 # operations
132 acl_repo_name = None
132 acl_repo_name = None
133
133
134 # List of setting names to store encrypted. Plugins may override this list
134 # List of setting names to store encrypted. Plugins may override this list
135 # to store settings encrypted.
135 # to store settings encrypted.
136 _settings_encrypted = []
136 _settings_encrypted = []
137
137
138 # Mapping of python to DB settings model types. Plugins may override or
138 # Mapping of python to DB settings model types. Plugins may override or
139 # extend this mapping.
139 # extend this mapping.
140 _settings_type_map = {
140 _settings_type_map = {
141 colander.String: 'unicode',
141 colander.String: 'unicode',
142 colander.Integer: 'int',
142 colander.Integer: 'int',
143 colander.Boolean: 'bool',
143 colander.Boolean: 'bool',
144 colander.List: 'list',
144 colander.List: 'list',
145 }
145 }
146
146
147 # list of keys in settings that are unsafe to be logged, should be passwords
147 # list of keys in settings that are unsafe to be logged, should be passwords
148 # or other crucial credentials
148 # or other crucial credentials
149 _settings_unsafe_keys = []
149 _settings_unsafe_keys = []
150
150
151 def __init__(self, plugin_id):
151 def __init__(self, plugin_id):
152 self._plugin_id = plugin_id
152 self._plugin_id = plugin_id
153
153
154 def __str__(self):
154 def __str__(self):
155 return self.get_id()
155 return self.get_id()
156
156
157 def _get_setting_full_name(self, name):
157 def _get_setting_full_name(self, name):
158 """
158 """
159 Return the full setting name used for storing values in the database.
159 Return the full setting name used for storing values in the database.
160 """
160 """
161 # TODO: johbo: Using the name here is problematic. It would be good to
161 # TODO: johbo: Using the name here is problematic. It would be good to
162 # introduce either new models in the database to hold Plugin and
162 # introduce either new models in the database to hold Plugin and
163 # PluginSetting or to use the plugin id here.
163 # PluginSetting or to use the plugin id here.
164 return 'auth_{}_{}'.format(self.name, name)
164 return 'auth_{}_{}'.format(self.name, name)
165
165
166 def _get_setting_type(self, name):
166 def _get_setting_type(self, name):
167 """
167 """
168 Return the type of a setting. This type is defined by the SettingsModel
168 Return the type of a setting. This type is defined by the SettingsModel
169 and determines how the setting is stored in DB. Optionally the suffix
169 and determines how the setting is stored in DB. Optionally the suffix
170 `.encrypted` is appended to instruct SettingsModel to store it
170 `.encrypted` is appended to instruct SettingsModel to store it
171 encrypted.
171 encrypted.
172 """
172 """
173 schema_node = self.get_settings_schema().get(name)
173 schema_node = self.get_settings_schema().get(name)
174 db_type = self._settings_type_map.get(
174 db_type = self._settings_type_map.get(
175 type(schema_node.typ), 'unicode')
175 type(schema_node.typ), 'unicode')
176 if name in self._settings_encrypted:
176 if name in self._settings_encrypted:
177 db_type = '{}.encrypted'.format(db_type)
177 db_type = '{}.encrypted'.format(db_type)
178 return db_type
178 return db_type
179
179
180 @classmethod
180 @classmethod
181 def docs(cls):
181 def docs(cls):
182 """
182 """
183 Defines documentation url which helps with plugin setup
183 Defines documentation url which helps with plugin setup
184 """
184 """
185 return ''
185 return ''
186
186
187 @classmethod
187 @classmethod
188 def icon(cls):
188 def icon(cls):
189 """
189 """
190 Defines ICON in SVG format for authentication method
190 Defines ICON in SVG format for authentication method
191 """
191 """
192 return ''
192 return ''
193
193
194 def is_enabled(self):
194 def is_enabled(self):
195 """
195 """
196 Returns true if this plugin is enabled. An enabled plugin can be
196 Returns true if this plugin is enabled. An enabled plugin can be
197 configured in the admin interface but it is not consulted during
197 configured in the admin interface but it is not consulted during
198 authentication.
198 authentication.
199 """
199 """
200 auth_plugins = SettingsModel().get_auth_plugins()
200 auth_plugins = SettingsModel().get_auth_plugins()
201 return self.get_id() in auth_plugins
201 return self.get_id() in auth_plugins
202
202
203 def is_active(self, plugin_cached_settings=None):
203 def is_active(self, plugin_cached_settings=None):
204 """
204 """
205 Returns true if the plugin is activated. An activated plugin is
205 Returns true if the plugin is activated. An activated plugin is
206 consulted during authentication, assumed it is also enabled.
206 consulted during authentication, assumed it is also enabled.
207 """
207 """
208 return self.get_setting_by_name(
208 return self.get_setting_by_name(
209 'enabled', plugin_cached_settings=plugin_cached_settings)
209 'enabled', plugin_cached_settings=plugin_cached_settings)
210
210
211 def get_id(self):
211 def get_id(self):
212 """
212 """
213 Returns the plugin id.
213 Returns the plugin id.
214 """
214 """
215 return self._plugin_id
215 return self._plugin_id
216
216
217 def get_display_name(self):
217 def get_display_name(self):
218 """
218 """
219 Returns a translation string for displaying purposes.
219 Returns a translation string for displaying purposes.
220 """
220 """
221 raise NotImplementedError('Not implemented in base class')
221 raise NotImplementedError('Not implemented in base class')
222
222
223 def get_settings_schema(self):
223 def get_settings_schema(self):
224 """
224 """
225 Returns a colander schema, representing the plugin settings.
225 Returns a colander schema, representing the plugin settings.
226 """
226 """
227 return AuthnPluginSettingsSchemaBase()
227 return AuthnPluginSettingsSchemaBase()
228
228
229 def get_settings(self):
229 def get_settings(self):
230 """
230 """
231 Returns the plugin settings as dictionary.
231 Returns the plugin settings as dictionary.
232 """
232 """
233 settings = {}
233 settings = {}
234 raw_settings = SettingsModel().get_all_settings()
234 raw_settings = SettingsModel().get_all_settings()
235 for node in self.get_settings_schema():
235 for node in self.get_settings_schema():
236 settings[node.name] = self.get_setting_by_name(
236 settings[node.name] = self.get_setting_by_name(
237 node.name, plugin_cached_settings=raw_settings)
237 node.name, plugin_cached_settings=raw_settings)
238 return settings
238 return settings
239
239
240 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
240 def get_setting_by_name(self, name, default=None, plugin_cached_settings=None):
241 """
241 """
242 Returns a plugin setting by name.
242 Returns a plugin setting by name.
243 """
243 """
244 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
244 full_name = 'rhodecode_{}'.format(self._get_setting_full_name(name))
245 if plugin_cached_settings:
245 if plugin_cached_settings:
246 plugin_settings = plugin_cached_settings
246 plugin_settings = plugin_cached_settings
247 else:
247 else:
248 plugin_settings = SettingsModel().get_all_settings()
248 plugin_settings = SettingsModel().get_all_settings()
249
249
250 if full_name in plugin_settings:
250 if full_name in plugin_settings:
251 return plugin_settings[full_name]
251 return plugin_settings[full_name]
252 else:
252 else:
253 return default
253 return default
254
254
255 def create_or_update_setting(self, name, value):
255 def create_or_update_setting(self, name, value):
256 """
256 """
257 Create or update a setting for this plugin in the persistent storage.
257 Create or update a setting for this plugin in the persistent storage.
258 """
258 """
259 full_name = self._get_setting_full_name(name)
259 full_name = self._get_setting_full_name(name)
260 type_ = self._get_setting_type(name)
260 type_ = self._get_setting_type(name)
261 db_setting = SettingsModel().create_or_update_setting(
261 db_setting = SettingsModel().create_or_update_setting(
262 full_name, value, type_)
262 full_name, value, type_)
263 return db_setting.app_settings_value
263 return db_setting.app_settings_value
264
264
265 def log_safe_settings(self, settings):
265 def log_safe_settings(self, settings):
266 """
266 """
267 returns a log safe representation of settings, without any secrets
267 returns a log safe representation of settings, without any secrets
268 """
268 """
269 settings_copy = copy.deepcopy(settings)
269 settings_copy = copy.deepcopy(settings)
270 for k in self._settings_unsafe_keys:
270 for k in self._settings_unsafe_keys:
271 if k in settings_copy:
271 if k in settings_copy:
272 del settings_copy[k]
272 del settings_copy[k]
273 return settings_copy
273 return settings_copy
274
274
275 @hybrid_property
275 @hybrid_property
276 def name(self):
276 def name(self):
277 """
277 """
278 Returns the name of this authentication plugin.
278 Returns the name of this authentication plugin.
279
279
280 :returns: string
280 :returns: string
281 """
281 """
282 raise NotImplementedError("Not implemented in base class")
282 raise NotImplementedError("Not implemented in base class")
283
283
284 def get_url_slug(self):
284 def get_url_slug(self):
285 """
285 """
286 Returns a slug which should be used when constructing URLs which refer
286 Returns a slug which should be used when constructing URLs which refer
287 to this plugin. By default it returns the plugin name. If the name is
287 to this plugin. By default it returns the plugin name. If the name is
288 not suitable for using it in an URL the plugin should override this
288 not suitable for using it in an URL the plugin should override this
289 method.
289 method.
290 """
290 """
291 return self.name
291 return self.name
292
292
293 @property
293 @property
294 def is_headers_auth(self):
294 def is_headers_auth(self):
295 """
295 """
296 Returns True if this authentication plugin uses HTTP headers as
296 Returns True if this authentication plugin uses HTTP headers as
297 authentication method.
297 authentication method.
298 """
298 """
299 return False
299 return False
300
300
301 @hybrid_property
301 @hybrid_property
302 def is_container_auth(self):
302 def is_container_auth(self):
303 """
303 """
304 Deprecated method that indicates if this authentication plugin uses
304 Deprecated method that indicates if this authentication plugin uses
305 HTTP headers as authentication method.
305 HTTP headers as authentication method.
306 """
306 """
307 warnings.warn(
307 warnings.warn(
308 'Use is_headers_auth instead.', category=DeprecationWarning)
308 'Use is_headers_auth instead.', category=DeprecationWarning)
309 return self.is_headers_auth
309 return self.is_headers_auth
310
310
311 @hybrid_property
311 @hybrid_property
312 def allows_creating_users(self):
312 def allows_creating_users(self):
313 """
313 """
314 Defines if Plugin allows users to be created on-the-fly when
314 Defines if Plugin allows users to be created on-the-fly when
315 authentication is called. Controls how external plugins should behave
315 authentication is called. Controls how external plugins should behave
316 in terms if they are allowed to create new users, or not. Base plugins
316 in terms if they are allowed to create new users, or not. Base plugins
317 should not be allowed to, but External ones should be !
317 should not be allowed to, but External ones should be !
318
318
319 :return: bool
319 :return: bool
320 """
320 """
321 return False
321 return False
322
322
323 def set_auth_type(self, auth_type):
323 def set_auth_type(self, auth_type):
324 self.auth_type = auth_type
324 self.auth_type = auth_type
325
325
326 def set_calling_scope_repo(self, acl_repo_name):
326 def set_calling_scope_repo(self, acl_repo_name):
327 self.acl_repo_name = acl_repo_name
327 self.acl_repo_name = acl_repo_name
328
328
329 def allows_authentication_from(
329 def allows_authentication_from(
330 self, user, allows_non_existing_user=True,
330 self, user, allows_non_existing_user=True,
331 allowed_auth_plugins=None, allowed_auth_sources=None):
331 allowed_auth_plugins=None, allowed_auth_sources=None):
332 """
332 """
333 Checks if this authentication module should accept a request for
333 Checks if this authentication module should accept a request for
334 the current user.
334 the current user.
335
335
336 :param user: user object fetched using plugin's get_user() method.
336 :param user: user object fetched using plugin's get_user() method.
337 :param allows_non_existing_user: if True, don't allow the
337 :param allows_non_existing_user: if True, don't allow the
338 user to be empty, meaning not existing in our database
338 user to be empty, meaning not existing in our database
339 :param allowed_auth_plugins: if provided, users extern_type will be
339 :param allowed_auth_plugins: if provided, users extern_type will be
340 checked against a list of provided extern types, which are plugin
340 checked against a list of provided extern types, which are plugin
341 auth_names in the end
341 auth_names in the end
342 :param allowed_auth_sources: authentication type allowed,
342 :param allowed_auth_sources: authentication type allowed,
343 `http` or `vcs` default is both.
343 `http` or `vcs` default is both.
344 defines if plugin will accept only http authentication vcs
344 defines if plugin will accept only http authentication vcs
345 authentication(git/hg) or both
345 authentication(git/hg) or both
346 :returns: boolean
346 :returns: boolean
347 """
347 """
348 if not user and not allows_non_existing_user:
348 if not user and not allows_non_existing_user:
349 log.debug('User is empty but plugin does not allow empty users,'
349 log.debug('User is empty but plugin does not allow empty users,'
350 'not allowed to authenticate')
350 'not allowed to authenticate')
351 return False
351 return False
352
352
353 expected_auth_plugins = allowed_auth_plugins or [self.name]
353 expected_auth_plugins = allowed_auth_plugins or [self.name]
354 if user and (user.extern_type and
354 if user and (user.extern_type and
355 user.extern_type not in expected_auth_plugins):
355 user.extern_type not in expected_auth_plugins):
356 log.debug(
356 log.debug(
357 'User `%s` is bound to `%s` auth type. Plugin allows only '
357 'User `%s` is bound to `%s` auth type. Plugin allows only '
358 '%s, skipping', user, user.extern_type, expected_auth_plugins)
358 '%s, skipping', user, user.extern_type, expected_auth_plugins)
359
359
360 return False
360 return False
361
361
362 # by default accept both
362 # by default accept both
363 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
363 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
364 if self.auth_type not in expected_auth_from:
364 if self.auth_type not in expected_auth_from:
365 log.debug('Current auth source is %s but plugin only allows %s',
365 log.debug('Current auth source is %s but plugin only allows %s',
366 self.auth_type, expected_auth_from)
366 self.auth_type, expected_auth_from)
367 return False
367 return False
368
368
369 return True
369 return True
370
370
371 def get_user(self, username=None, **kwargs):
371 def get_user(self, username=None, **kwargs):
372 """
372 """
373 Helper method for user fetching in plugins, by default it's using
373 Helper method for user fetching in plugins, by default it's using
374 simple fetch by username, but this method can be custimized in plugins
374 simple fetch by username, but this method can be custimized in plugins
375 eg. headers auth plugin to fetch user by environ params
375 eg. headers auth plugin to fetch user by environ params
376
376
377 :param username: username if given to fetch from database
377 :param username: username if given to fetch from database
378 :param kwargs: extra arguments needed for user fetching.
378 :param kwargs: extra arguments needed for user fetching.
379 """
379 """
380 user = None
380 user = None
381 log.debug(
381 log.debug(
382 'Trying to fetch user `%s` from RhodeCode database', username)
382 'Trying to fetch user `%s` from RhodeCode database', username)
383 if username:
383 if username:
384 user = User.get_by_username(username)
384 user = User.get_by_username(username)
385 if not user:
385 if not user:
386 log.debug('User not found, fallback to fetch user in '
386 log.debug('User not found, fallback to fetch user in '
387 'case insensitive mode')
387 'case insensitive mode')
388 user = User.get_by_username(username, case_insensitive=True)
388 user = User.get_by_username(username, case_insensitive=True)
389 else:
389 else:
390 log.debug('provided username:`%s` is empty skipping...', username)
390 log.debug('provided username:`%s` is empty skipping...', username)
391 if not user:
391 if not user:
392 log.debug('User `%s` not found in database', username)
392 log.debug('User `%s` not found in database', username)
393 else:
393 else:
394 log.debug('Got DB user:%s', user)
394 log.debug('Got DB user:%s', user)
395 return user
395 return user
396
396
397 def user_activation_state(self):
397 def user_activation_state(self):
398 """
398 """
399 Defines user activation state when creating new users
399 Defines user activation state when creating new users
400
400
401 :returns: boolean
401 :returns: boolean
402 """
402 """
403 raise NotImplementedError("Not implemented in base class")
403 raise NotImplementedError("Not implemented in base class")
404
404
405 def auth(self, userobj, username, passwd, settings, **kwargs):
405 def auth(self, userobj, username, passwd, settings, **kwargs):
406 """
406 """
407 Given a user object (which may be null), username, a plaintext
407 Given a user object (which may be null), username, a plaintext
408 password, and a settings object (containing all the keys needed as
408 password, and a settings object (containing all the keys needed as
409 listed in settings()), authenticate this user's login attempt.
409 listed in settings()), authenticate this user's login attempt.
410
410
411 Return None on failure. On success, return a dictionary of the form:
411 Return None on failure. On success, return a dictionary of the form:
412
412
413 see: RhodeCodeAuthPluginBase.auth_func_attrs
413 see: RhodeCodeAuthPluginBase.auth_func_attrs
414 This is later validated for correctness
414 This is later validated for correctness
415 """
415 """
416 raise NotImplementedError("not implemented in base class")
416 raise NotImplementedError("not implemented in base class")
417
417
418 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
418 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
419 """
419 """
420 Wrapper to call self.auth() that validates call on it
420 Wrapper to call self.auth() that validates call on it
421
421
422 :param userobj: userobj
422 :param userobj: userobj
423 :param username: username
423 :param username: username
424 :param passwd: plaintext password
424 :param passwd: plaintext password
425 :param settings: plugin settings
425 :param settings: plugin settings
426 """
426 """
427 auth = self.auth(userobj, username, passwd, settings, **kwargs)
427 auth = self.auth(userobj, username, passwd, settings, **kwargs)
428 if auth:
428 if auth:
429 auth['_plugin'] = self.name
429 auth['_plugin'] = self.name
430 auth['_ttl_cache'] = self.get_ttl_cache(settings)
430 auth['_ttl_cache'] = self.get_ttl_cache(settings)
431 # check if hash should be migrated ?
431 # check if hash should be migrated ?
432 new_hash = auth.get('_hash_migrate')
432 new_hash = auth.get('_hash_migrate')
433 if new_hash:
433 if new_hash:
434 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
434 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
435 if 'user_group_sync' not in auth:
435 if 'user_group_sync' not in auth:
436 auth['user_group_sync'] = False
436 auth['user_group_sync'] = False
437 return self._validate_auth_return(auth)
437 return self._validate_auth_return(auth)
438 return auth
438 return auth
439
439
440 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
440 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
441 new_hash_cypher = _RhodeCodeCryptoBCrypt()
441 new_hash_cypher = _RhodeCodeCryptoBCrypt()
442 # extra checks, so make sure new hash is correct.
442 # extra checks, so make sure new hash is correct.
443 password_encoded = safe_str(password)
443 password_encoded = safe_str(password)
444 if new_hash and new_hash_cypher.hash_check(
444 if new_hash and new_hash_cypher.hash_check(
445 password_encoded, new_hash):
445 password_encoded, new_hash):
446 cur_user = User.get_by_username(username)
446 cur_user = User.get_by_username(username)
447 cur_user.password = new_hash
447 cur_user.password = new_hash
448 Session().add(cur_user)
448 Session().add(cur_user)
449 Session().flush()
449 Session().flush()
450 log.info('Migrated user %s hash to bcrypt', cur_user)
450 log.info('Migrated user %s hash to bcrypt', cur_user)
451
451
452 def _validate_auth_return(self, ret):
452 def _validate_auth_return(self, ret):
453 if not isinstance(ret, dict):
453 if not isinstance(ret, dict):
454 raise Exception('returned value from auth must be a dict')
454 raise Exception('returned value from auth must be a dict')
455 for k in self.auth_func_attrs:
455 for k in self.auth_func_attrs:
456 if k not in ret:
456 if k not in ret:
457 raise Exception('Missing %s attribute from returned data' % k)
457 raise Exception('Missing %s attribute from returned data' % k)
458 return ret
458 return ret
459
459
460 def get_ttl_cache(self, settings=None):
460 def get_ttl_cache(self, settings=None):
461 plugin_settings = settings or self.get_settings()
461 plugin_settings = settings or self.get_settings()
462 # we set default to 30, we make a compromise here,
462 # we set default to 30, we make a compromise here,
463 # performance > security, mostly due to LDAP/SVN, majority
463 # performance > security, mostly due to LDAP/SVN, majority
464 # of users pick cache_ttl to be enabled
464 # of users pick cache_ttl to be enabled
465 from rhodecode.authentication import plugin_default_auth_ttl
465 from rhodecode.authentication import plugin_default_auth_ttl
466 cache_ttl = plugin_default_auth_ttl
466 cache_ttl = plugin_default_auth_ttl
467
467
468 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
468 if isinstance(self.AUTH_CACHE_TTL, (int, long)):
469 # plugin cache set inside is more important than the settings value
469 # plugin cache set inside is more important than the settings value
470 cache_ttl = self.AUTH_CACHE_TTL
470 cache_ttl = self.AUTH_CACHE_TTL
471 elif plugin_settings.get('cache_ttl'):
471 elif plugin_settings.get('cache_ttl'):
472 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
472 cache_ttl = safe_int(plugin_settings.get('cache_ttl'), 0)
473
473
474 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
474 plugin_cache_active = bool(cache_ttl and cache_ttl > 0)
475 return plugin_cache_active, cache_ttl
475 return plugin_cache_active, cache_ttl
476
476
477
477
478 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
478 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
479
479
480 @hybrid_property
480 @hybrid_property
481 def allows_creating_users(self):
481 def allows_creating_users(self):
482 return True
482 return True
483
483
484 def use_fake_password(self):
484 def use_fake_password(self):
485 """
485 """
486 Return a boolean that indicates whether or not we should set the user's
486 Return a boolean that indicates whether or not we should set the user's
487 password to a random value when it is authenticated by this plugin.
487 password to a random value when it is authenticated by this plugin.
488 If your plugin provides authentication, then you will generally
488 If your plugin provides authentication, then you will generally
489 want this.
489 want this.
490
490
491 :returns: boolean
491 :returns: boolean
492 """
492 """
493 raise NotImplementedError("Not implemented in base class")
493 raise NotImplementedError("Not implemented in base class")
494
494
495 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
495 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
496 # at this point _authenticate calls plugin's `auth()` function
496 # at this point _authenticate calls plugin's `auth()` function
497 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
497 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
498 userobj, username, passwd, settings, **kwargs)
498 userobj, username, passwd, settings, **kwargs)
499
499
500 if auth:
500 if auth:
501 # maybe plugin will clean the username ?
501 # maybe plugin will clean the username ?
502 # we should use the return value
502 # we should use the return value
503 username = auth['username']
503 username = auth['username']
504
504
505 # if external source tells us that user is not active, we should
505 # if external source tells us that user is not active, we should
506 # skip rest of the process. This can prevent from creating users in
506 # skip rest of the process. This can prevent from creating users in
507 # RhodeCode when using external authentication, but if it's
507 # RhodeCode when using external authentication, but if it's
508 # inactive user we shouldn't create that user anyway
508 # inactive user we shouldn't create that user anyway
509 if auth['active_from_extern'] is False:
509 if auth['active_from_extern'] is False:
510 log.warning(
510 log.warning(
511 "User %s authenticated against %s, but is inactive",
511 "User %s authenticated against %s, but is inactive",
512 username, self.__module__)
512 username, self.__module__)
513 return None
513 return None
514
514
515 cur_user = User.get_by_username(username, case_insensitive=True)
515 cur_user = User.get_by_username(username, case_insensitive=True)
516 is_user_existing = cur_user is not None
516 is_user_existing = cur_user is not None
517
517
518 if is_user_existing:
518 if is_user_existing:
519 log.debug('Syncing user `%s` from '
519 log.debug('Syncing user `%s` from '
520 '`%s` plugin', username, self.name)
520 '`%s` plugin', username, self.name)
521 else:
521 else:
522 log.debug('Creating non existing user `%s` from '
522 log.debug('Creating non existing user `%s` from '
523 '`%s` plugin', username, self.name)
523 '`%s` plugin', username, self.name)
524
524
525 if self.allows_creating_users:
525 if self.allows_creating_users:
526 log.debug('Plugin `%s` allows to '
526 log.debug('Plugin `%s` allows to '
527 'create new users', self.name)
527 'create new users', self.name)
528 else:
528 else:
529 log.debug('Plugin `%s` does not allow to '
529 log.debug('Plugin `%s` does not allow to '
530 'create new users', self.name)
530 'create new users', self.name)
531
531
532 user_parameters = {
532 user_parameters = {
533 'username': username,
533 'username': username,
534 'email': auth["email"],
534 'email': auth["email"],
535 'firstname': auth["firstname"],
535 'firstname': auth["firstname"],
536 'lastname': auth["lastname"],
536 'lastname': auth["lastname"],
537 'active': auth["active"],
537 'active': auth["active"],
538 'admin': auth["admin"],
538 'admin': auth["admin"],
539 'extern_name': auth["extern_name"],
539 'extern_name': auth["extern_name"],
540 'extern_type': self.name,
540 'extern_type': self.name,
541 'plugin': self,
541 'plugin': self,
542 'allow_to_create_user': self.allows_creating_users,
542 'allow_to_create_user': self.allows_creating_users,
543 }
543 }
544
544
545 if not is_user_existing:
545 if not is_user_existing:
546 if self.use_fake_password():
546 if self.use_fake_password():
547 # Randomize the PW because we don't need it, but don't want
547 # Randomize the PW because we don't need it, but don't want
548 # them blank either
548 # them blank either
549 passwd = PasswordGenerator().gen_password(length=16)
549 passwd = PasswordGenerator().gen_password(length=16)
550 user_parameters['password'] = passwd
550 user_parameters['password'] = passwd
551 else:
551 else:
552 # Since the password is required by create_or_update method of
552 # Since the password is required by create_or_update method of
553 # UserModel, we need to set it explicitly.
553 # UserModel, we need to set it explicitly.
554 # The create_or_update method is smart and recognises the
554 # The create_or_update method is smart and recognises the
555 # password hashes as well.
555 # password hashes as well.
556 user_parameters['password'] = cur_user.password
556 user_parameters['password'] = cur_user.password
557
557
558 # we either create or update users, we also pass the flag
558 # we either create or update users, we also pass the flag
559 # that controls if this method can actually do that.
559 # that controls if this method can actually do that.
560 # raises NotAllowedToCreateUserError if it cannot, and we try to.
560 # raises NotAllowedToCreateUserError if it cannot, and we try to.
561 user = UserModel().create_or_update(**user_parameters)
561 user = UserModel().create_or_update(**user_parameters)
562 Session().flush()
562 Session().flush()
563 # enforce user is just in given groups, all of them has to be ones
563 # enforce user is just in given groups, all of them has to be ones
564 # created from plugins. We store this info in _group_data JSON
564 # created from plugins. We store this info in _group_data JSON
565 # field
565 # field
566
566
567 if auth['user_group_sync']:
567 if auth['user_group_sync']:
568 try:
568 try:
569 groups = auth['groups'] or []
569 groups = auth['groups'] or []
570 log.debug(
570 log.debug(
571 'Performing user_group sync based on set `%s` '
571 'Performing user_group sync based on set `%s` '
572 'returned by `%s` plugin', groups, self.name)
572 'returned by `%s` plugin', groups, self.name)
573 UserGroupModel().enforce_groups(user, groups, self.name)
573 UserGroupModel().enforce_groups(user, groups, self.name)
574 except Exception:
574 except Exception:
575 # for any reason group syncing fails, we should
575 # for any reason group syncing fails, we should
576 # proceed with login
576 # proceed with login
577 log.error(traceback.format_exc())
577 log.error(traceback.format_exc())
578
578
579 Session().commit()
579 Session().commit()
580 return auth
580 return auth
581
581
582
582
583 class AuthLdapBase(object):
583 class AuthLdapBase(object):
584
584
585 @classmethod
585 @classmethod
586 def _build_servers(cls, ldap_server_type, ldap_server, port, use_resolver=True):
586 def _build_servers(cls, ldap_server_type, ldap_server, port, use_resolver=True):
587
587
588 def host_resolver(host, port, full_resolve=True):
588 def host_resolver(host, port, full_resolve=True):
589 """
589 """
590 Main work for this function is to prevent ldap connection issues,
590 Main work for this function is to prevent ldap connection issues,
591 and detect them early using a "greenified" sockets
591 and detect them early using a "greenified" sockets
592 """
592 """
593 host = host.strip()
593 host = host.strip()
594 if not full_resolve:
594 if not full_resolve:
595 return '{}:{}'.format(host, port)
595 return '{}:{}'.format(host, port)
596
596
597 log.debug('LDAP: Resolving IP for LDAP host %s', host)
597 log.debug('LDAP: Resolving IP for LDAP host %s', host)
598 try:
598 try:
599 ip = socket.gethostbyname(host)
599 ip = socket.gethostbyname(host)
600 log.debug('Got LDAP server %s ip %s', host, ip)
600 log.debug('Got LDAP server %s ip %s', host, ip)
601 except Exception:
601 except Exception:
602 raise LdapConnectionError(
602 raise LdapConnectionError(
603 'Failed to resolve host: `{}`'.format(host))
603 'Failed to resolve host: `{}`'.format(host))
604
604
605 log.debug('LDAP: Checking if IP %s is accessible', ip)
605 log.debug('LDAP: Checking if IP %s is accessible', ip)
606 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
606 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
607 try:
607 try:
608 s.connect((ip, int(port)))
608 s.connect((ip, int(port)))
609 s.shutdown(socket.SHUT_RD)
609 s.shutdown(socket.SHUT_RD)
610 except Exception:
610 except Exception:
611 raise LdapConnectionError(
611 raise LdapConnectionError(
612 'Failed to connect to host: `{}:{}`'.format(host, port))
612 'Failed to connect to host: `{}:{}`'.format(host, port))
613
613
614 return '{}:{}'.format(host, port)
614 return '{}:{}'.format(host, port)
615
615
616 if len(ldap_server) == 1:
616 if len(ldap_server) == 1:
617 # in case of single server use resolver to detect potential
617 # in case of single server use resolver to detect potential
618 # connection issues
618 # connection issues
619 full_resolve = True
619 full_resolve = True
620 else:
620 else:
621 full_resolve = False
621 full_resolve = False
622
622
623 return ', '.join(
623 return ', '.join(
624 ["{}://{}".format(
624 ["{}://{}".format(
625 ldap_server_type,
625 ldap_server_type,
626 host_resolver(host, port, full_resolve=use_resolver and full_resolve))
626 host_resolver(host, port, full_resolve=use_resolver and full_resolve))
627 for host in ldap_server])
627 for host in ldap_server])
628
628
629 @classmethod
629 @classmethod
630 def _get_server_list(cls, servers):
630 def _get_server_list(cls, servers):
631 return map(string.strip, servers.split(','))
631 return map(string.strip, servers.split(','))
632
632
633 @classmethod
633 @classmethod
634 def get_uid(cls, username, server_addresses):
634 def get_uid(cls, username, server_addresses):
635 uid = username
635 uid = username
636 for server_addr in server_addresses:
636 for server_addr in server_addresses:
637 uid = chop_at(username, "@%s" % server_addr)
637 uid = chop_at(username, "@%s" % server_addr)
638 return uid
638 return uid
639
639
640 @classmethod
640 @classmethod
641 def validate_username(cls, username):
641 def validate_username(cls, username):
642 if "," in username:
642 if "," in username:
643 raise LdapUsernameError(
643 raise LdapUsernameError(
644 "invalid character `,` in username: `{}`".format(username))
644 "invalid character `,` in username: `{}`".format(username))
645
645
646 @classmethod
646 @classmethod
647 def validate_password(cls, username, password):
647 def validate_password(cls, username, password):
648 if not password:
648 if not password:
649 msg = "Authenticating user %s with blank password not allowed"
649 msg = "Authenticating user %s with blank password not allowed"
650 log.warning(msg, username)
650 log.warning(msg, username)
651 raise LdapPasswordError(msg)
651 raise LdapPasswordError(msg)
652
652
653
653
654 def loadplugin(plugin_id):
654 def loadplugin(plugin_id):
655 """
655 """
656 Loads and returns an instantiated authentication plugin.
656 Loads and returns an instantiated authentication plugin.
657 Returns the RhodeCodeAuthPluginBase subclass on success,
657 Returns the RhodeCodeAuthPluginBase subclass on success,
658 or None on failure.
658 or None on failure.
659 """
659 """
660 # TODO: Disusing pyramids thread locals to retrieve the registry.
660 # TODO: Disusing pyramids thread locals to retrieve the registry.
661 authn_registry = get_authn_registry()
661 authn_registry = get_authn_registry()
662 plugin = authn_registry.get_plugin(plugin_id)
662 plugin = authn_registry.get_plugin(plugin_id)
663 if plugin is None:
663 if plugin is None:
664 log.error('Authentication plugin not found: "%s"', plugin_id)
664 log.error('Authentication plugin not found: "%s"', plugin_id)
665 return plugin
665 return plugin
666
666
667
667
668 def get_authn_registry(registry=None):
668 def get_authn_registry(registry=None):
669 registry = registry or get_current_registry()
669 registry = registry or get_current_registry()
670 authn_registry = registry.getUtility(IAuthnPluginRegistry)
670 authn_registry = registry.getUtility(IAuthnPluginRegistry)
671 return authn_registry
671 return authn_registry
672
672
673
673
674 def authenticate(username, password, environ=None, auth_type=None,
674 def authenticate(username, password, environ=None, auth_type=None,
675 skip_missing=False, registry=None, acl_repo_name=None):
675 skip_missing=False, registry=None, acl_repo_name=None):
676 """
676 """
677 Authentication function used for access control,
677 Authentication function used for access control,
678 It tries to authenticate based on enabled authentication modules.
678 It tries to authenticate based on enabled authentication modules.
679
679
680 :param username: username can be empty for headers auth
680 :param username: username can be empty for headers auth
681 :param password: password can be empty for headers auth
681 :param password: password can be empty for headers auth
682 :param environ: environ headers passed for headers auth
682 :param environ: environ headers passed for headers auth
683 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
683 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
684 :param skip_missing: ignores plugins that are in db but not in environment
684 :param skip_missing: ignores plugins that are in db but not in environment
685 :returns: None if auth failed, plugin_user dict if auth is correct
685 :returns: None if auth failed, plugin_user dict if auth is correct
686 """
686 """
687 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
687 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
688 raise ValueError('auth type must be on of http, vcs got "%s" instead'
688 raise ValueError('auth type must be on of http, vcs got "%s" instead'
689 % auth_type)
689 % auth_type)
690 headers_only = environ and not (username and password)
690 headers_only = environ and not (username and password)
691
691
692 authn_registry = get_authn_registry(registry)
692 authn_registry = get_authn_registry(registry)
693 plugins_to_check = authn_registry.get_plugins_for_authentication()
693 plugins_to_check = authn_registry.get_plugins_for_authentication()
694 log.debug('Starting ordered authentication chain using %s plugins',
694 log.debug('Starting ordered authentication chain using %s plugins',
695 [x.name for x in plugins_to_check])
695 [x.name for x in plugins_to_check])
696 for plugin in plugins_to_check:
696 for plugin in plugins_to_check:
697 plugin.set_auth_type(auth_type)
697 plugin.set_auth_type(auth_type)
698 plugin.set_calling_scope_repo(acl_repo_name)
698 plugin.set_calling_scope_repo(acl_repo_name)
699
699
700 if headers_only and not plugin.is_headers_auth:
700 if headers_only and not plugin.is_headers_auth:
701 log.debug('Auth type is for headers only and plugin `%s` is not '
701 log.debug('Auth type is for headers only and plugin `%s` is not '
702 'headers plugin, skipping...', plugin.get_id())
702 'headers plugin, skipping...', plugin.get_id())
703 continue
703 continue
704
704
705 log.debug('Trying authentication using ** %s **', plugin.get_id())
705 log.debug('Trying authentication using ** %s **', plugin.get_id())
706
706
707 # load plugin settings from RhodeCode database
707 # load plugin settings from RhodeCode database
708 plugin_settings = plugin.get_settings()
708 plugin_settings = plugin.get_settings()
709 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
709 plugin_sanitized_settings = plugin.log_safe_settings(plugin_settings)
710 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
710 log.debug('Plugin `%s` settings:%s', plugin.get_id(), plugin_sanitized_settings)
711
711
712 # use plugin's method of user extraction.
712 # use plugin's method of user extraction.
713 user = plugin.get_user(username, environ=environ,
713 user = plugin.get_user(username, environ=environ,
714 settings=plugin_settings)
714 settings=plugin_settings)
715 display_user = user.username if user else username
715 display_user = user.username if user else username
716 log.debug(
716 log.debug(
717 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
717 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
718
718
719 if not plugin.allows_authentication_from(user):
719 if not plugin.allows_authentication_from(user):
720 log.debug('Plugin %s does not accept user `%s` for authentication',
720 log.debug('Plugin %s does not accept user `%s` for authentication',
721 plugin.get_id(), display_user)
721 plugin.get_id(), display_user)
722 continue
722 continue
723 else:
723 else:
724 log.debug('Plugin %s accepted user `%s` for authentication',
724 log.debug('Plugin %s accepted user `%s` for authentication',
725 plugin.get_id(), display_user)
725 plugin.get_id(), display_user)
726
726
727 log.info('Authenticating user `%s` using %s plugin',
727 log.info('Authenticating user `%s` using %s plugin',
728 display_user, plugin.get_id())
728 display_user, plugin.get_id())
729
729
730 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings)
730 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(plugin_settings)
731
731
732 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
732 log.debug('AUTH_CACHE_TTL for plugin `%s` active: %s (TTL: %s)',
733 plugin.get_id(), plugin_cache_active, cache_ttl)
733 plugin.get_id(), plugin_cache_active, cache_ttl)
734
734
735 user_id = user.user_id if user else None
735 user_id = user.user_id if user else None
736 # don't cache for empty users
736 # don't cache for empty users
737 plugin_cache_active = plugin_cache_active and user_id
737 plugin_cache_active = plugin_cache_active and user_id
738 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
738 cache_namespace_uid = 'cache_user_auth.{}'.format(user_id)
739 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
739 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
740
740
741 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
741 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
742 expiration_time=cache_ttl,
742 expiration_time=cache_ttl,
743 condition=plugin_cache_active)
743 condition=plugin_cache_active)
744 def compute_auth(
744 def compute_auth(
745 cache_name, plugin_name, username, password):
745 cache_name, plugin_name, username, password):
746
746
747 # _authenticate is a wrapper for .auth() method of plugin.
747 # _authenticate is a wrapper for .auth() method of plugin.
748 # it checks if .auth() sends proper data.
748 # it checks if .auth() sends proper data.
749 # For RhodeCodeExternalAuthPlugin it also maps users to
749 # For RhodeCodeExternalAuthPlugin it also maps users to
750 # Database and maps the attributes returned from .auth()
750 # Database and maps the attributes returned from .auth()
751 # to RhodeCode database. If this function returns data
751 # to RhodeCode database. If this function returns data
752 # then auth is correct.
752 # then auth is correct.
753 log.debug('Running plugin `%s` _authenticate method '
753 log.debug('Running plugin `%s` _authenticate method '
754 'using username and password', plugin.get_id())
754 'using username and password', plugin.get_id())
755 return plugin._authenticate(
755 return plugin._authenticate(
756 user, username, password, plugin_settings,
756 user, username, password, plugin_settings,
757 environ=environ or {})
757 environ=environ or {})
758
758
759 start = time.time()
759 start = time.time()
760 # for environ based auth, password can be empty, but then the validation is
760 # for environ based auth, password can be empty, but then the validation is
761 # on the server that fills in the env data needed for authentication
761 # on the server that fills in the env data needed for authentication
762 plugin_user = compute_auth('auth', plugin.name, username, (password or ''))
762 plugin_user = compute_auth('auth', plugin.name, username, (password or ''))
763
763
764 auth_time = time.time() - start
764 auth_time = time.time() - start
765 log.debug('Authentication for plugin `%s` completed in %.3fs, '
765 log.debug('Authentication for plugin `%s` completed in %.3fs, '
766 'expiration time of fetched cache %.1fs.',
766 'expiration time of fetched cache %.1fs.',
767 plugin.get_id(), auth_time, cache_ttl)
767 plugin.get_id(), auth_time, cache_ttl)
768
768
769 log.debug('PLUGIN USER DATA: %s', plugin_user)
769 log.debug('PLUGIN USER DATA: %s', plugin_user)
770
770
771 if plugin_user:
771 if plugin_user:
772 log.debug('Plugin returned proper authentication data')
772 log.debug('Plugin returned proper authentication data')
773 return plugin_user
773 return plugin_user
774 # we failed to Auth because .auth() method didn't return proper user
774 # we failed to Auth because .auth() method didn't return proper user
775 log.debug("User `%s` failed to authenticate against %s",
775 log.debug("User `%s` failed to authenticate against %s",
776 display_user, plugin.get_id())
776 display_user, plugin.get_id())
777
777
778 # case when we failed to authenticate against all defined plugins
778 # case when we failed to authenticate against all defined plugins
779 return None
779 return None
780
780
781
781
782 def chop_at(s, sub, inclusive=False):
782 def chop_at(s, sub, inclusive=False):
783 """Truncate string ``s`` at the first occurrence of ``sub``.
783 """Truncate string ``s`` at the first occurrence of ``sub``.
784
784
785 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
785 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
786
786
787 >>> chop_at("plutocratic brats", "rat")
787 >>> chop_at("plutocratic brats", "rat")
788 'plutoc'
788 'plutoc'
789 >>> chop_at("plutocratic brats", "rat", True)
789 >>> chop_at("plutocratic brats", "rat", True)
790 'plutocrat'
790 'plutocrat'
791 """
791 """
792 pos = s.find(sub)
792 pos = s.find(sub)
793 if pos == -1:
793 if pos == -1:
794 return s
794 return s
795 if inclusive:
795 if inclusive:
796 return s[:pos+len(sub)]
796 return s[:pos+len(sub)]
797 return s[:pos]
797 return s[:pos]
General Comments 0
You need to be logged in to leave comments. Login now