##// END OF EJS Templates
auth: make the store_user_in_session public, as it's used in external auth plugins.
marcink -
r3250:7ab39ef0 default
parent child Browse files
Show More
@@ -1,461 +1,461 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2018 RhodeCode GmbH
3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import collections
22 import collections
23 import datetime
23 import datetime
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import logging
26 import logging
27 import urlparse
27 import urlparse
28 import requests
28 import requests
29
29
30 from pyramid.httpexceptions import HTTPFound
30 from pyramid.httpexceptions import HTTPFound
31 from pyramid.view import view_config
31 from pyramid.view import view_config
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
35 from rhodecode.events import UserRegistered, trigger
35 from rhodecode.events import UserRegistered, trigger
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib import audit_logger
37 from rhodecode.lib import audit_logger
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
39 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 from rhodecode.lib.base import get_ip_addr
40 from rhodecode.lib.base import get_ip_addr
41 from rhodecode.lib.exceptions import UserCreationError
41 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.model.db import User, UserApiKeys
43 from rhodecode.model.db import User, UserApiKeys
44 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
44 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.auth_token import AuthTokenModel
46 from rhodecode.model.auth_token import AuthTokenModel
47 from rhodecode.model.settings import SettingsModel
47 from rhodecode.model.settings import SettingsModel
48 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.translation import _
49 from rhodecode.translation import _
50
50
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54 CaptchaData = collections.namedtuple(
54 CaptchaData = collections.namedtuple(
55 'CaptchaData', 'active, private_key, public_key')
55 'CaptchaData', 'active, private_key, public_key')
56
56
57
57
58 def _store_user_in_session(session, username, remember=False):
58 def store_user_in_session(session, username, remember=False):
59 user = User.get_by_username(username, case_insensitive=True)
59 user = User.get_by_username(username, case_insensitive=True)
60 auth_user = AuthUser(user.user_id)
60 auth_user = AuthUser(user.user_id)
61 auth_user.set_authenticated()
61 auth_user.set_authenticated()
62 cs = auth_user.get_cookie_store()
62 cs = auth_user.get_cookie_store()
63 session['rhodecode_user'] = cs
63 session['rhodecode_user'] = cs
64 user.update_lastlogin()
64 user.update_lastlogin()
65 Session().commit()
65 Session().commit()
66
66
67 # If they want to be remembered, update the cookie
67 # If they want to be remembered, update the cookie
68 if remember:
68 if remember:
69 _year = (datetime.datetime.now() +
69 _year = (datetime.datetime.now() +
70 datetime.timedelta(seconds=60 * 60 * 24 * 365))
70 datetime.timedelta(seconds=60 * 60 * 24 * 365))
71 session._set_cookie_expires(_year)
71 session._set_cookie_expires(_year)
72
72
73 session.save()
73 session.save()
74
74
75 safe_cs = cs.copy()
75 safe_cs = cs.copy()
76 safe_cs['password'] = '****'
76 safe_cs['password'] = '****'
77 log.info('user %s is now authenticated and stored in '
77 log.info('user %s is now authenticated and stored in '
78 'session, session attrs %s', username, safe_cs)
78 'session, session attrs %s', username, safe_cs)
79
79
80 # dumps session attrs back to cookie
80 # dumps session attrs back to cookie
81 session._update_cookie_out()
81 session._update_cookie_out()
82 # we set new cookie
82 # we set new cookie
83 headers = None
83 headers = None
84 if session.request['set_cookie']:
84 if session.request['set_cookie']:
85 # send set-cookie headers back to response to update cookie
85 # send set-cookie headers back to response to update cookie
86 headers = [('Set-Cookie', session.request['cookie_out'])]
86 headers = [('Set-Cookie', session.request['cookie_out'])]
87 return headers
87 return headers
88
88
89
89
90 def get_came_from(request):
90 def get_came_from(request):
91 came_from = safe_str(request.GET.get('came_from', ''))
91 came_from = safe_str(request.GET.get('came_from', ''))
92 parsed = urlparse.urlparse(came_from)
92 parsed = urlparse.urlparse(came_from)
93 allowed_schemes = ['http', 'https']
93 allowed_schemes = ['http', 'https']
94 default_came_from = h.route_path('home')
94 default_came_from = h.route_path('home')
95 if parsed.scheme and parsed.scheme not in allowed_schemes:
95 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 log.error('Suspicious URL scheme detected %s for url %s',
96 log.error('Suspicious URL scheme detected %s for url %s',
97 parsed.scheme, parsed)
97 parsed.scheme, parsed)
98 came_from = default_came_from
98 came_from = default_came_from
99 elif parsed.netloc and request.host != parsed.netloc:
99 elif parsed.netloc and request.host != parsed.netloc:
100 log.error('Suspicious NETLOC detected %s for url %s server url '
100 log.error('Suspicious NETLOC detected %s for url %s server url '
101 'is: %s', parsed.netloc, parsed, request.host)
101 'is: %s', parsed.netloc, parsed, request.host)
102 came_from = default_came_from
102 came_from = default_came_from
103 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
103 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 log.error('Header injection detected `%s` for url %s server url ',
104 log.error('Header injection detected `%s` for url %s server url ',
105 parsed.path, parsed)
105 parsed.path, parsed)
106 came_from = default_came_from
106 came_from = default_came_from
107
107
108 return came_from or default_came_from
108 return came_from or default_came_from
109
109
110
110
111 class LoginView(BaseAppView):
111 class LoginView(BaseAppView):
112
112
113 def load_default_context(self):
113 def load_default_context(self):
114 c = self._get_local_tmpl_context()
114 c = self._get_local_tmpl_context()
115 c.came_from = get_came_from(self.request)
115 c.came_from = get_came_from(self.request)
116
116
117 return c
117 return c
118
118
119 def _get_captcha_data(self):
119 def _get_captcha_data(self):
120 settings = SettingsModel().get_all_settings()
120 settings = SettingsModel().get_all_settings()
121 private_key = settings.get('rhodecode_captcha_private_key')
121 private_key = settings.get('rhodecode_captcha_private_key')
122 public_key = settings.get('rhodecode_captcha_public_key')
122 public_key = settings.get('rhodecode_captcha_public_key')
123 active = bool(private_key)
123 active = bool(private_key)
124 return CaptchaData(
124 return CaptchaData(
125 active=active, private_key=private_key, public_key=public_key)
125 active=active, private_key=private_key, public_key=public_key)
126
126
127 def validate_captcha(self, private_key):
127 def validate_captcha(self, private_key):
128
128
129 captcha_rs = self.request.POST.get('g-recaptcha-response')
129 captcha_rs = self.request.POST.get('g-recaptcha-response')
130 url = "https://www.google.com/recaptcha/api/siteverify"
130 url = "https://www.google.com/recaptcha/api/siteverify"
131 params = {
131 params = {
132 'secret': private_key,
132 'secret': private_key,
133 'response': captcha_rs,
133 'response': captcha_rs,
134 'remoteip': get_ip_addr(self.request.environ)
134 'remoteip': get_ip_addr(self.request.environ)
135 }
135 }
136 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
136 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
137 verify_rs = verify_rs.json()
137 verify_rs = verify_rs.json()
138 captcha_status = verify_rs.get('success', False)
138 captcha_status = verify_rs.get('success', False)
139 captcha_errors = verify_rs.get('error-codes', [])
139 captcha_errors = verify_rs.get('error-codes', [])
140 if not isinstance(captcha_errors, list):
140 if not isinstance(captcha_errors, list):
141 captcha_errors = [captcha_errors]
141 captcha_errors = [captcha_errors]
142 captcha_errors = ', '.join(captcha_errors)
142 captcha_errors = ', '.join(captcha_errors)
143 captcha_message = ''
143 captcha_message = ''
144 if captcha_status is False:
144 if captcha_status is False:
145 captcha_message = "Bad captcha. Errors: {}".format(
145 captcha_message = "Bad captcha. Errors: {}".format(
146 captcha_errors)
146 captcha_errors)
147
147
148 return captcha_status, captcha_message
148 return captcha_status, captcha_message
149
149
150 @view_config(
150 @view_config(
151 route_name='login', request_method='GET',
151 route_name='login', request_method='GET',
152 renderer='rhodecode:templates/login.mako')
152 renderer='rhodecode:templates/login.mako')
153 def login(self):
153 def login(self):
154 c = self.load_default_context()
154 c = self.load_default_context()
155 auth_user = self._rhodecode_user
155 auth_user = self._rhodecode_user
156
156
157 # redirect if already logged in
157 # redirect if already logged in
158 if (auth_user.is_authenticated and
158 if (auth_user.is_authenticated and
159 not auth_user.is_default and auth_user.ip_allowed):
159 not auth_user.is_default and auth_user.ip_allowed):
160 raise HTTPFound(c.came_from)
160 raise HTTPFound(c.came_from)
161
161
162 # check if we use headers plugin, and try to login using it.
162 # check if we use headers plugin, and try to login using it.
163 try:
163 try:
164 log.debug('Running PRE-AUTH for headers based authentication')
164 log.debug('Running PRE-AUTH for headers based authentication')
165 auth_info = authenticate(
165 auth_info = authenticate(
166 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
166 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
167 if auth_info:
167 if auth_info:
168 headers = _store_user_in_session(
168 headers = store_user_in_session(
169 self.session, auth_info.get('username'))
169 self.session, auth_info.get('username'))
170 raise HTTPFound(c.came_from, headers=headers)
170 raise HTTPFound(c.came_from, headers=headers)
171 except UserCreationError as e:
171 except UserCreationError as e:
172 log.error(e)
172 log.error(e)
173 h.flash(e, category='error')
173 h.flash(e, category='error')
174
174
175 return self._get_template_context(c)
175 return self._get_template_context(c)
176
176
177 @view_config(
177 @view_config(
178 route_name='login', request_method='POST',
178 route_name='login', request_method='POST',
179 renderer='rhodecode:templates/login.mako')
179 renderer='rhodecode:templates/login.mako')
180 def login_post(self):
180 def login_post(self):
181 c = self.load_default_context()
181 c = self.load_default_context()
182
182
183 login_form = LoginForm(self.request.translate)()
183 login_form = LoginForm(self.request.translate)()
184
184
185 try:
185 try:
186 self.session.invalidate()
186 self.session.invalidate()
187 form_result = login_form.to_python(self.request.POST)
187 form_result = login_form.to_python(self.request.POST)
188 # form checks for username/password, now we're authenticated
188 # form checks for username/password, now we're authenticated
189 headers = _store_user_in_session(
189 headers = store_user_in_session(
190 self.session,
190 self.session,
191 username=form_result['username'],
191 username=form_result['username'],
192 remember=form_result['remember'])
192 remember=form_result['remember'])
193 log.debug('Redirecting to "%s" after login.', c.came_from)
193 log.debug('Redirecting to "%s" after login.', c.came_from)
194
194
195 audit_user = audit_logger.UserWrap(
195 audit_user = audit_logger.UserWrap(
196 username=self.request.POST.get('username'),
196 username=self.request.POST.get('username'),
197 ip_addr=self.request.remote_addr)
197 ip_addr=self.request.remote_addr)
198 action_data = {'user_agent': self.request.user_agent}
198 action_data = {'user_agent': self.request.user_agent}
199 audit_logger.store_web(
199 audit_logger.store_web(
200 'user.login.success', action_data=action_data,
200 'user.login.success', action_data=action_data,
201 user=audit_user, commit=True)
201 user=audit_user, commit=True)
202
202
203 raise HTTPFound(c.came_from, headers=headers)
203 raise HTTPFound(c.came_from, headers=headers)
204 except formencode.Invalid as errors:
204 except formencode.Invalid as errors:
205 defaults = errors.value
205 defaults = errors.value
206 # remove password from filling in form again
206 # remove password from filling in form again
207 defaults.pop('password', None)
207 defaults.pop('password', None)
208 render_ctx = {
208 render_ctx = {
209 'errors': errors.error_dict,
209 'errors': errors.error_dict,
210 'defaults': defaults,
210 'defaults': defaults,
211 }
211 }
212
212
213 audit_user = audit_logger.UserWrap(
213 audit_user = audit_logger.UserWrap(
214 username=self.request.POST.get('username'),
214 username=self.request.POST.get('username'),
215 ip_addr=self.request.remote_addr)
215 ip_addr=self.request.remote_addr)
216 action_data = {'user_agent': self.request.user_agent}
216 action_data = {'user_agent': self.request.user_agent}
217 audit_logger.store_web(
217 audit_logger.store_web(
218 'user.login.failure', action_data=action_data,
218 'user.login.failure', action_data=action_data,
219 user=audit_user, commit=True)
219 user=audit_user, commit=True)
220 return self._get_template_context(c, **render_ctx)
220 return self._get_template_context(c, **render_ctx)
221
221
222 except UserCreationError as e:
222 except UserCreationError as e:
223 # headers auth or other auth functions that create users on
223 # headers auth or other auth functions that create users on
224 # the fly can throw this exception signaling that there's issue
224 # the fly can throw this exception signaling that there's issue
225 # with user creation, explanation should be provided in
225 # with user creation, explanation should be provided in
226 # Exception itself
226 # Exception itself
227 h.flash(e, category='error')
227 h.flash(e, category='error')
228 return self._get_template_context(c)
228 return self._get_template_context(c)
229
229
230 @CSRFRequired()
230 @CSRFRequired()
231 @view_config(route_name='logout', request_method='POST')
231 @view_config(route_name='logout', request_method='POST')
232 def logout(self):
232 def logout(self):
233 auth_user = self._rhodecode_user
233 auth_user = self._rhodecode_user
234 log.info('Deleting session for user: `%s`', auth_user)
234 log.info('Deleting session for user: `%s`', auth_user)
235
235
236 action_data = {'user_agent': self.request.user_agent}
236 action_data = {'user_agent': self.request.user_agent}
237 audit_logger.store_web(
237 audit_logger.store_web(
238 'user.logout', action_data=action_data,
238 'user.logout', action_data=action_data,
239 user=auth_user, commit=True)
239 user=auth_user, commit=True)
240 self.session.delete()
240 self.session.delete()
241 return HTTPFound(h.route_path('home'))
241 return HTTPFound(h.route_path('home'))
242
242
243 @HasPermissionAnyDecorator(
243 @HasPermissionAnyDecorator(
244 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
244 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
245 @view_config(
245 @view_config(
246 route_name='register', request_method='GET',
246 route_name='register', request_method='GET',
247 renderer='rhodecode:templates/register.mako',)
247 renderer='rhodecode:templates/register.mako',)
248 def register(self, defaults=None, errors=None):
248 def register(self, defaults=None, errors=None):
249 c = self.load_default_context()
249 c = self.load_default_context()
250 defaults = defaults or {}
250 defaults = defaults or {}
251 errors = errors or {}
251 errors = errors or {}
252
252
253 settings = SettingsModel().get_all_settings()
253 settings = SettingsModel().get_all_settings()
254 register_message = settings.get('rhodecode_register_message') or ''
254 register_message = settings.get('rhodecode_register_message') or ''
255 captcha = self._get_captcha_data()
255 captcha = self._get_captcha_data()
256 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
256 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
257 .AuthUser().permissions['global']
257 .AuthUser().permissions['global']
258
258
259 render_ctx = self._get_template_context(c)
259 render_ctx = self._get_template_context(c)
260 render_ctx.update({
260 render_ctx.update({
261 'defaults': defaults,
261 'defaults': defaults,
262 'errors': errors,
262 'errors': errors,
263 'auto_active': auto_active,
263 'auto_active': auto_active,
264 'captcha_active': captcha.active,
264 'captcha_active': captcha.active,
265 'captcha_public_key': captcha.public_key,
265 'captcha_public_key': captcha.public_key,
266 'register_message': register_message,
266 'register_message': register_message,
267 })
267 })
268 return render_ctx
268 return render_ctx
269
269
270 @HasPermissionAnyDecorator(
270 @HasPermissionAnyDecorator(
271 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
271 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
272 @view_config(
272 @view_config(
273 route_name='register', request_method='POST',
273 route_name='register', request_method='POST',
274 renderer='rhodecode:templates/register.mako')
274 renderer='rhodecode:templates/register.mako')
275 def register_post(self):
275 def register_post(self):
276 self.load_default_context()
276 self.load_default_context()
277 captcha = self._get_captcha_data()
277 captcha = self._get_captcha_data()
278 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
278 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
279 .AuthUser().permissions['global']
279 .AuthUser().permissions['global']
280
280
281 register_form = RegisterForm(self.request.translate)()
281 register_form = RegisterForm(self.request.translate)()
282 try:
282 try:
283
283
284 form_result = register_form.to_python(self.request.POST)
284 form_result = register_form.to_python(self.request.POST)
285 form_result['active'] = auto_active
285 form_result['active'] = auto_active
286
286
287 if captcha.active:
287 if captcha.active:
288 captcha_status, captcha_message = self.validate_captcha(
288 captcha_status, captcha_message = self.validate_captcha(
289 captcha.private_key)
289 captcha.private_key)
290
290
291 if not captcha_status:
291 if not captcha_status:
292 _value = form_result
292 _value = form_result
293 _msg = _('Bad captcha')
293 _msg = _('Bad captcha')
294 error_dict = {'recaptcha_field': captcha_message}
294 error_dict = {'recaptcha_field': captcha_message}
295 raise formencode.Invalid(
295 raise formencode.Invalid(
296 _msg, _value, None, error_dict=error_dict)
296 _msg, _value, None, error_dict=error_dict)
297
297
298 new_user = UserModel().create_registration(form_result)
298 new_user = UserModel().create_registration(form_result)
299
299
300 action_data = {'data': new_user.get_api_data(),
300 action_data = {'data': new_user.get_api_data(),
301 'user_agent': self.request.user_agent}
301 'user_agent': self.request.user_agent}
302
302
303 audit_user = audit_logger.UserWrap(
303 audit_user = audit_logger.UserWrap(
304 username=new_user.username,
304 username=new_user.username,
305 user_id=new_user.user_id,
305 user_id=new_user.user_id,
306 ip_addr=self.request.remote_addr)
306 ip_addr=self.request.remote_addr)
307
307
308 audit_logger.store_web(
308 audit_logger.store_web(
309 'user.register', action_data=action_data,
309 'user.register', action_data=action_data,
310 user=audit_user)
310 user=audit_user)
311
311
312 event = UserRegistered(user=new_user, session=self.session)
312 event = UserRegistered(user=new_user, session=self.session)
313 trigger(event)
313 trigger(event)
314 h.flash(
314 h.flash(
315 _('You have successfully registered with RhodeCode'),
315 _('You have successfully registered with RhodeCode'),
316 category='success')
316 category='success')
317 Session().commit()
317 Session().commit()
318
318
319 redirect_ro = self.request.route_path('login')
319 redirect_ro = self.request.route_path('login')
320 raise HTTPFound(redirect_ro)
320 raise HTTPFound(redirect_ro)
321
321
322 except formencode.Invalid as errors:
322 except formencode.Invalid as errors:
323 errors.value.pop('password', None)
323 errors.value.pop('password', None)
324 errors.value.pop('password_confirmation', None)
324 errors.value.pop('password_confirmation', None)
325 return self.register(
325 return self.register(
326 defaults=errors.value, errors=errors.error_dict)
326 defaults=errors.value, errors=errors.error_dict)
327
327
328 except UserCreationError as e:
328 except UserCreationError as e:
329 # container auth or other auth functions that create users on
329 # container auth or other auth functions that create users on
330 # the fly can throw this exception signaling that there's issue
330 # the fly can throw this exception signaling that there's issue
331 # with user creation, explanation should be provided in
331 # with user creation, explanation should be provided in
332 # Exception itself
332 # Exception itself
333 h.flash(e, category='error')
333 h.flash(e, category='error')
334 return self.register()
334 return self.register()
335
335
336 @view_config(
336 @view_config(
337 route_name='reset_password', request_method=('GET', 'POST'),
337 route_name='reset_password', request_method=('GET', 'POST'),
338 renderer='rhodecode:templates/password_reset.mako')
338 renderer='rhodecode:templates/password_reset.mako')
339 def password_reset(self):
339 def password_reset(self):
340 c = self.load_default_context()
340 c = self.load_default_context()
341 captcha = self._get_captcha_data()
341 captcha = self._get_captcha_data()
342
342
343 template_context = {
343 template_context = {
344 'captcha_active': captcha.active,
344 'captcha_active': captcha.active,
345 'captcha_public_key': captcha.public_key,
345 'captcha_public_key': captcha.public_key,
346 'defaults': {},
346 'defaults': {},
347 'errors': {},
347 'errors': {},
348 }
348 }
349
349
350 # always send implicit message to prevent from discovery of
350 # always send implicit message to prevent from discovery of
351 # matching emails
351 # matching emails
352 msg = _('If such email exists, a password reset link was sent to it.')
352 msg = _('If such email exists, a password reset link was sent to it.')
353
353
354 if self.request.POST:
354 if self.request.POST:
355 if h.HasPermissionAny('hg.password_reset.disabled')():
355 if h.HasPermissionAny('hg.password_reset.disabled')():
356 _email = self.request.POST.get('email', '')
356 _email = self.request.POST.get('email', '')
357 log.error('Failed attempt to reset password for `%s`.', _email)
357 log.error('Failed attempt to reset password for `%s`.', _email)
358 h.flash(_('Password reset has been disabled.'),
358 h.flash(_('Password reset has been disabled.'),
359 category='error')
359 category='error')
360 return HTTPFound(self.request.route_path('reset_password'))
360 return HTTPFound(self.request.route_path('reset_password'))
361
361
362 password_reset_form = PasswordResetForm(self.request.translate)()
362 password_reset_form = PasswordResetForm(self.request.translate)()
363 try:
363 try:
364 form_result = password_reset_form.to_python(
364 form_result = password_reset_form.to_python(
365 self.request.POST)
365 self.request.POST)
366 user_email = form_result['email']
366 user_email = form_result['email']
367
367
368 if captcha.active:
368 if captcha.active:
369 captcha_status, captcha_message = self.validate_captcha(
369 captcha_status, captcha_message = self.validate_captcha(
370 captcha.private_key)
370 captcha.private_key)
371
371
372 if not captcha_status:
372 if not captcha_status:
373 _value = form_result
373 _value = form_result
374 _msg = _('Bad captcha')
374 _msg = _('Bad captcha')
375 error_dict = {'recaptcha_field': captcha_message}
375 error_dict = {'recaptcha_field': captcha_message}
376 raise formencode.Invalid(
376 raise formencode.Invalid(
377 _msg, _value, None, error_dict=error_dict)
377 _msg, _value, None, error_dict=error_dict)
378
378
379 # Generate reset URL and send mail.
379 # Generate reset URL and send mail.
380 user = User.get_by_email(user_email)
380 user = User.get_by_email(user_email)
381
381
382 # generate password reset token that expires in 10 minutes
382 # generate password reset token that expires in 10 minutes
383 description = u'Generated token for password reset from {}'.format(
383 description = u'Generated token for password reset from {}'.format(
384 datetime.datetime.now().isoformat())
384 datetime.datetime.now().isoformat())
385
385
386 reset_token = UserModel().add_auth_token(
386 reset_token = UserModel().add_auth_token(
387 user=user, lifetime_minutes=10,
387 user=user, lifetime_minutes=10,
388 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
388 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
389 description=description)
389 description=description)
390 Session().commit()
390 Session().commit()
391
391
392 log.debug('Successfully created password recovery token')
392 log.debug('Successfully created password recovery token')
393 password_reset_url = self.request.route_url(
393 password_reset_url = self.request.route_url(
394 'reset_password_confirmation',
394 'reset_password_confirmation',
395 _query={'key': reset_token.api_key})
395 _query={'key': reset_token.api_key})
396 UserModel().reset_password_link(
396 UserModel().reset_password_link(
397 form_result, password_reset_url)
397 form_result, password_reset_url)
398 # Display success message and redirect.
398 # Display success message and redirect.
399 h.flash(msg, category='success')
399 h.flash(msg, category='success')
400
400
401 action_data = {'email': user_email,
401 action_data = {'email': user_email,
402 'user_agent': self.request.user_agent}
402 'user_agent': self.request.user_agent}
403 audit_logger.store_web(
403 audit_logger.store_web(
404 'user.password.reset_request', action_data=action_data,
404 'user.password.reset_request', action_data=action_data,
405 user=self._rhodecode_user, commit=True)
405 user=self._rhodecode_user, commit=True)
406 return HTTPFound(self.request.route_path('reset_password'))
406 return HTTPFound(self.request.route_path('reset_password'))
407
407
408 except formencode.Invalid as errors:
408 except formencode.Invalid as errors:
409 template_context.update({
409 template_context.update({
410 'defaults': errors.value,
410 'defaults': errors.value,
411 'errors': errors.error_dict,
411 'errors': errors.error_dict,
412 })
412 })
413 if not self.request.POST.get('email'):
413 if not self.request.POST.get('email'):
414 # case of empty email, we want to report that
414 # case of empty email, we want to report that
415 return self._get_template_context(c, **template_context)
415 return self._get_template_context(c, **template_context)
416
416
417 if 'recaptcha_field' in errors.error_dict:
417 if 'recaptcha_field' in errors.error_dict:
418 # case of failed captcha
418 # case of failed captcha
419 return self._get_template_context(c, **template_context)
419 return self._get_template_context(c, **template_context)
420
420
421 log.debug('faking response on invalid password reset')
421 log.debug('faking response on invalid password reset')
422 # make this take 2s, to prevent brute forcing.
422 # make this take 2s, to prevent brute forcing.
423 time.sleep(2)
423 time.sleep(2)
424 h.flash(msg, category='success')
424 h.flash(msg, category='success')
425 return HTTPFound(self.request.route_path('reset_password'))
425 return HTTPFound(self.request.route_path('reset_password'))
426
426
427 return self._get_template_context(c, **template_context)
427 return self._get_template_context(c, **template_context)
428
428
429 @view_config(route_name='reset_password_confirmation',
429 @view_config(route_name='reset_password_confirmation',
430 request_method='GET')
430 request_method='GET')
431 def password_reset_confirmation(self):
431 def password_reset_confirmation(self):
432 self.load_default_context()
432 self.load_default_context()
433 if self.request.GET and self.request.GET.get('key'):
433 if self.request.GET and self.request.GET.get('key'):
434 # make this take 2s, to prevent brute forcing.
434 # make this take 2s, to prevent brute forcing.
435 time.sleep(2)
435 time.sleep(2)
436
436
437 token = AuthTokenModel().get_auth_token(
437 token = AuthTokenModel().get_auth_token(
438 self.request.GET.get('key'))
438 self.request.GET.get('key'))
439
439
440 # verify token is the correct role
440 # verify token is the correct role
441 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
441 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
442 log.debug('Got token with role:%s expected is %s',
442 log.debug('Got token with role:%s expected is %s',
443 getattr(token, 'role', 'EMPTY_TOKEN'),
443 getattr(token, 'role', 'EMPTY_TOKEN'),
444 UserApiKeys.ROLE_PASSWORD_RESET)
444 UserApiKeys.ROLE_PASSWORD_RESET)
445 h.flash(
445 h.flash(
446 _('Given reset token is invalid'), category='error')
446 _('Given reset token is invalid'), category='error')
447 return HTTPFound(self.request.route_path('reset_password'))
447 return HTTPFound(self.request.route_path('reset_password'))
448
448
449 try:
449 try:
450 owner = token.user
450 owner = token.user
451 data = {'email': owner.email, 'token': token.api_key}
451 data = {'email': owner.email, 'token': token.api_key}
452 UserModel().reset_password(data)
452 UserModel().reset_password(data)
453 h.flash(
453 h.flash(
454 _('Your password reset was successful, '
454 _('Your password reset was successful, '
455 'a new password has been sent to your email'),
455 'a new password has been sent to your email'),
456 category='success')
456 category='success')
457 except Exception as e:
457 except Exception as e:
458 log.error(e)
458 log.error(e)
459 return HTTPFound(self.request.route_path('reset_password'))
459 return HTTPFound(self.request.route_path('reset_password'))
460
460
461 return HTTPFound(self.request.route_path('login'))
461 return HTTPFound(self.request.route_path('login'))
General Comments 0
You need to be logged in to leave comments. Login now