##// END OF EJS Templates
login: don't use request.params because it allows to passing multiple...
marcink -
r2149:cae7e0e2 default
parent child Browse files
Show More
@@ -1,426 +1,427 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 formencode.htmlfill
25 import formencode.htmlfill
26 import logging
26 import logging
27 import urlparse
27 import urlparse
28
28
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.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
35 from rhodecode.events import UserRegistered
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 self._register_global_c(c)
116 self._register_global_c(c)
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 @view_config(
127 @view_config(
128 route_name='login', request_method='GET',
128 route_name='login', request_method='GET',
129 renderer='rhodecode:templates/login.mako')
129 renderer='rhodecode:templates/login.mako')
130 def login(self):
130 def login(self):
131 c = self.load_default_context()
131 c = self.load_default_context()
132 auth_user = self._rhodecode_user
132 auth_user = self._rhodecode_user
133
133
134 # redirect if already logged in
134 # redirect if already logged in
135 if (auth_user.is_authenticated and
135 if (auth_user.is_authenticated and
136 not auth_user.is_default and auth_user.ip_allowed):
136 not auth_user.is_default and auth_user.ip_allowed):
137 raise HTTPFound(c.came_from)
137 raise HTTPFound(c.came_from)
138
138
139 # check if we use headers plugin, and try to login using it.
139 # check if we use headers plugin, and try to login using it.
140 try:
140 try:
141 log.debug('Running PRE-AUTH for headers based authentication')
141 log.debug('Running PRE-AUTH for headers based authentication')
142 auth_info = authenticate(
142 auth_info = authenticate(
143 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
143 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
144 if auth_info:
144 if auth_info:
145 headers = _store_user_in_session(
145 headers = _store_user_in_session(
146 self.session, auth_info.get('username'))
146 self.session, auth_info.get('username'))
147 raise HTTPFound(c.came_from, headers=headers)
147 raise HTTPFound(c.came_from, headers=headers)
148 except UserCreationError as e:
148 except UserCreationError as e:
149 log.error(e)
149 log.error(e)
150 self.session.flash(e, queue='error')
150 self.session.flash(e, queue='error')
151
151
152 return self._get_template_context(c)
152 return self._get_template_context(c)
153
153
154 @view_config(
154 @view_config(
155 route_name='login', request_method='POST',
155 route_name='login', request_method='POST',
156 renderer='rhodecode:templates/login.mako')
156 renderer='rhodecode:templates/login.mako')
157 def login_post(self):
157 def login_post(self):
158 c = self.load_default_context()
158 c = self.load_default_context()
159
159
160 login_form = LoginForm()()
160 login_form = LoginForm()()
161
161
162 try:
162 try:
163 self.session.invalidate()
163 self.session.invalidate()
164 form_result = login_form.to_python(self.request.params)
164 form_result = login_form.to_python(self.request.POST)
165 # form checks for username/password, now we're authenticated
165 # form checks for username/password, now we're authenticated
166 headers = _store_user_in_session(
166 headers = _store_user_in_session(
167 self.session,
167 self.session,
168 username=form_result['username'],
168 username=form_result['username'],
169 remember=form_result['remember'])
169 remember=form_result['remember'])
170 log.debug('Redirecting to "%s" after login.', c.came_from)
170 log.debug('Redirecting to "%s" after login.', c.came_from)
171
171
172 audit_user = audit_logger.UserWrap(
172 audit_user = audit_logger.UserWrap(
173 username=self.request.params.get('username'),
173 username=self.request.POST.get('username'),
174 ip_addr=self.request.remote_addr)
174 ip_addr=self.request.remote_addr)
175 action_data = {'user_agent': self.request.user_agent}
175 action_data = {'user_agent': self.request.user_agent}
176 audit_logger.store_web(
176 audit_logger.store_web(
177 'user.login.success', action_data=action_data,
177 'user.login.success', action_data=action_data,
178 user=audit_user, commit=True)
178 user=audit_user, commit=True)
179
179
180 raise HTTPFound(c.came_from, headers=headers)
180 raise HTTPFound(c.came_from, headers=headers)
181 except formencode.Invalid as errors:
181 except formencode.Invalid as errors:
182 defaults = errors.value
182 defaults = errors.value
183 # remove password from filling in form again
183 # remove password from filling in form again
184 defaults.pop('password', None)
184 defaults.pop('password', None)
185 render_ctx = self._get_template_context(c)
185 render_ctx = self._get_template_context(c)
186 render_ctx.update({
186 render_ctx.update({
187 'errors': errors.error_dict,
187 'errors': errors.error_dict,
188 'defaults': defaults,
188 'defaults': defaults,
189 })
189 })
190
190
191 audit_user = audit_logger.UserWrap(
191 audit_user = audit_logger.UserWrap(
192 username=self.request.params.get('username'),
192 username=self.request.POST.get('username'),
193 ip_addr=self.request.remote_addr)
193 ip_addr=self.request.remote_addr)
194 action_data = {'user_agent': self.request.user_agent}
194 action_data = {'user_agent': self.request.user_agent}
195 audit_logger.store_web(
195 audit_logger.store_web(
196 'user.login.failure', action_data=action_data,
196 'user.login.failure', action_data=action_data,
197 user=audit_user, commit=True)
197 user=audit_user, commit=True)
198 return render_ctx
198 return render_ctx
199
199
200 except UserCreationError as e:
200 except UserCreationError as e:
201 # headers auth or other auth functions that create users on
201 # headers auth or other auth functions that create users on
202 # the fly can throw this exception signaling that there's issue
202 # the fly can throw this exception signaling that there's issue
203 # with user creation, explanation should be provided in
203 # with user creation, explanation should be provided in
204 # Exception itself
204 # Exception itself
205 self.session.flash(e, queue='error')
205 self.session.flash(e, queue='error')
206 return self._get_template_context(c)
206 return self._get_template_context(c)
207
207
208 @CSRFRequired()
208 @CSRFRequired()
209 @view_config(route_name='logout', request_method='POST')
209 @view_config(route_name='logout', request_method='POST')
210 def logout(self):
210 def logout(self):
211 auth_user = self._rhodecode_user
211 auth_user = self._rhodecode_user
212 log.info('Deleting session for user: `%s`', auth_user)
212 log.info('Deleting session for user: `%s`', auth_user)
213
213
214 action_data = {'user_agent': self.request.user_agent}
214 action_data = {'user_agent': self.request.user_agent}
215 audit_logger.store_web(
215 audit_logger.store_web(
216 'user.logout', action_data=action_data,
216 'user.logout', action_data=action_data,
217 user=auth_user, commit=True)
217 user=auth_user, commit=True)
218 self.session.delete()
218 self.session.delete()
219 return HTTPFound(h.route_path('home'))
219 return HTTPFound(h.route_path('home'))
220
220
221 @HasPermissionAnyDecorator(
221 @HasPermissionAnyDecorator(
222 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
222 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
223 @view_config(
223 @view_config(
224 route_name='register', request_method='GET',
224 route_name='register', request_method='GET',
225 renderer='rhodecode:templates/register.mako',)
225 renderer='rhodecode:templates/register.mako',)
226 def register(self, defaults=None, errors=None):
226 def register(self, defaults=None, errors=None):
227 c = self.load_default_context()
227 c = self.load_default_context()
228 defaults = defaults or {}
228 defaults = defaults or {}
229 errors = errors or {}
229 errors = errors or {}
230
230
231 settings = SettingsModel().get_all_settings()
231 settings = SettingsModel().get_all_settings()
232 register_message = settings.get('rhodecode_register_message') or ''
232 register_message = settings.get('rhodecode_register_message') or ''
233 captcha = self._get_captcha_data()
233 captcha = self._get_captcha_data()
234 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
234 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
235 .AuthUser().permissions['global']
235 .AuthUser().permissions['global']
236
236
237 render_ctx = self._get_template_context(c)
237 render_ctx = self._get_template_context(c)
238 render_ctx.update({
238 render_ctx.update({
239 'defaults': defaults,
239 'defaults': defaults,
240 'errors': errors,
240 'errors': errors,
241 'auto_active': auto_active,
241 'auto_active': auto_active,
242 'captcha_active': captcha.active,
242 'captcha_active': captcha.active,
243 'captcha_public_key': captcha.public_key,
243 'captcha_public_key': captcha.public_key,
244 'register_message': register_message,
244 'register_message': register_message,
245 })
245 })
246 return render_ctx
246 return render_ctx
247
247
248 @HasPermissionAnyDecorator(
248 @HasPermissionAnyDecorator(
249 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
249 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
250 @view_config(
250 @view_config(
251 route_name='register', request_method='POST',
251 route_name='register', request_method='POST',
252 renderer='rhodecode:templates/register.mako')
252 renderer='rhodecode:templates/register.mako')
253 def register_post(self):
253 def register_post(self):
254 captcha = self._get_captcha_data()
254 captcha = self._get_captcha_data()
255 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
255 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
256 .AuthUser().permissions['global']
256 .AuthUser().permissions['global']
257
257
258 register_form = RegisterForm()()
258 register_form = RegisterForm()()
259 try:
259 try:
260 form_result = register_form.to_python(self.request.params)
260
261 form_result = register_form.to_python(self.request.POST)
261 form_result['active'] = auto_active
262 form_result['active'] = auto_active
262
263
263 if captcha.active:
264 if captcha.active:
264 response = submit(
265 response = submit(
265 self.request.params.get('recaptcha_challenge_field'),
266 self.request.POST.get('recaptcha_challenge_field'),
266 self.request.params.get('recaptcha_response_field'),
267 self.request.POST.get('recaptcha_response_field'),
267 private_key=captcha.private_key,
268 private_key=captcha.private_key,
268 remoteip=get_ip_addr(self.request.environ))
269 remoteip=get_ip_addr(self.request.environ))
269 if not response.is_valid:
270 if not response.is_valid:
270 _value = form_result
271 _value = form_result
271 _msg = _('Bad captcha')
272 _msg = _('Bad captcha')
272 error_dict = {'recaptcha_field': _msg}
273 error_dict = {'recaptcha_field': _msg}
273 raise formencode.Invalid(_msg, _value, None,
274 raise formencode.Invalid(_msg, _value, None,
274 error_dict=error_dict)
275 error_dict=error_dict)
275
276
276 new_user = UserModel().create_registration(form_result)
277 new_user = UserModel().create_registration(form_result)
277 event = UserRegistered(user=new_user, session=self.session)
278 event = UserRegistered(user=new_user, session=self.session)
278 self.request.registry.notify(event)
279 self.request.registry.notify(event)
279 self.session.flash(
280 self.session.flash(
280 _('You have successfully registered with RhodeCode'),
281 _('You have successfully registered with RhodeCode'),
281 queue='success')
282 queue='success')
282 Session().commit()
283 Session().commit()
283
284
284 redirect_ro = self.request.route_path('login')
285 redirect_ro = self.request.route_path('login')
285 raise HTTPFound(redirect_ro)
286 raise HTTPFound(redirect_ro)
286
287
287 except formencode.Invalid as errors:
288 except formencode.Invalid as errors:
288 errors.value.pop('password', None)
289 errors.value.pop('password', None)
289 errors.value.pop('password_confirmation', None)
290 errors.value.pop('password_confirmation', None)
290 return self.register(
291 return self.register(
291 defaults=errors.value, errors=errors.error_dict)
292 defaults=errors.value, errors=errors.error_dict)
292
293
293 except UserCreationError as e:
294 except UserCreationError as e:
294 # container auth or other auth functions that create users on
295 # container auth or other auth functions that create users on
295 # the fly can throw this exception signaling that there's issue
296 # the fly can throw this exception signaling that there's issue
296 # with user creation, explanation should be provided in
297 # with user creation, explanation should be provided in
297 # Exception itself
298 # Exception itself
298 self.session.flash(e, queue='error')
299 self.session.flash(e, queue='error')
299 return self.register()
300 return self.register()
300
301
301 @view_config(
302 @view_config(
302 route_name='reset_password', request_method=('GET', 'POST'),
303 route_name='reset_password', request_method=('GET', 'POST'),
303 renderer='rhodecode:templates/password_reset.mako')
304 renderer='rhodecode:templates/password_reset.mako')
304 def password_reset(self):
305 def password_reset(self):
305 captcha = self._get_captcha_data()
306 captcha = self._get_captcha_data()
306
307
307 render_ctx = {
308 render_ctx = {
308 'captcha_active': captcha.active,
309 'captcha_active': captcha.active,
309 'captcha_public_key': captcha.public_key,
310 'captcha_public_key': captcha.public_key,
310 'defaults': {},
311 'defaults': {},
311 'errors': {},
312 'errors': {},
312 }
313 }
313
314
314 # always send implicit message to prevent from discovery of
315 # always send implicit message to prevent from discovery of
315 # matching emails
316 # matching emails
316 msg = _('If such email exists, a password reset link was sent to it.')
317 msg = _('If such email exists, a password reset link was sent to it.')
317
318
318 if self.request.POST:
319 if self.request.POST:
319 if h.HasPermissionAny('hg.password_reset.disabled')():
320 if h.HasPermissionAny('hg.password_reset.disabled')():
320 _email = self.request.POST.get('email', '')
321 _email = self.request.POST.get('email', '')
321 log.error('Failed attempt to reset password for `%s`.', _email)
322 log.error('Failed attempt to reset password for `%s`.', _email)
322 self.session.flash(_('Password reset has been disabled.'),
323 self.session.flash(_('Password reset has been disabled.'),
323 queue='error')
324 queue='error')
324 return HTTPFound(self.request.route_path('reset_password'))
325 return HTTPFound(self.request.route_path('reset_password'))
325
326
326 password_reset_form = PasswordResetForm()()
327 password_reset_form = PasswordResetForm()()
327 try:
328 try:
328 form_result = password_reset_form.to_python(
329 form_result = password_reset_form.to_python(
329 self.request.params)
330 self.request.POST)
330 user_email = form_result['email']
331 user_email = form_result['email']
331
332
332 if captcha.active:
333 if captcha.active:
333 response = submit(
334 response = submit(
334 self.request.params.get('recaptcha_challenge_field'),
335 self.request.POST.get('recaptcha_challenge_field'),
335 self.request.params.get('recaptcha_response_field'),
336 self.request.POST.get('recaptcha_response_field'),
336 private_key=captcha.private_key,
337 private_key=captcha.private_key,
337 remoteip=get_ip_addr(self.request.environ))
338 remoteip=get_ip_addr(self.request.environ))
338 if not response.is_valid:
339 if not response.is_valid:
339 _value = form_result
340 _value = form_result
340 _msg = _('Bad captcha')
341 _msg = _('Bad captcha')
341 error_dict = {'recaptcha_field': _msg}
342 error_dict = {'recaptcha_field': _msg}
342 raise formencode.Invalid(
343 raise formencode.Invalid(
343 _msg, _value, None, error_dict=error_dict)
344 _msg, _value, None, error_dict=error_dict)
344
345
345 # Generate reset URL and send mail.
346 # Generate reset URL and send mail.
346 user = User.get_by_email(user_email)
347 user = User.get_by_email(user_email)
347
348
348 # generate password reset token that expires in 10minutes
349 # generate password reset token that expires in 10minutes
349 desc = 'Generated token for password reset from {}'.format(
350 desc = 'Generated token for password reset from {}'.format(
350 datetime.datetime.now().isoformat())
351 datetime.datetime.now().isoformat())
351 reset_token = AuthTokenModel().create(
352 reset_token = AuthTokenModel().create(
352 user, lifetime=10,
353 user, lifetime=10,
353 description=desc,
354 description=desc,
354 role=UserApiKeys.ROLE_PASSWORD_RESET)
355 role=UserApiKeys.ROLE_PASSWORD_RESET)
355 Session().commit()
356 Session().commit()
356
357
357 log.debug('Successfully created password recovery token')
358 log.debug('Successfully created password recovery token')
358 password_reset_url = self.request.route_url(
359 password_reset_url = self.request.route_url(
359 'reset_password_confirmation',
360 'reset_password_confirmation',
360 _query={'key': reset_token.api_key})
361 _query={'key': reset_token.api_key})
361 UserModel().reset_password_link(
362 UserModel().reset_password_link(
362 form_result, password_reset_url)
363 form_result, password_reset_url)
363 # Display success message and redirect.
364 # Display success message and redirect.
364 self.session.flash(msg, queue='success')
365 self.session.flash(msg, queue='success')
365
366
366 action_data = {'email': user_email,
367 action_data = {'email': user_email,
367 'user_agent': self.request.user_agent}
368 'user_agent': self.request.user_agent}
368 audit_logger.store_web(
369 audit_logger.store_web(
369 'user.password.reset_request', action_data=action_data,
370 'user.password.reset_request', action_data=action_data,
370 user=self._rhodecode_user, commit=True)
371 user=self._rhodecode_user, commit=True)
371 return HTTPFound(self.request.route_path('reset_password'))
372 return HTTPFound(self.request.route_path('reset_password'))
372
373
373 except formencode.Invalid as errors:
374 except formencode.Invalid as errors:
374 render_ctx.update({
375 render_ctx.update({
375 'defaults': errors.value,
376 'defaults': errors.value,
376 'errors': errors.error_dict,
377 'errors': errors.error_dict,
377 })
378 })
378 if not self.request.params.get('email'):
379 if not self.request.POST.get('email'):
379 # case of empty email, we want to report that
380 # case of empty email, we want to report that
380 return render_ctx
381 return render_ctx
381
382
382 if 'recaptcha_field' in errors.error_dict:
383 if 'recaptcha_field' in errors.error_dict:
383 # case of failed captcha
384 # case of failed captcha
384 return render_ctx
385 return render_ctx
385
386
386 log.debug('faking response on invalid password reset')
387 log.debug('faking response on invalid password reset')
387 # make this take 2s, to prevent brute forcing.
388 # make this take 2s, to prevent brute forcing.
388 time.sleep(2)
389 time.sleep(2)
389 self.session.flash(msg, queue='success')
390 self.session.flash(msg, queue='success')
390 return HTTPFound(self.request.route_path('reset_password'))
391 return HTTPFound(self.request.route_path('reset_password'))
391
392
392 return render_ctx
393 return render_ctx
393
394
394 @view_config(route_name='reset_password_confirmation',
395 @view_config(route_name='reset_password_confirmation',
395 request_method='GET')
396 request_method='GET')
396 def password_reset_confirmation(self):
397 def password_reset_confirmation(self):
397
398
398 if self.request.GET and self.request.GET.get('key'):
399 if self.request.GET and self.request.GET.get('key'):
399 # make this take 2s, to prevent brute forcing.
400 # make this take 2s, to prevent brute forcing.
400 time.sleep(2)
401 time.sleep(2)
401
402
402 token = AuthTokenModel().get_auth_token(
403 token = AuthTokenModel().get_auth_token(
403 self.request.GET.get('key'))
404 self.request.GET.get('key'))
404
405
405 # verify token is the correct role
406 # verify token is the correct role
406 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
407 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
407 log.debug('Got token with role:%s expected is %s',
408 log.debug('Got token with role:%s expected is %s',
408 getattr(token, 'role', 'EMPTY_TOKEN'),
409 getattr(token, 'role', 'EMPTY_TOKEN'),
409 UserApiKeys.ROLE_PASSWORD_RESET)
410 UserApiKeys.ROLE_PASSWORD_RESET)
410 self.session.flash(
411 self.session.flash(
411 _('Given reset token is invalid'), queue='error')
412 _('Given reset token is invalid'), queue='error')
412 return HTTPFound(self.request.route_path('reset_password'))
413 return HTTPFound(self.request.route_path('reset_password'))
413
414
414 try:
415 try:
415 owner = token.user
416 owner = token.user
416 data = {'email': owner.email, 'token': token.api_key}
417 data = {'email': owner.email, 'token': token.api_key}
417 UserModel().reset_password(data)
418 UserModel().reset_password(data)
418 self.session.flash(
419 self.session.flash(
419 _('Your password reset was successful, '
420 _('Your password reset was successful, '
420 'a new password has been sent to your email'),
421 'a new password has been sent to your email'),
421 queue='success')
422 queue='success')
422 except Exception as e:
423 except Exception as e:
423 log.error(e)
424 log.error(e)
424 return HTTPFound(self.request.route_path('reset_password'))
425 return HTTPFound(self.request.route_path('reset_password'))
425
426
426 return HTTPFound(self.request.route_path('login'))
427 return HTTPFound(self.request.route_path('login'))
General Comments 0
You need to be logged in to leave comments. Login now