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