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