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