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