##// END OF EJS Templates
auth-token: allow other authentication types to use auth-token....
marcink -
r440:23eb57a0 default
parent child Browse files
Show More
@@ -1,615 +1,621 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 logging
26 import logging
27 import time
27 import time
28 import traceback
28 import traceback
29 import warnings
29 import warnings
30
30
31 from pyramid.threadlocal import get_current_registry
31 from pyramid.threadlocal import get_current_registry
32 from sqlalchemy.ext.hybrid import hybrid_property
32 from sqlalchemy.ext.hybrid import hybrid_property
33
33
34 from rhodecode.authentication.interface import IAuthnPluginRegistry
34 from rhodecode.authentication.interface import IAuthnPluginRegistry
35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
35 from rhodecode.authentication.schema import AuthnPluginSettingsSchemaBase
36 from rhodecode.lib import caches
36 from rhodecode.lib import caches
37 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
37 from rhodecode.lib.auth import PasswordGenerator, _RhodeCodeCryptoBCrypt
38 from rhodecode.lib.utils2 import md5_safe, safe_int
38 from rhodecode.lib.utils2 import md5_safe, safe_int
39 from rhodecode.lib.utils2 import safe_str
39 from rhodecode.lib.utils2 import safe_str
40 from rhodecode.model.db import User
40 from rhodecode.model.db import User
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from rhodecode.model.settings import SettingsModel
42 from rhodecode.model.settings import SettingsModel
43 from rhodecode.model.user import UserModel
43 from rhodecode.model.user import UserModel
44 from rhodecode.model.user_group import UserGroupModel
44 from rhodecode.model.user_group import UserGroupModel
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49 # auth types that authenticate() function can receive
49 # auth types that authenticate() function can receive
50 VCS_TYPE = 'vcs'
50 VCS_TYPE = 'vcs'
51 HTTP_TYPE = 'http'
51 HTTP_TYPE = 'http'
52
52
53
53
54 class LazyFormencode(object):
54 class LazyFormencode(object):
55 def __init__(self, formencode_obj, *args, **kwargs):
55 def __init__(self, formencode_obj, *args, **kwargs):
56 self.formencode_obj = formencode_obj
56 self.formencode_obj = formencode_obj
57 self.args = args
57 self.args = args
58 self.kwargs = kwargs
58 self.kwargs = kwargs
59
59
60 def __call__(self, *args, **kwargs):
60 def __call__(self, *args, **kwargs):
61 from inspect import isfunction
61 from inspect import isfunction
62 formencode_obj = self.formencode_obj
62 formencode_obj = self.formencode_obj
63 if isfunction(formencode_obj):
63 if isfunction(formencode_obj):
64 # case we wrap validators into functions
64 # case we wrap validators into functions
65 formencode_obj = self.formencode_obj(*args, **kwargs)
65 formencode_obj = self.formencode_obj(*args, **kwargs)
66 return formencode_obj(*self.args, **self.kwargs)
66 return formencode_obj(*self.args, **self.kwargs)
67
67
68
68
69 class RhodeCodeAuthPluginBase(object):
69 class RhodeCodeAuthPluginBase(object):
70 # cache the authentication request for N amount of seconds. Some kind
70 # cache the authentication request for N amount of seconds. Some kind
71 # of authentication methods are very heavy and it's very efficient to cache
71 # of authentication methods are very heavy and it's very efficient to cache
72 # the result of a call. If it's set to None (default) cache is off
72 # the result of a call. If it's set to None (default) cache is off
73 AUTH_CACHE_TTL = None
73 AUTH_CACHE_TTL = None
74 AUTH_CACHE = {}
74 AUTH_CACHE = {}
75
75
76 auth_func_attrs = {
76 auth_func_attrs = {
77 "username": "unique username",
77 "username": "unique username",
78 "firstname": "first name",
78 "firstname": "first name",
79 "lastname": "last name",
79 "lastname": "last name",
80 "email": "email address",
80 "email": "email address",
81 "groups": '["list", "of", "groups"]',
81 "groups": '["list", "of", "groups"]',
82 "extern_name": "name in external source of record",
82 "extern_name": "name in external source of record",
83 "extern_type": "type of external source of record",
83 "extern_type": "type of external source of record",
84 "admin": 'True|False defines if user should be RhodeCode super admin',
84 "admin": 'True|False defines if user should be RhodeCode super admin',
85 "active":
85 "active":
86 'True|False defines active state of user internally for RhodeCode',
86 'True|False defines active state of user internally for RhodeCode',
87 "active_from_extern":
87 "active_from_extern":
88 "True|False\None, active state from the external auth, "
88 "True|False\None, active state from the external auth, "
89 "None means use definition from RhodeCode extern_type active value"
89 "None means use definition from RhodeCode extern_type active value"
90 }
90 }
91 # set on authenticate() method and via set_auth_type func.
91 # set on authenticate() method and via set_auth_type func.
92 auth_type = None
92 auth_type = None
93
93
94 # List of setting names to store encrypted. Plugins may override this list
94 # List of setting names to store encrypted. Plugins may override this list
95 # to store settings encrypted.
95 # to store settings encrypted.
96 _settings_encrypted = []
96 _settings_encrypted = []
97
97
98 # Mapping of python to DB settings model types. Plugins may override or
98 # Mapping of python to DB settings model types. Plugins may override or
99 # extend this mapping.
99 # extend this mapping.
100 _settings_type_map = {
100 _settings_type_map = {
101 colander.String: 'unicode',
101 colander.String: 'unicode',
102 colander.Integer: 'int',
102 colander.Integer: 'int',
103 colander.Boolean: 'bool',
103 colander.Boolean: 'bool',
104 colander.List: 'list',
104 colander.List: 'list',
105 }
105 }
106
106
107 def __init__(self, plugin_id):
107 def __init__(self, plugin_id):
108 self._plugin_id = plugin_id
108 self._plugin_id = plugin_id
109
109
110 def __str__(self):
110 def __str__(self):
111 return self.get_id()
111 return self.get_id()
112
112
113 def _get_setting_full_name(self, name):
113 def _get_setting_full_name(self, name):
114 """
114 """
115 Return the full setting name used for storing values in the database.
115 Return the full setting name used for storing values in the database.
116 """
116 """
117 # TODO: johbo: Using the name here is problematic. It would be good to
117 # TODO: johbo: Using the name here is problematic. It would be good to
118 # introduce either new models in the database to hold Plugin and
118 # introduce either new models in the database to hold Plugin and
119 # PluginSetting or to use the plugin id here.
119 # PluginSetting or to use the plugin id here.
120 return 'auth_{}_{}'.format(self.name, name)
120 return 'auth_{}_{}'.format(self.name, name)
121
121
122 def _get_setting_type(self, name):
122 def _get_setting_type(self, name):
123 """
123 """
124 Return the type of a setting. This type is defined by the SettingsModel
124 Return the type of a setting. This type is defined by the SettingsModel
125 and determines how the setting is stored in DB. Optionally the suffix
125 and determines how the setting is stored in DB. Optionally the suffix
126 `.encrypted` is appended to instruct SettingsModel to store it
126 `.encrypted` is appended to instruct SettingsModel to store it
127 encrypted.
127 encrypted.
128 """
128 """
129 schema_node = self.get_settings_schema().get(name)
129 schema_node = self.get_settings_schema().get(name)
130 db_type = self._settings_type_map.get(
130 db_type = self._settings_type_map.get(
131 type(schema_node.typ), 'unicode')
131 type(schema_node.typ), 'unicode')
132 if name in self._settings_encrypted:
132 if name in self._settings_encrypted:
133 db_type = '{}.encrypted'.format(db_type)
133 db_type = '{}.encrypted'.format(db_type)
134 return db_type
134 return db_type
135
135
136 def is_enabled(self):
136 def is_enabled(self):
137 """
137 """
138 Returns true if this plugin is enabled. An enabled plugin can be
138 Returns true if this plugin is enabled. An enabled plugin can be
139 configured in the admin interface but it is not consulted during
139 configured in the admin interface but it is not consulted during
140 authentication.
140 authentication.
141 """
141 """
142 auth_plugins = SettingsModel().get_auth_plugins()
142 auth_plugins = SettingsModel().get_auth_plugins()
143 return self.get_id() in auth_plugins
143 return self.get_id() in auth_plugins
144
144
145 def is_active(self):
145 def is_active(self):
146 """
146 """
147 Returns true if the plugin is activated. An activated plugin is
147 Returns true if the plugin is activated. An activated plugin is
148 consulted during authentication, assumed it is also enabled.
148 consulted during authentication, assumed it is also enabled.
149 """
149 """
150 return self.get_setting_by_name('enabled')
150 return self.get_setting_by_name('enabled')
151
151
152 def get_id(self):
152 def get_id(self):
153 """
153 """
154 Returns the plugin id.
154 Returns the plugin id.
155 """
155 """
156 return self._plugin_id
156 return self._plugin_id
157
157
158 def get_display_name(self):
158 def get_display_name(self):
159 """
159 """
160 Returns a translation string for displaying purposes.
160 Returns a translation string for displaying purposes.
161 """
161 """
162 raise NotImplementedError('Not implemented in base class')
162 raise NotImplementedError('Not implemented in base class')
163
163
164 def get_settings_schema(self):
164 def get_settings_schema(self):
165 """
165 """
166 Returns a colander schema, representing the plugin settings.
166 Returns a colander schema, representing the plugin settings.
167 """
167 """
168 return AuthnPluginSettingsSchemaBase()
168 return AuthnPluginSettingsSchemaBase()
169
169
170 def get_setting_by_name(self, name, default=None):
170 def get_setting_by_name(self, name, default=None):
171 """
171 """
172 Returns a plugin setting by name.
172 Returns a plugin setting by name.
173 """
173 """
174 full_name = self._get_setting_full_name(name)
174 full_name = self._get_setting_full_name(name)
175 db_setting = SettingsModel().get_setting_by_name(full_name)
175 db_setting = SettingsModel().get_setting_by_name(full_name)
176 return db_setting.app_settings_value if db_setting else default
176 return db_setting.app_settings_value if db_setting else default
177
177
178 def create_or_update_setting(self, name, value):
178 def create_or_update_setting(self, name, value):
179 """
179 """
180 Create or update a setting for this plugin in the persistent storage.
180 Create or update a setting for this plugin in the persistent storage.
181 """
181 """
182 full_name = self._get_setting_full_name(name)
182 full_name = self._get_setting_full_name(name)
183 type_ = self._get_setting_type(name)
183 type_ = self._get_setting_type(name)
184 db_setting = SettingsModel().create_or_update_setting(
184 db_setting = SettingsModel().create_or_update_setting(
185 full_name, value, type_)
185 full_name, value, type_)
186 return db_setting.app_settings_value
186 return db_setting.app_settings_value
187
187
188 def get_settings(self):
188 def get_settings(self):
189 """
189 """
190 Returns the plugin settings as dictionary.
190 Returns the plugin settings as dictionary.
191 """
191 """
192 settings = {}
192 settings = {}
193 for node in self.get_settings_schema():
193 for node in self.get_settings_schema():
194 settings[node.name] = self.get_setting_by_name(node.name)
194 settings[node.name] = self.get_setting_by_name(node.name)
195 return settings
195 return settings
196
196
197 @property
197 @property
198 def validators(self):
198 def validators(self):
199 """
199 """
200 Exposes RhodeCode validators modules
200 Exposes RhodeCode validators modules
201 """
201 """
202 # this is a hack to overcome issues with pylons threadlocals and
202 # this is a hack to overcome issues with pylons threadlocals and
203 # translator object _() not beein registered properly.
203 # translator object _() not beein registered properly.
204 class LazyCaller(object):
204 class LazyCaller(object):
205 def __init__(self, name):
205 def __init__(self, name):
206 self.validator_name = name
206 self.validator_name = name
207
207
208 def __call__(self, *args, **kwargs):
208 def __call__(self, *args, **kwargs):
209 from rhodecode.model import validators as v
209 from rhodecode.model import validators as v
210 obj = getattr(v, self.validator_name)
210 obj = getattr(v, self.validator_name)
211 # log.debug('Initializing lazy formencode object: %s', obj)
211 # log.debug('Initializing lazy formencode object: %s', obj)
212 return LazyFormencode(obj, *args, **kwargs)
212 return LazyFormencode(obj, *args, **kwargs)
213
213
214 class ProxyGet(object):
214 class ProxyGet(object):
215 def __getattribute__(self, name):
215 def __getattribute__(self, name):
216 return LazyCaller(name)
216 return LazyCaller(name)
217
217
218 return ProxyGet()
218 return ProxyGet()
219
219
220 @hybrid_property
220 @hybrid_property
221 def name(self):
221 def name(self):
222 """
222 """
223 Returns the name of this authentication plugin.
223 Returns the name of this authentication plugin.
224
224
225 :returns: string
225 :returns: string
226 """
226 """
227 raise NotImplementedError("Not implemented in base class")
227 raise NotImplementedError("Not implemented in base class")
228
228
229 @property
229 @property
230 def is_headers_auth(self):
230 def is_headers_auth(self):
231 """
231 """
232 Returns True if this authentication plugin uses HTTP headers as
232 Returns True if this authentication plugin uses HTTP headers as
233 authentication method.
233 authentication method.
234 """
234 """
235 return False
235 return False
236
236
237 @hybrid_property
237 @hybrid_property
238 def is_container_auth(self):
238 def is_container_auth(self):
239 """
239 """
240 Deprecated method that indicates if this authentication plugin uses
240 Deprecated method that indicates if this authentication plugin uses
241 HTTP headers as authentication method.
241 HTTP headers as authentication method.
242 """
242 """
243 warnings.warn(
243 warnings.warn(
244 'Use is_headers_auth instead.', category=DeprecationWarning)
244 'Use is_headers_auth instead.', category=DeprecationWarning)
245 return self.is_headers_auth
245 return self.is_headers_auth
246
246
247 @hybrid_property
247 @hybrid_property
248 def allows_creating_users(self):
248 def allows_creating_users(self):
249 """
249 """
250 Defines if Plugin allows users to be created on-the-fly when
250 Defines if Plugin allows users to be created on-the-fly when
251 authentication is called. Controls how external plugins should behave
251 authentication is called. Controls how external plugins should behave
252 in terms if they are allowed to create new users, or not. Base plugins
252 in terms if they are allowed to create new users, or not. Base plugins
253 should not be allowed to, but External ones should be !
253 should not be allowed to, but External ones should be !
254
254
255 :return: bool
255 :return: bool
256 """
256 """
257 return False
257 return False
258
258
259 def set_auth_type(self, auth_type):
259 def set_auth_type(self, auth_type):
260 self.auth_type = auth_type
260 self.auth_type = auth_type
261
261
262 def allows_authentication_from(
262 def allows_authentication_from(
263 self, user, allows_non_existing_user=True,
263 self, user, allows_non_existing_user=True,
264 allowed_auth_plugins=None, allowed_auth_sources=None):
264 allowed_auth_plugins=None, allowed_auth_sources=None):
265 """
265 """
266 Checks if this authentication module should accept a request for
266 Checks if this authentication module should accept a request for
267 the current user.
267 the current user.
268
268
269 :param user: user object fetched using plugin's get_user() method.
269 :param user: user object fetched using plugin's get_user() method.
270 :param allows_non_existing_user: if True, don't allow the
270 :param allows_non_existing_user: if True, don't allow the
271 user to be empty, meaning not existing in our database
271 user to be empty, meaning not existing in our database
272 :param allowed_auth_plugins: if provided, users extern_type will be
272 :param allowed_auth_plugins: if provided, users extern_type will be
273 checked against a list of provided extern types, which are plugin
273 checked against a list of provided extern types, which are plugin
274 auth_names in the end
274 auth_names in the end
275 :param allowed_auth_sources: authentication type allowed,
275 :param allowed_auth_sources: authentication type allowed,
276 `http` or `vcs` default is both.
276 `http` or `vcs` default is both.
277 defines if plugin will accept only http authentication vcs
277 defines if plugin will accept only http authentication vcs
278 authentication(git/hg) or both
278 authentication(git/hg) or both
279 :returns: boolean
279 :returns: boolean
280 """
280 """
281 if not user and not allows_non_existing_user:
281 if not user and not allows_non_existing_user:
282 log.debug('User is empty but plugin does not allow empty users,'
282 log.debug('User is empty but plugin does not allow empty users,'
283 'not allowed to authenticate')
283 'not allowed to authenticate')
284 return False
284 return False
285
285
286 expected_auth_plugins = allowed_auth_plugins or [self.name]
286 expected_auth_plugins = allowed_auth_plugins or [self.name]
287 if user and (user.extern_type and
287 if user and (user.extern_type and
288 user.extern_type not in expected_auth_plugins):
288 user.extern_type not in expected_auth_plugins):
289 log.debug(
289 log.debug(
290 'User `%s` is bound to `%s` auth type. Plugin allows only '
290 'User `%s` is bound to `%s` auth type. Plugin allows only '
291 '%s, skipping', user, user.extern_type, expected_auth_plugins)
291 '%s, skipping', user, user.extern_type, expected_auth_plugins)
292
292
293 return False
293 return False
294
294
295 # by default accept both
295 # by default accept both
296 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
296 expected_auth_from = allowed_auth_sources or [HTTP_TYPE, VCS_TYPE]
297 if self.auth_type not in expected_auth_from:
297 if self.auth_type not in expected_auth_from:
298 log.debug('Current auth source is %s but plugin only allows %s',
298 log.debug('Current auth source is %s but plugin only allows %s',
299 self.auth_type, expected_auth_from)
299 self.auth_type, expected_auth_from)
300 return False
300 return False
301
301
302 return True
302 return True
303
303
304 def get_user(self, username=None, **kwargs):
304 def get_user(self, username=None, **kwargs):
305 """
305 """
306 Helper method for user fetching in plugins, by default it's using
306 Helper method for user fetching in plugins, by default it's using
307 simple fetch by username, but this method can be custimized in plugins
307 simple fetch by username, but this method can be custimized in plugins
308 eg. headers auth plugin to fetch user by environ params
308 eg. headers auth plugin to fetch user by environ params
309
309
310 :param username: username if given to fetch from database
310 :param username: username if given to fetch from database
311 :param kwargs: extra arguments needed for user fetching.
311 :param kwargs: extra arguments needed for user fetching.
312 """
312 """
313 user = None
313 user = None
314 log.debug(
314 log.debug(
315 'Trying to fetch user `%s` from RhodeCode database', username)
315 'Trying to fetch user `%s` from RhodeCode database', username)
316 if username:
316 if username:
317 user = User.get_by_username(username)
317 user = User.get_by_username(username)
318 if not user:
318 if not user:
319 log.debug('User not found, fallback to fetch user in '
319 log.debug('User not found, fallback to fetch user in '
320 'case insensitive mode')
320 'case insensitive mode')
321 user = User.get_by_username(username, case_insensitive=True)
321 user = User.get_by_username(username, case_insensitive=True)
322 else:
322 else:
323 log.debug('provided username:`%s` is empty skipping...', username)
323 log.debug('provided username:`%s` is empty skipping...', username)
324 if not user:
324 if not user:
325 log.debug('User `%s` not found in database', username)
325 log.debug('User `%s` not found in database', username)
326 return user
326 return user
327
327
328 def user_activation_state(self):
328 def user_activation_state(self):
329 """
329 """
330 Defines user activation state when creating new users
330 Defines user activation state when creating new users
331
331
332 :returns: boolean
332 :returns: boolean
333 """
333 """
334 raise NotImplementedError("Not implemented in base class")
334 raise NotImplementedError("Not implemented in base class")
335
335
336 def auth(self, userobj, username, passwd, settings, **kwargs):
336 def auth(self, userobj, username, passwd, settings, **kwargs):
337 """
337 """
338 Given a user object (which may be null), username, a plaintext
338 Given a user object (which may be null), username, a plaintext
339 password, and a settings object (containing all the keys needed as
339 password, and a settings object (containing all the keys needed as
340 listed in settings()), authenticate this user's login attempt.
340 listed in settings()), authenticate this user's login attempt.
341
341
342 Return None on failure. On success, return a dictionary of the form:
342 Return None on failure. On success, return a dictionary of the form:
343
343
344 see: RhodeCodeAuthPluginBase.auth_func_attrs
344 see: RhodeCodeAuthPluginBase.auth_func_attrs
345 This is later validated for correctness
345 This is later validated for correctness
346 """
346 """
347 raise NotImplementedError("not implemented in base class")
347 raise NotImplementedError("not implemented in base class")
348
348
349 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
349 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
350 """
350 """
351 Wrapper to call self.auth() that validates call on it
351 Wrapper to call self.auth() that validates call on it
352
352
353 :param userobj: userobj
353 :param userobj: userobj
354 :param username: username
354 :param username: username
355 :param passwd: plaintext password
355 :param passwd: plaintext password
356 :param settings: plugin settings
356 :param settings: plugin settings
357 """
357 """
358 auth = self.auth(userobj, username, passwd, settings, **kwargs)
358 auth = self.auth(userobj, username, passwd, settings, **kwargs)
359 if auth:
359 if auth:
360 # check if hash should be migrated ?
360 # check if hash should be migrated ?
361 new_hash = auth.get('_hash_migrate')
361 new_hash = auth.get('_hash_migrate')
362 if new_hash:
362 if new_hash:
363 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
363 self._migrate_hash_to_bcrypt(username, passwd, new_hash)
364 return self._validate_auth_return(auth)
364 return self._validate_auth_return(auth)
365 return auth
365 return auth
366
366
367 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
367 def _migrate_hash_to_bcrypt(self, username, password, new_hash):
368 new_hash_cypher = _RhodeCodeCryptoBCrypt()
368 new_hash_cypher = _RhodeCodeCryptoBCrypt()
369 # extra checks, so make sure new hash is correct.
369 # extra checks, so make sure new hash is correct.
370 password_encoded = safe_str(password)
370 password_encoded = safe_str(password)
371 if new_hash and new_hash_cypher.hash_check(
371 if new_hash and new_hash_cypher.hash_check(
372 password_encoded, new_hash):
372 password_encoded, new_hash):
373 cur_user = User.get_by_username(username)
373 cur_user = User.get_by_username(username)
374 cur_user.password = new_hash
374 cur_user.password = new_hash
375 Session().add(cur_user)
375 Session().add(cur_user)
376 Session().flush()
376 Session().flush()
377 log.info('Migrated user %s hash to bcrypt', cur_user)
377 log.info('Migrated user %s hash to bcrypt', cur_user)
378
378
379 def _validate_auth_return(self, ret):
379 def _validate_auth_return(self, ret):
380 if not isinstance(ret, dict):
380 if not isinstance(ret, dict):
381 raise Exception('returned value from auth must be a dict')
381 raise Exception('returned value from auth must be a dict')
382 for k in self.auth_func_attrs:
382 for k in self.auth_func_attrs:
383 if k not in ret:
383 if k not in ret:
384 raise Exception('Missing %s attribute from returned data' % k)
384 raise Exception('Missing %s attribute from returned data' % k)
385 return ret
385 return ret
386
386
387
387
388 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
388 class RhodeCodeExternalAuthPlugin(RhodeCodeAuthPluginBase):
389
389
390 @hybrid_property
390 @hybrid_property
391 def allows_creating_users(self):
391 def allows_creating_users(self):
392 return True
392 return True
393
393
394 def use_fake_password(self):
394 def use_fake_password(self):
395 """
395 """
396 Return a boolean that indicates whether or not we should set the user's
396 Return a boolean that indicates whether or not we should set the user's
397 password to a random value when it is authenticated by this plugin.
397 password to a random value when it is authenticated by this plugin.
398 If your plugin provides authentication, then you will generally
398 If your plugin provides authentication, then you will generally
399 want this.
399 want this.
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 _authenticate(self, userobj, username, passwd, settings, **kwargs):
405 def _authenticate(self, userobj, username, passwd, settings, **kwargs):
406 # at this point _authenticate calls plugin's `auth()` function
406 # at this point _authenticate calls plugin's `auth()` function
407 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
407 auth = super(RhodeCodeExternalAuthPlugin, self)._authenticate(
408 userobj, username, passwd, settings, **kwargs)
408 userobj, username, passwd, settings, **kwargs)
409 if auth:
409 if auth:
410 # maybe plugin will clean the username ?
410 # maybe plugin will clean the username ?
411 # we should use the return value
411 # we should use the return value
412 username = auth['username']
412 username = auth['username']
413
413
414 # if external source tells us that user is not active, we should
414 # if external source tells us that user is not active, we should
415 # skip rest of the process. This can prevent from creating users in
415 # skip rest of the process. This can prevent from creating users in
416 # RhodeCode when using external authentication, but if it's
416 # RhodeCode when using external authentication, but if it's
417 # inactive user we shouldn't create that user anyway
417 # inactive user we shouldn't create that user anyway
418 if auth['active_from_extern'] is False:
418 if auth['active_from_extern'] is False:
419 log.warning(
419 log.warning(
420 "User %s authenticated against %s, but is inactive",
420 "User %s authenticated against %s, but is inactive",
421 username, self.__module__)
421 username, self.__module__)
422 return None
422 return None
423
423
424 cur_user = User.get_by_username(username, case_insensitive=True)
424 cur_user = User.get_by_username(username, case_insensitive=True)
425 is_user_existing = cur_user is not None
425 is_user_existing = cur_user is not None
426
426
427 if is_user_existing:
427 if is_user_existing:
428 log.debug('Syncing user `%s` from '
428 log.debug('Syncing user `%s` from '
429 '`%s` plugin', username, self.name)
429 '`%s` plugin', username, self.name)
430 else:
430 else:
431 log.debug('Creating non existing user `%s` from '
431 log.debug('Creating non existing user `%s` from '
432 '`%s` plugin', username, self.name)
432 '`%s` plugin', username, self.name)
433
433
434 if self.allows_creating_users:
434 if self.allows_creating_users:
435 log.debug('Plugin `%s` allows to '
435 log.debug('Plugin `%s` allows to '
436 'create new users', self.name)
436 'create new users', self.name)
437 else:
437 else:
438 log.debug('Plugin `%s` does not allow to '
438 log.debug('Plugin `%s` does not allow to '
439 'create new users', self.name)
439 'create new users', self.name)
440
440
441 user_parameters = {
441 user_parameters = {
442 'username': username,
442 'username': username,
443 'email': auth["email"],
443 'email': auth["email"],
444 'firstname': auth["firstname"],
444 'firstname': auth["firstname"],
445 'lastname': auth["lastname"],
445 'lastname': auth["lastname"],
446 'active': auth["active"],
446 'active': auth["active"],
447 'admin': auth["admin"],
447 'admin': auth["admin"],
448 'extern_name': auth["extern_name"],
448 'extern_name': auth["extern_name"],
449 'extern_type': self.name,
449 'extern_type': self.name,
450 'plugin': self,
450 'plugin': self,
451 'allow_to_create_user': self.allows_creating_users,
451 'allow_to_create_user': self.allows_creating_users,
452 }
452 }
453
453
454 if not is_user_existing:
454 if not is_user_existing:
455 if self.use_fake_password():
455 if self.use_fake_password():
456 # Randomize the PW because we don't need it, but don't want
456 # Randomize the PW because we don't need it, but don't want
457 # them blank either
457 # them blank either
458 passwd = PasswordGenerator().gen_password(length=16)
458 passwd = PasswordGenerator().gen_password(length=16)
459 user_parameters['password'] = passwd
459 user_parameters['password'] = passwd
460 else:
460 else:
461 # Since the password is required by create_or_update method of
461 # Since the password is required by create_or_update method of
462 # UserModel, we need to set it explicitly.
462 # UserModel, we need to set it explicitly.
463 # The create_or_update method is smart and recognises the
463 # The create_or_update method is smart and recognises the
464 # password hashes as well.
464 # password hashes as well.
465 user_parameters['password'] = cur_user.password
465 user_parameters['password'] = cur_user.password
466
466
467 # we either create or update users, we also pass the flag
467 # we either create or update users, we also pass the flag
468 # that controls if this method can actually do that.
468 # that controls if this method can actually do that.
469 # raises NotAllowedToCreateUserError if it cannot, and we try to.
469 # raises NotAllowedToCreateUserError if it cannot, and we try to.
470 user = UserModel().create_or_update(**user_parameters)
470 user = UserModel().create_or_update(**user_parameters)
471 Session().flush()
471 Session().flush()
472 # enforce user is just in given groups, all of them has to be ones
472 # enforce user is just in given groups, all of them has to be ones
473 # created from plugins. We store this info in _group_data JSON
473 # created from plugins. We store this info in _group_data JSON
474 # field
474 # field
475 try:
475 try:
476 groups = auth['groups'] or []
476 groups = auth['groups'] or []
477 UserGroupModel().enforce_groups(user, groups, self.name)
477 UserGroupModel().enforce_groups(user, groups, self.name)
478 except Exception:
478 except Exception:
479 # for any reason group syncing fails, we should
479 # for any reason group syncing fails, we should
480 # proceed with login
480 # proceed with login
481 log.error(traceback.format_exc())
481 log.error(traceback.format_exc())
482 Session().commit()
482 Session().commit()
483 return auth
483 return auth
484
484
485
485
486 def loadplugin(plugin_id):
486 def loadplugin(plugin_id):
487 """
487 """
488 Loads and returns an instantiated authentication plugin.
488 Loads and returns an instantiated authentication plugin.
489 Returns the RhodeCodeAuthPluginBase subclass on success,
489 Returns the RhodeCodeAuthPluginBase subclass on success,
490 or None on failure.
490 or None on failure.
491 """
491 """
492 # TODO: Disusing pyramids thread locals to retrieve the registry.
492 # TODO: Disusing pyramids thread locals to retrieve the registry.
493 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
493 authn_registry = get_authn_registry()
494 plugin = authn_registry.get_plugin(plugin_id)
494 plugin = authn_registry.get_plugin(plugin_id)
495 if plugin is None:
495 if plugin is None:
496 log.error('Authentication plugin not found: "%s"', plugin_id)
496 log.error('Authentication plugin not found: "%s"', plugin_id)
497 return plugin
497 return plugin
498
498
499
499
500 def get_authn_registry(registry=None):
501 registry = registry or get_current_registry()
502 authn_registry = registry.getUtility(IAuthnPluginRegistry)
503 return authn_registry
504
505
500 def get_auth_cache_manager(custom_ttl=None):
506 def get_auth_cache_manager(custom_ttl=None):
501 return caches.get_cache_manager(
507 return caches.get_cache_manager(
502 'auth_plugins', 'rhodecode.authentication', custom_ttl)
508 'auth_plugins', 'rhodecode.authentication', custom_ttl)
503
509
504
510
505 def authenticate(username, password, environ=None, auth_type=None,
511 def authenticate(username, password, environ=None, auth_type=None,
506 skip_missing=False):
512 skip_missing=False):
507 """
513 """
508 Authentication function used for access control,
514 Authentication function used for access control,
509 It tries to authenticate based on enabled authentication modules.
515 It tries to authenticate based on enabled authentication modules.
510
516
511 :param username: username can be empty for headers auth
517 :param username: username can be empty for headers auth
512 :param password: password can be empty for headers auth
518 :param password: password can be empty for headers auth
513 :param environ: environ headers passed for headers auth
519 :param environ: environ headers passed for headers auth
514 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
520 :param auth_type: type of authentication, either `HTTP_TYPE` or `VCS_TYPE`
515 :param skip_missing: ignores plugins that are in db but not in environment
521 :param skip_missing: ignores plugins that are in db but not in environment
516 :returns: None if auth failed, plugin_user dict if auth is correct
522 :returns: None if auth failed, plugin_user dict if auth is correct
517 """
523 """
518 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
524 if not auth_type or auth_type not in [HTTP_TYPE, VCS_TYPE]:
519 raise ValueError('auth type must be on of http, vcs got "%s" instead'
525 raise ValueError('auth type must be on of http, vcs got "%s" instead'
520 % auth_type)
526 % auth_type)
521 headers_only = environ and not (username and password)
527 headers_only = environ and not (username and password)
522
528
523 authn_registry = get_current_registry().getUtility(IAuthnPluginRegistry)
529 authn_registry = get_authn_registry()
524 for plugin in authn_registry.get_plugins_for_authentication():
530 for plugin in authn_registry.get_plugins_for_authentication():
525 plugin.set_auth_type(auth_type)
531 plugin.set_auth_type(auth_type)
526 user = plugin.get_user(username)
532 user = plugin.get_user(username)
527 display_user = user.username if user else username
533 display_user = user.username if user else username
528
534
529 if headers_only and not plugin.is_headers_auth:
535 if headers_only and not plugin.is_headers_auth:
530 log.debug('Auth type is for headers only and plugin `%s` is not '
536 log.debug('Auth type is for headers only and plugin `%s` is not '
531 'headers plugin, skipping...', plugin.get_id())
537 'headers plugin, skipping...', plugin.get_id())
532 continue
538 continue
533
539
534 # load plugin settings from RhodeCode database
540 # load plugin settings from RhodeCode database
535 plugin_settings = plugin.get_settings()
541 plugin_settings = plugin.get_settings()
536 log.debug('Plugin settings:%s', plugin_settings)
542 log.debug('Plugin settings:%s', plugin_settings)
537
543
538 log.debug('Trying authentication using ** %s **', plugin.get_id())
544 log.debug('Trying authentication using ** %s **', plugin.get_id())
539 # use plugin's method of user extraction.
545 # use plugin's method of user extraction.
540 user = plugin.get_user(username, environ=environ,
546 user = plugin.get_user(username, environ=environ,
541 settings=plugin_settings)
547 settings=plugin_settings)
542 display_user = user.username if user else username
548 display_user = user.username if user else username
543 log.debug(
549 log.debug(
544 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
550 'Plugin %s extracted user is `%s`', plugin.get_id(), display_user)
545
551
546 if not plugin.allows_authentication_from(user):
552 if not plugin.allows_authentication_from(user):
547 log.debug('Plugin %s does not accept user `%s` for authentication',
553 log.debug('Plugin %s does not accept user `%s` for authentication',
548 plugin.get_id(), display_user)
554 plugin.get_id(), display_user)
549 continue
555 continue
550 else:
556 else:
551 log.debug('Plugin %s accepted user `%s` for authentication',
557 log.debug('Plugin %s accepted user `%s` for authentication',
552 plugin.get_id(), display_user)
558 plugin.get_id(), display_user)
553
559
554 log.info('Authenticating user `%s` using %s plugin',
560 log.info('Authenticating user `%s` using %s plugin',
555 display_user, plugin.get_id())
561 display_user, plugin.get_id())
556
562
557 _cache_ttl = 0
563 _cache_ttl = 0
558
564
559 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
565 if isinstance(plugin.AUTH_CACHE_TTL, (int, long)):
560 # plugin cache set inside is more important than the settings value
566 # plugin cache set inside is more important than the settings value
561 _cache_ttl = plugin.AUTH_CACHE_TTL
567 _cache_ttl = plugin.AUTH_CACHE_TTL
562 elif plugin_settings.get('auth_cache_ttl'):
568 elif plugin_settings.get('auth_cache_ttl'):
563 _cache_ttl = safe_int(plugin_settings.get('auth_cache_ttl'), 0)
569 _cache_ttl = safe_int(plugin_settings.get('auth_cache_ttl'), 0)
564
570
565 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
571 plugin_cache_active = bool(_cache_ttl and _cache_ttl > 0)
566
572
567 # get instance of cache manager configured for a namespace
573 # get instance of cache manager configured for a namespace
568 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
574 cache_manager = get_auth_cache_manager(custom_ttl=_cache_ttl)
569
575
570 log.debug('Cache for plugin `%s` active: %s', plugin.get_id(),
576 log.debug('Cache for plugin `%s` active: %s', plugin.get_id(),
571 plugin_cache_active)
577 plugin_cache_active)
572
578
573 # for environ based password can be empty, but then the validation is
579 # for environ based password can be empty, but then the validation is
574 # on the server that fills in the env data needed for authentication
580 # on the server that fills in the env data needed for authentication
575 _password_hash = md5_safe(plugin.name + username + (password or ''))
581 _password_hash = md5_safe(plugin.name + username + (password or ''))
576
582
577 # _authenticate is a wrapper for .auth() method of plugin.
583 # _authenticate is a wrapper for .auth() method of plugin.
578 # it checks if .auth() sends proper data.
584 # it checks if .auth() sends proper data.
579 # For RhodeCodeExternalAuthPlugin it also maps users to
585 # For RhodeCodeExternalAuthPlugin it also maps users to
580 # Database and maps the attributes returned from .auth()
586 # Database and maps the attributes returned from .auth()
581 # to RhodeCode database. If this function returns data
587 # to RhodeCode database. If this function returns data
582 # then auth is correct.
588 # then auth is correct.
583 start = time.time()
589 start = time.time()
584 log.debug('Running plugin `%s` _authenticate method',
590 log.debug('Running plugin `%s` _authenticate method',
585 plugin.get_id())
591 plugin.get_id())
586
592
587 def auth_func():
593 def auth_func():
588 """
594 """
589 This function is used internally in Cache of Beaker to calculate
595 This function is used internally in Cache of Beaker to calculate
590 Results
596 Results
591 """
597 """
592 return plugin._authenticate(
598 return plugin._authenticate(
593 user, username, password, plugin_settings,
599 user, username, password, plugin_settings,
594 environ=environ or {})
600 environ=environ or {})
595
601
596 if plugin_cache_active:
602 if plugin_cache_active:
597 plugin_user = cache_manager.get(
603 plugin_user = cache_manager.get(
598 _password_hash, createfunc=auth_func)
604 _password_hash, createfunc=auth_func)
599 else:
605 else:
600 plugin_user = auth_func()
606 plugin_user = auth_func()
601
607
602 auth_time = time.time() - start
608 auth_time = time.time() - start
603 log.debug('Authentication for plugin `%s` completed in %.3fs, '
609 log.debug('Authentication for plugin `%s` completed in %.3fs, '
604 'expiration time of fetched cache %.1fs.',
610 'expiration time of fetched cache %.1fs.',
605 plugin.get_id(), auth_time, _cache_ttl)
611 plugin.get_id(), auth_time, _cache_ttl)
606
612
607 log.debug('PLUGIN USER DATA: %s', plugin_user)
613 log.debug('PLUGIN USER DATA: %s', plugin_user)
608
614
609 if plugin_user:
615 if plugin_user:
610 log.debug('Plugin returned proper authentication data')
616 log.debug('Plugin returned proper authentication data')
611 return plugin_user
617 return plugin_user
612 # we failed to Auth because .auth() method didn't return proper user
618 # we failed to Auth because .auth() method didn't return proper user
613 log.debug("User `%s` failed to authenticate against %s",
619 log.debug("User `%s` failed to authenticate against %s",
614 display_user, plugin.get_id())
620 display_user, plugin.get_id())
615 return None
621 return None
@@ -1,136 +1,140 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2016 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 RhodeCode authentication token plugin for built in internal auth
22 RhodeCode authentication token plugin for built in internal auth
23 """
23 """
24
24
25 import logging
25 import logging
26
26
27 from sqlalchemy.ext.hybrid import hybrid_property
27 from sqlalchemy.ext.hybrid import hybrid_property
28
28
29 from rhodecode.translation import _
29 from rhodecode.translation import _
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, VCS_TYPE
30 from rhodecode.authentication.base import RhodeCodeAuthPluginBase, VCS_TYPE
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
31 from rhodecode.authentication.routes import AuthnPluginResourceBase
32 from rhodecode.model.db import User, UserApiKeys
32 from rhodecode.model.db import User, UserApiKeys
33
33
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 def plugin_factory(plugin_id, *args, **kwds):
38 def plugin_factory(plugin_id, *args, **kwds):
39 plugin = RhodeCodeAuthPlugin(plugin_id)
39 plugin = RhodeCodeAuthPlugin(plugin_id)
40 return plugin
40 return plugin
41
41
42
42
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
43 class RhodecodeAuthnResource(AuthnPluginResourceBase):
44 pass
44 pass
45
45
46
46
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
47 class RhodeCodeAuthPlugin(RhodeCodeAuthPluginBase):
48 """
48 """
49 Enables usage of authentication tokens for vcs operations.
49 Enables usage of authentication tokens for vcs operations.
50 """
50 """
51
51
52 def includeme(self, config):
52 def includeme(self, config):
53 config.add_authn_plugin(self)
53 config.add_authn_plugin(self)
54 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
54 config.add_authn_resource(self.get_id(), RhodecodeAuthnResource(self))
55 config.add_view(
55 config.add_view(
56 'rhodecode.authentication.views.AuthnPluginViewBase',
56 'rhodecode.authentication.views.AuthnPluginViewBase',
57 attr='settings_get',
57 attr='settings_get',
58 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
58 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
59 request_method='GET',
59 request_method='GET',
60 route_name='auth_home',
60 route_name='auth_home',
61 context=RhodecodeAuthnResource)
61 context=RhodecodeAuthnResource)
62 config.add_view(
62 config.add_view(
63 'rhodecode.authentication.views.AuthnPluginViewBase',
63 'rhodecode.authentication.views.AuthnPluginViewBase',
64 attr='settings_post',
64 attr='settings_post',
65 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
65 renderer='rhodecode:templates/admin/auth/plugin_settings.html',
66 request_method='POST',
66 request_method='POST',
67 route_name='auth_home',
67 route_name='auth_home',
68 context=RhodecodeAuthnResource)
68 context=RhodecodeAuthnResource)
69
69
70 def get_display_name(self):
70 def get_display_name(self):
71 return _('Rhodecode Token Auth')
71 return _('Rhodecode Token Auth')
72
72
73 @hybrid_property
73 @hybrid_property
74 def name(self):
74 def name(self):
75 return "authtoken"
75 return "authtoken"
76
76
77 def user_activation_state(self):
77 def user_activation_state(self):
78 def_user_perms = User.get_default_user().AuthUser.permissions['global']
78 def_user_perms = User.get_default_user().AuthUser.permissions['global']
79 return 'hg.register.auto_activate' in def_user_perms
79 return 'hg.register.auto_activate' in def_user_perms
80
80
81 def allows_authentication_from(
81 def allows_authentication_from(
82 self, user, allows_non_existing_user=True,
82 self, user, allows_non_existing_user=True,
83 allowed_auth_plugins=None, allowed_auth_sources=None):
83 allowed_auth_plugins=None, allowed_auth_sources=None):
84 """
84 """
85 Custom method for this auth that doesn't accept empty users. And also
85 Custom method for this auth that doesn't accept empty users. And also
86 allows rhodecode and authtoken extern_type to auth with this. But only
86 allows users from all other active plugins to use it and also
87 via vcs mode
87 authenticate against it. But only via vcs mode
88 """
88 """
89 # only this and rhodecode plugins can use this type
89 from rhodecode.authentication.base import get_authn_registry
90 from rhodecode.authentication.plugins import auth_rhodecode
90 authn_registry = get_authn_registry()
91 allowed_auth_plugins = [
91
92 self.name, auth_rhodecode.RhodeCodeAuthPlugin.name]
92 active_plugins = set(
93 [x.name for x in authn_registry.get_plugins_for_authentication()])
94 active_plugins.discard(self.name)
95
96 allowed_auth_plugins = [self.name] + list(active_plugins)
93 # only for vcs operations
97 # only for vcs operations
94 allowed_auth_sources = [VCS_TYPE]
98 allowed_auth_sources = [VCS_TYPE]
95
99
96 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
100 return super(RhodeCodeAuthPlugin, self).allows_authentication_from(
97 user, allows_non_existing_user=False,
101 user, allows_non_existing_user=False,
98 allowed_auth_plugins=allowed_auth_plugins,
102 allowed_auth_plugins=allowed_auth_plugins,
99 allowed_auth_sources=allowed_auth_sources)
103 allowed_auth_sources=allowed_auth_sources)
100
104
101 def auth(self, userobj, username, password, settings, **kwargs):
105 def auth(self, userobj, username, password, settings, **kwargs):
102 if not userobj:
106 if not userobj:
103 log.debug('userobj was:%s skipping' % (userobj, ))
107 log.debug('userobj was:%s skipping' % (userobj, ))
104 return None
108 return None
105
109
106 user_attrs = {
110 user_attrs = {
107 "username": userobj.username,
111 "username": userobj.username,
108 "firstname": userobj.firstname,
112 "firstname": userobj.firstname,
109 "lastname": userobj.lastname,
113 "lastname": userobj.lastname,
110 "groups": [],
114 "groups": [],
111 "email": userobj.email,
115 "email": userobj.email,
112 "admin": userobj.admin,
116 "admin": userobj.admin,
113 "active": userobj.active,
117 "active": userobj.active,
114 "active_from_extern": userobj.active,
118 "active_from_extern": userobj.active,
115 "extern_name": userobj.user_id,
119 "extern_name": userobj.user_id,
116 "extern_type": userobj.extern_type,
120 "extern_type": userobj.extern_type,
117 }
121 }
118
122
119 log.debug('Authenticating user with args %s', user_attrs)
123 log.debug('Authenticating user with args %s', user_attrs)
120 if userobj.active:
124 if userobj.active:
121 role = UserApiKeys.ROLE_VCS
125 role = UserApiKeys.ROLE_VCS
122 active_tokens = [x.api_key for x in
126 active_tokens = [x.api_key for x in
123 User.extra_valid_auth_tokens(userobj, role=role)]
127 User.extra_valid_auth_tokens(userobj, role=role)]
124 if userobj.username == username and password in active_tokens:
128 if userobj.username == username and password in active_tokens:
125 log.info(
129 log.info(
126 'user `%s` successfully authenticated via %s',
130 'user `%s` successfully authenticated via %s',
127 user_attrs['username'], self.name)
131 user_attrs['username'], self.name)
128 return user_attrs
132 return user_attrs
129 log.error(
133 log.error(
130 'user `%s` failed to authenticate via %s, reason: bad or '
134 'user `%s` failed to authenticate via %s, reason: bad or '
131 'inactive token.', username, self.name)
135 'inactive token.', username, self.name)
132 else:
136 else:
133 log.warning(
137 log.warning(
134 'user `%s` failed to authenticate via %s, reason: account not '
138 'user `%s` failed to authenticate via %s, reason: account not '
135 'active.', username, self.name)
139 'active.', username, self.name)
136 return None
140 return None
@@ -1,192 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 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 import colander
21 import colander
22 import formencode.htmlfill
22 import formencode.htmlfill
23 import logging
23 import logging
24
24
25 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
26 from pyramid.renderers import render
26 from pyramid.renderers import render
27 from pyramid.response import Response
27 from pyramid.response import Response
28
28
29 from rhodecode.authentication.base import get_auth_cache_manager
29 from rhodecode.authentication.base import (
30 from rhodecode.authentication.interface import IAuthnPluginRegistry
30 get_auth_cache_manager, get_authn_registry)
31 from rhodecode.lib import auth
31 from rhodecode.lib import auth
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 from rhodecode.model.forms import AuthSettingsForm
33 from rhodecode.model.forms import AuthSettingsForm
34 from rhodecode.model.meta import Session
34 from rhodecode.model.meta import Session
35 from rhodecode.model.settings import SettingsModel
35 from rhodecode.model.settings import SettingsModel
36 from rhodecode.translation import _
36 from rhodecode.translation import _
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class AuthnPluginViewBase(object):
41 class AuthnPluginViewBase(object):
42
42
43 def __init__(self, context, request):
43 def __init__(self, context, request):
44 self.request = request
44 self.request = request
45 self.context = context
45 self.context = context
46 self.plugin = context.plugin
46 self.plugin = context.plugin
47 self._rhodecode_user = request.user
47 self._rhodecode_user = request.user
48
48
49 @LoginRequired()
49 @LoginRequired()
50 @HasPermissionAllDecorator('hg.admin')
50 @HasPermissionAllDecorator('hg.admin')
51 def settings_get(self, defaults=None, errors=None):
51 def settings_get(self, defaults=None, errors=None):
52 """
52 """
53 View that displays the plugin settings as a form.
53 View that displays the plugin settings as a form.
54 """
54 """
55 defaults = defaults or {}
55 defaults = defaults or {}
56 errors = errors or {}
56 errors = errors or {}
57 schema = self.plugin.get_settings_schema()
57 schema = self.plugin.get_settings_schema()
58
58
59 # Compute default values for the form. Priority is:
59 # Compute default values for the form. Priority is:
60 # 1. Passed to this method 2. DB value 3. Schema default
60 # 1. Passed to this method 2. DB value 3. Schema default
61 for node in schema:
61 for node in schema:
62 if node.name not in defaults:
62 if node.name not in defaults:
63 defaults[node.name] = self.plugin.get_setting_by_name(
63 defaults[node.name] = self.plugin.get_setting_by_name(
64 node.name, node.default)
64 node.name, node.default)
65
65
66 template_context = {
66 template_context = {
67 'defaults': defaults,
67 'defaults': defaults,
68 'errors': errors,
68 'errors': errors,
69 'plugin': self.context.plugin,
69 'plugin': self.context.plugin,
70 'resource': self.context,
70 'resource': self.context,
71 }
71 }
72
72
73 return template_context
73 return template_context
74
74
75 @LoginRequired()
75 @LoginRequired()
76 @HasPermissionAllDecorator('hg.admin')
76 @HasPermissionAllDecorator('hg.admin')
77 @auth.CSRFRequired()
77 @auth.CSRFRequired()
78 def settings_post(self):
78 def settings_post(self):
79 """
79 """
80 View that validates and stores the plugin settings.
80 View that validates and stores the plugin settings.
81 """
81 """
82 schema = self.plugin.get_settings_schema()
82 schema = self.plugin.get_settings_schema()
83 data = self.request.params
83 data = self.request.params
84
84
85 try:
85 try:
86 valid_data = schema.deserialize(data)
86 valid_data = schema.deserialize(data)
87 except colander.Invalid, e:
87 except colander.Invalid, e:
88 # Display error message and display form again.
88 # Display error message and display form again.
89 self.request.session.flash(
89 self.request.session.flash(
90 _('Errors exist when saving plugin settings. '
90 _('Errors exist when saving plugin settings. '
91 'Please check the form inputs.'),
91 'Please check the form inputs.'),
92 queue='error')
92 queue='error')
93 defaults = {key: data[key] for key in data if key in schema}
93 defaults = {key: data[key] for key in data if key in schema}
94 return self.settings_get(errors=e.asdict(), defaults=defaults)
94 return self.settings_get(errors=e.asdict(), defaults=defaults)
95
95
96 # Store validated data.
96 # Store validated data.
97 for name, value in valid_data.items():
97 for name, value in valid_data.items():
98 self.plugin.create_or_update_setting(name, value)
98 self.plugin.create_or_update_setting(name, value)
99 Session.commit()
99 Session.commit()
100
100
101 # Display success message and redirect.
101 # Display success message and redirect.
102 self.request.session.flash(
102 self.request.session.flash(
103 _('Auth settings updated successfully.'),
103 _('Auth settings updated successfully.'),
104 queue='success')
104 queue='success')
105 redirect_to = self.request.resource_path(
105 redirect_to = self.request.resource_path(
106 self.context, route_name='auth_home')
106 self.context, route_name='auth_home')
107 return HTTPFound(redirect_to)
107 return HTTPFound(redirect_to)
108
108
109
109
110 # TODO: Ongoing migration in these views.
110 # TODO: Ongoing migration in these views.
111 # - Maybe we should also use a colander schema for these views.
111 # - Maybe we should also use a colander schema for these views.
112 class AuthSettingsView(object):
112 class AuthSettingsView(object):
113 def __init__(self, context, request):
113 def __init__(self, context, request):
114 self.context = context
114 self.context = context
115 self.request = request
115 self.request = request
116
116
117 # TODO: Move this into a utility function. It is needed in all view
117 # TODO: Move this into a utility function. It is needed in all view
118 # classes during migration. Maybe a mixin?
118 # classes during migration. Maybe a mixin?
119
119
120 # Some of the decorators rely on this attribute to be present on the
120 # Some of the decorators rely on this attribute to be present on the
121 # class of the decorated method.
121 # class of the decorated method.
122 self._rhodecode_user = request.user
122 self._rhodecode_user = request.user
123
123
124 @LoginRequired()
124 @LoginRequired()
125 @HasPermissionAllDecorator('hg.admin')
125 @HasPermissionAllDecorator('hg.admin')
126 def index(self, defaults=None, errors=None, prefix_error=False):
126 def index(self, defaults=None, errors=None, prefix_error=False):
127 defaults = defaults or {}
127 defaults = defaults or {}
128 authn_registry = self.request.registry.getUtility(IAuthnPluginRegistry)
128 authn_registry = get_authn_registry(self.request.registry)
129 enabled_plugins = SettingsModel().get_auth_plugins()
129 enabled_plugins = SettingsModel().get_auth_plugins()
130
130
131 # Create template context and render it.
131 # Create template context and render it.
132 template_context = {
132 template_context = {
133 'resource': self.context,
133 'resource': self.context,
134 'available_plugins': authn_registry.get_plugins(),
134 'available_plugins': authn_registry.get_plugins(),
135 'enabled_plugins': enabled_plugins,
135 'enabled_plugins': enabled_plugins,
136 }
136 }
137 html = render('rhodecode:templates/admin/auth/auth_settings.html',
137 html = render('rhodecode:templates/admin/auth/auth_settings.html',
138 template_context,
138 template_context,
139 request=self.request)
139 request=self.request)
140
140
141 # Create form default values and fill the form.
141 # Create form default values and fill the form.
142 form_defaults = {
142 form_defaults = {
143 'auth_plugins': ','.join(enabled_plugins)
143 'auth_plugins': ','.join(enabled_plugins)
144 }
144 }
145 form_defaults.update(defaults)
145 form_defaults.update(defaults)
146 html = formencode.htmlfill.render(
146 html = formencode.htmlfill.render(
147 html,
147 html,
148 defaults=form_defaults,
148 defaults=form_defaults,
149 errors=errors,
149 errors=errors,
150 prefix_error=prefix_error,
150 prefix_error=prefix_error,
151 encoding="UTF-8",
151 encoding="UTF-8",
152 force_defaults=False)
152 force_defaults=False)
153
153
154 return Response(html)
154 return Response(html)
155
155
156 @LoginRequired()
156 @LoginRequired()
157 @HasPermissionAllDecorator('hg.admin')
157 @HasPermissionAllDecorator('hg.admin')
158 @auth.CSRFRequired()
158 @auth.CSRFRequired()
159 def auth_settings(self):
159 def auth_settings(self):
160 try:
160 try:
161 form = AuthSettingsForm()()
161 form = AuthSettingsForm()()
162 form_result = form.to_python(self.request.params)
162 form_result = form.to_python(self.request.params)
163 plugins = ','.join(form_result['auth_plugins'])
163 plugins = ','.join(form_result['auth_plugins'])
164 setting = SettingsModel().create_or_update_setting(
164 setting = SettingsModel().create_or_update_setting(
165 'auth_plugins', plugins)
165 'auth_plugins', plugins)
166 Session().add(setting)
166 Session().add(setting)
167 Session().commit()
167 Session().commit()
168
168
169 cache_manager = get_auth_cache_manager()
169 cache_manager = get_auth_cache_manager()
170 cache_manager.clear()
170 cache_manager.clear()
171 self.request.session.flash(
171 self.request.session.flash(
172 _('Auth settings updated successfully.'),
172 _('Auth settings updated successfully.'),
173 queue='success')
173 queue='success')
174 except formencode.Invalid as errors:
174 except formencode.Invalid as errors:
175 e = errors.error_dict or {}
175 e = errors.error_dict or {}
176 self.request.session.flash(
176 self.request.session.flash(
177 _('Errors exist when saving plugin setting. '
177 _('Errors exist when saving plugin setting. '
178 'Please check the form inputs.'),
178 'Please check the form inputs.'),
179 queue='error')
179 queue='error')
180 return self.index(
180 return self.index(
181 defaults=errors.value,
181 defaults=errors.value,
182 errors=e,
182 errors=e,
183 prefix_error=False)
183 prefix_error=False)
184 except Exception:
184 except Exception:
185 log.exception('Exception in auth_settings')
185 log.exception('Exception in auth_settings')
186 self.request.session.flash(
186 self.request.session.flash(
187 _('Error occurred during update of auth settings.'),
187 _('Error occurred during update of auth settings.'),
188 queue='error')
188 queue='error')
189
189
190 redirect_to = self.request.resource_path(
190 redirect_to = self.request.resource_path(
191 self.context, route_name='auth_home')
191 self.context, route_name='auth_home')
192 return HTTPFound(redirect_to)
192 return HTTPFound(redirect_to)
General Comments 0
You need to be logged in to leave comments. Login now