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