##// END OF EJS Templates
login: Fix login redirection when came_from not set.
johbo -
r58:1389be9f default
parent child Browse files
Show More
@@ -1,336 +1,336 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2016 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 datetime
21 import datetime
22 import formencode
22 import formencode
23 import logging
23 import logging
24 import urlparse
24 import urlparse
25
25
26 from pylons import url
26 from pylons import url
27 from pyramid.httpexceptions import HTTPFound
27 from pyramid.httpexceptions import HTTPFound
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from recaptcha.client.captcha import submit
29 from recaptcha.client.captcha import submit
30
30
31 from rhodecode.events import UserRegistered
31 from rhodecode.events import UserRegistered
32 from rhodecode.lib.auth import (
32 from rhodecode.lib.auth import (
33 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
33 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
34 from rhodecode.lib.base import get_ip_addr
34 from rhodecode.lib.base import get_ip_addr
35 from rhodecode.lib.exceptions import UserCreationError
35 from rhodecode.lib.exceptions import UserCreationError
36 from rhodecode.lib.utils2 import safe_str
36 from rhodecode.lib.utils2 import safe_str
37 from rhodecode.model.db import User
37 from rhodecode.model.db import User
38 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
38 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
39 from rhodecode.model.login_session import LoginSession
39 from rhodecode.model.login_session import LoginSession
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.settings import SettingsModel
41 from rhodecode.model.settings import SettingsModel
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from rhodecode.translation import _
43 from rhodecode.translation import _
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 def _store_user_in_session(session, username, remember=False):
49 def _store_user_in_session(session, username, remember=False):
50 user = User.get_by_username(username, case_insensitive=True)
50 user = User.get_by_username(username, case_insensitive=True)
51 auth_user = AuthUser(user.user_id)
51 auth_user = AuthUser(user.user_id)
52 auth_user.set_authenticated()
52 auth_user.set_authenticated()
53 cs = auth_user.get_cookie_store()
53 cs = auth_user.get_cookie_store()
54 session['rhodecode_user'] = cs
54 session['rhodecode_user'] = cs
55 user.update_lastlogin()
55 user.update_lastlogin()
56 Session().commit()
56 Session().commit()
57
57
58 # If they want to be remembered, update the cookie
58 # If they want to be remembered, update the cookie
59 if remember:
59 if remember:
60 _year = (datetime.datetime.now() +
60 _year = (datetime.datetime.now() +
61 datetime.timedelta(seconds=60 * 60 * 24 * 365))
61 datetime.timedelta(seconds=60 * 60 * 24 * 365))
62 session._set_cookie_expires(_year)
62 session._set_cookie_expires(_year)
63
63
64 session.save()
64 session.save()
65
65
66 log.info('user %s is now authenticated and stored in '
66 log.info('user %s is now authenticated and stored in '
67 'session, session attrs %s', username, cs)
67 'session, session attrs %s', username, cs)
68
68
69 # dumps session attrs back to cookie
69 # dumps session attrs back to cookie
70 session._update_cookie_out()
70 session._update_cookie_out()
71 # we set new cookie
71 # we set new cookie
72 headers = None
72 headers = None
73 if session.request['set_cookie']:
73 if session.request['set_cookie']:
74 # send set-cookie headers back to response to update cookie
74 # send set-cookie headers back to response to update cookie
75 headers = [('Set-Cookie', session.request['cookie_out'])]
75 headers = [('Set-Cookie', session.request['cookie_out'])]
76 return headers
76 return headers
77
77
78
78
79 def get_came_from(request):
79 def get_came_from(request):
80 came_from = safe_str(request.GET.get('came_from', ''))
80 came_from = safe_str(request.GET.get('came_from', ''))
81 parsed = urlparse.urlparse(came_from)
81 parsed = urlparse.urlparse(came_from)
82 allowed_schemes = ['http', 'https']
82 allowed_schemes = ['http', 'https']
83 if parsed.scheme and parsed.scheme not in allowed_schemes:
83 if parsed.scheme and parsed.scheme not in allowed_schemes:
84 log.error('Suspicious URL scheme detected %s for url %s' %
84 log.error('Suspicious URL scheme detected %s for url %s' %
85 (parsed.scheme, parsed))
85 (parsed.scheme, parsed))
86 came_from = url('home')
86 came_from = url('home')
87 elif parsed.netloc and request.host != parsed.netloc:
87 elif parsed.netloc and request.host != parsed.netloc:
88 log.error('Suspicious NETLOC detected %s for url %s server url '
88 log.error('Suspicious NETLOC detected %s for url %s server url '
89 'is: %s' % (parsed.netloc, parsed, request.host))
89 'is: %s' % (parsed.netloc, parsed, request.host))
90 came_from = url('home')
90 came_from = url('home')
91 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
91 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
92 log.error('Header injection detected `%s` for url %s server url ' %
92 log.error('Header injection detected `%s` for url %s server url ' %
93 (parsed.path, parsed))
93 (parsed.path, parsed))
94 came_from = url('home')
94 came_from = url('home')
95
95
96 return came_from
96 return came_from or url('home')
97
97
98
98
99 class LoginView(object):
99 class LoginView(object):
100
100
101 def __init__(self, context, request):
101 def __init__(self, context, request):
102 self.request = request
102 self.request = request
103 self.context = context
103 self.context = context
104 self.session = request.session
104 self.session = request.session
105 self._rhodecode_user = request.user
105 self._rhodecode_user = request.user
106
106
107 def _get_template_context(self):
107 def _get_template_context(self):
108 return {
108 return {
109 'came_from': get_came_from(self.request),
109 'came_from': get_came_from(self.request),
110 'defaults': {},
110 'defaults': {},
111 'errors': {},
111 'errors': {},
112 }
112 }
113
113
114 @view_config(
114 @view_config(
115 route_name='login', request_method='GET',
115 route_name='login', request_method='GET',
116 renderer='rhodecode:templates/login.html')
116 renderer='rhodecode:templates/login.html')
117 def login(self):
117 def login(self):
118 user = self.request.user
118 user = self.request.user
119
119
120 # redirect if already logged in
120 # redirect if already logged in
121 if user.is_authenticated and not user.is_default and user.ip_allowed:
121 if user.is_authenticated and not user.is_default and user.ip_allowed:
122 raise HTTPFound(get_came_from(self.request))
122 raise HTTPFound(get_came_from(self.request))
123
123
124 return self._get_template_context()
124 return self._get_template_context()
125
125
126 @view_config(
126 @view_config(
127 route_name='login', request_method='POST',
127 route_name='login', request_method='POST',
128 renderer='rhodecode:templates/login.html')
128 renderer='rhodecode:templates/login.html')
129 def login_post(self):
129 def login_post(self):
130 came_from = get_came_from(self.request)
130 came_from = get_came_from(self.request)
131 session = self.request.session
131 session = self.request.session
132 login_form = LoginForm()()
132 login_form = LoginForm()()
133
133
134 try:
134 try:
135 session.invalidate()
135 session.invalidate()
136 form_result = login_form.to_python(self.request.params)
136 form_result = login_form.to_python(self.request.params)
137 # form checks for username/password, now we're authenticated
137 # form checks for username/password, now we're authenticated
138 headers = _store_user_in_session(
138 headers = _store_user_in_session(
139 self.session,
139 self.session,
140 username=form_result['username'],
140 username=form_result['username'],
141 remember=form_result['remember'])
141 remember=form_result['remember'])
142 raise HTTPFound(came_from, headers=headers)
142 raise HTTPFound(came_from, headers=headers)
143 except formencode.Invalid as errors:
143 except formencode.Invalid as errors:
144 defaults = errors.value
144 defaults = errors.value
145 # remove password from filling in form again
145 # remove password from filling in form again
146 del defaults['password']
146 del defaults['password']
147 render_ctx = self._get_template_context()
147 render_ctx = self._get_template_context()
148 render_ctx.update({
148 render_ctx.update({
149 'errors': errors.error_dict,
149 'errors': errors.error_dict,
150 'defaults': defaults,
150 'defaults': defaults,
151 })
151 })
152 return render_ctx
152 return render_ctx
153
153
154 except UserCreationError as e:
154 except UserCreationError as e:
155 # container auth or other auth functions that create users on
155 # container auth or other auth functions that create users on
156 # the fly can throw this exception signaling that there's issue
156 # the fly can throw this exception signaling that there's issue
157 # with user creation, explanation should be provided in
157 # with user creation, explanation should be provided in
158 # Exception itself
158 # Exception itself
159 session.flash(e, queue='error')
159 session.flash(e, queue='error')
160
160
161 # check if we use container plugin, and try to login using it.
161 # check if we use container plugin, and try to login using it.
162 from rhodecode.authentication.base import authenticate, HTTP_TYPE
162 from rhodecode.authentication.base import authenticate, HTTP_TYPE
163 try:
163 try:
164 log.debug('Running PRE-AUTH for container based authentication')
164 log.debug('Running PRE-AUTH for container 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 except UserCreationError as e:
167 except UserCreationError as e:
168 log.error(e)
168 log.error(e)
169 session.flash(e, queue='error')
169 session.flash(e, queue='error')
170 # render login, with flash message about limit
170 # render login, with flash message about limit
171 return self._get_template_context()
171 return self._get_template_context()
172
172
173 if auth_info:
173 if auth_info:
174 headers = _store_user_in_session(auth_info.get('username'))
174 headers = _store_user_in_session(auth_info.get('username'))
175 raise HTTPFound(came_from, headers=headers)
175 raise HTTPFound(came_from, headers=headers)
176
176
177 return self._get_template_context()
177 return self._get_template_context()
178
178
179 @CSRFRequired()
179 @CSRFRequired()
180 @view_config(route_name='logout', request_method='POST')
180 @view_config(route_name='logout', request_method='POST')
181 def logout(self):
181 def logout(self):
182 LoginSession().destroy_user_session()
182 LoginSession().destroy_user_session()
183 return HTTPFound(url('home'))
183 return HTTPFound(url('home'))
184
184
185 @HasPermissionAnyDecorator(
185 @HasPermissionAnyDecorator(
186 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
186 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
187 @view_config(
187 @view_config(
188 route_name='register', request_method='GET',
188 route_name='register', request_method='GET',
189 renderer='rhodecode:templates/register.html',)
189 renderer='rhodecode:templates/register.html',)
190 def register(self, defaults={}, errors={}):
190 def register(self, defaults={}, errors={}):
191 settings = SettingsModel().get_all_settings()
191 settings = SettingsModel().get_all_settings()
192 captcha_public_key = settings.get('rhodecode_captcha_public_key')
192 captcha_public_key = settings.get('rhodecode_captcha_public_key')
193 captcha_private_key = settings.get('rhodecode_captcha_private_key')
193 captcha_private_key = settings.get('rhodecode_captcha_private_key')
194 captcha_active = bool(captcha_private_key)
194 captcha_active = bool(captcha_private_key)
195 register_message = settings.get('rhodecode_register_message') or ''
195 register_message = settings.get('rhodecode_register_message') or ''
196 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
196 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
197 .AuthUser.permissions['global']
197 .AuthUser.permissions['global']
198
198
199 render_ctx = self._get_template_context()
199 render_ctx = self._get_template_context()
200 render_ctx.update({
200 render_ctx.update({
201 'defaults': defaults,
201 'defaults': defaults,
202 'errors': errors,
202 'errors': errors,
203 'auto_active': auto_active,
203 'auto_active': auto_active,
204 'captcha_active': captcha_active,
204 'captcha_active': captcha_active,
205 'captcha_public_key': captcha_public_key,
205 'captcha_public_key': captcha_public_key,
206 'register_message': register_message,
206 'register_message': register_message,
207 })
207 })
208 return render_ctx
208 return render_ctx
209
209
210 @view_config(
210 @view_config(
211 route_name='register', request_method='POST',
211 route_name='register', request_method='POST',
212 renderer='rhodecode:templates/register.html')
212 renderer='rhodecode:templates/register.html')
213 def register_post(self):
213 def register_post(self):
214 captcha_private_key = SettingsModel().get_setting_by_name(
214 captcha_private_key = SettingsModel().get_setting_by_name(
215 'rhodecode_captcha_private_key')
215 'rhodecode_captcha_private_key')
216 captcha_active = bool(captcha_private_key)
216 captcha_active = bool(captcha_private_key)
217 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
217 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
218 .AuthUser.permissions['global']
218 .AuthUser.permissions['global']
219
219
220 register_form = RegisterForm()()
220 register_form = RegisterForm()()
221 try:
221 try:
222 form_result = register_form.to_python(self.request.params)
222 form_result = register_form.to_python(self.request.params)
223 form_result['active'] = auto_active
223 form_result['active'] = auto_active
224
224
225 if captcha_active:
225 if captcha_active:
226 response = submit(
226 response = submit(
227 self.request.params.get('recaptcha_challenge_field'),
227 self.request.params.get('recaptcha_challenge_field'),
228 self.request.params.get('recaptcha_response_field'),
228 self.request.params.get('recaptcha_response_field'),
229 private_key=captcha_private_key,
229 private_key=captcha_private_key,
230 remoteip=get_ip_addr(self.request.environ))
230 remoteip=get_ip_addr(self.request.environ))
231 if captcha_active and not response.is_valid:
231 if captcha_active and not response.is_valid:
232 _value = form_result
232 _value = form_result
233 _msg = _('bad captcha')
233 _msg = _('bad captcha')
234 error_dict = {'recaptcha_field': _msg}
234 error_dict = {'recaptcha_field': _msg}
235 raise formencode.Invalid(_msg, _value, None,
235 raise formencode.Invalid(_msg, _value, None,
236 error_dict=error_dict)
236 error_dict=error_dict)
237
237
238 new_user = UserModel().create_registration(form_result)
238 new_user = UserModel().create_registration(form_result)
239 event = UserRegistered(user=new_user, session=self.session)
239 event = UserRegistered(user=new_user, session=self.session)
240 self.request.registry.notify(event)
240 self.request.registry.notify(event)
241 self.session.flash(
241 self.session.flash(
242 _('You have successfully registered with RhodeCode'),
242 _('You have successfully registered with RhodeCode'),
243 queue='success')
243 queue='success')
244 Session().commit()
244 Session().commit()
245
245
246 redirect_ro = self.request.route_path('login')
246 redirect_ro = self.request.route_path('login')
247 raise HTTPFound(redirect_ro)
247 raise HTTPFound(redirect_ro)
248
248
249 except formencode.Invalid as errors:
249 except formencode.Invalid as errors:
250 del errors.value['password']
250 del errors.value['password']
251 del errors.value['password_confirmation']
251 del errors.value['password_confirmation']
252 return self.register(
252 return self.register(
253 defaults=errors.value, errors=errors.error_dict)
253 defaults=errors.value, errors=errors.error_dict)
254
254
255 except UserCreationError as e:
255 except UserCreationError as e:
256 # container auth or other auth functions that create users on
256 # container auth or other auth functions that create users on
257 # the fly can throw this exception signaling that there's issue
257 # the fly can throw this exception signaling that there's issue
258 # with user creation, explanation should be provided in
258 # with user creation, explanation should be provided in
259 # Exception itself
259 # Exception itself
260 self.session.flash(e, queue='error')
260 self.session.flash(e, queue='error')
261 return self.register()
261 return self.register()
262
262
263 @view_config(
263 @view_config(
264 route_name='reset_password', request_method=('GET', 'POST'),
264 route_name='reset_password', request_method=('GET', 'POST'),
265 renderer='rhodecode:templates/password_reset.html')
265 renderer='rhodecode:templates/password_reset.html')
266 def password_reset(self):
266 def password_reset(self):
267 settings = SettingsModel().get_all_settings()
267 settings = SettingsModel().get_all_settings()
268 captcha_private_key = settings.get('rhodecode_captcha_private_key')
268 captcha_private_key = settings.get('rhodecode_captcha_private_key')
269 captcha_active = bool(captcha_private_key)
269 captcha_active = bool(captcha_private_key)
270 captcha_public_key = settings.get('rhodecode_captcha_public_key')
270 captcha_public_key = settings.get('rhodecode_captcha_public_key')
271
271
272 render_ctx = {
272 render_ctx = {
273 'captcha_active': captcha_active,
273 'captcha_active': captcha_active,
274 'captcha_public_key': captcha_public_key,
274 'captcha_public_key': captcha_public_key,
275 'defaults': {},
275 'defaults': {},
276 'errors': {},
276 'errors': {},
277 }
277 }
278
278
279 if self.request.POST:
279 if self.request.POST:
280 password_reset_form = PasswordResetForm()()
280 password_reset_form = PasswordResetForm()()
281 try:
281 try:
282 form_result = password_reset_form.to_python(
282 form_result = password_reset_form.to_python(
283 self.request.params)
283 self.request.params)
284 if captcha_active:
284 if captcha_active:
285 response = submit(
285 response = submit(
286 self.request.params.get('recaptcha_challenge_field'),
286 self.request.params.get('recaptcha_challenge_field'),
287 self.request.params.get('recaptcha_response_field'),
287 self.request.params.get('recaptcha_response_field'),
288 private_key=captcha_private_key,
288 private_key=captcha_private_key,
289 remoteip=get_ip_addr(self.request.environ))
289 remoteip=get_ip_addr(self.request.environ))
290 if captcha_active and not response.is_valid:
290 if captcha_active and not response.is_valid:
291 _value = form_result
291 _value = form_result
292 _msg = _('bad captcha')
292 _msg = _('bad captcha')
293 error_dict = {'recaptcha_field': _msg}
293 error_dict = {'recaptcha_field': _msg}
294 raise formencode.Invalid(_msg, _value, None,
294 raise formencode.Invalid(_msg, _value, None,
295 error_dict=error_dict)
295 error_dict=error_dict)
296
296
297 # Generate reset URL and send mail.
297 # Generate reset URL and send mail.
298 user_email = form_result['email']
298 user_email = form_result['email']
299 user = User.get_by_email(user_email)
299 user = User.get_by_email(user_email)
300 password_reset_url = self.request.route_url(
300 password_reset_url = self.request.route_url(
301 'reset_password_confirmation',
301 'reset_password_confirmation',
302 _query={'key': user.api_key})
302 _query={'key': user.api_key})
303 UserModel().reset_password_link(
303 UserModel().reset_password_link(
304 form_result, password_reset_url)
304 form_result, password_reset_url)
305
305
306 # Display success message and redirect.
306 # Display success message and redirect.
307 self.session.flash(
307 self.session.flash(
308 _('Your password reset link was sent'),
308 _('Your password reset link was sent'),
309 queue='success')
309 queue='success')
310 return HTTPFound(self.request.route_path('login'))
310 return HTTPFound(self.request.route_path('login'))
311
311
312 except formencode.Invalid as errors:
312 except formencode.Invalid as errors:
313 render_ctx.update({
313 render_ctx.update({
314 'defaults': errors.value,
314 'defaults': errors.value,
315 'errors': errors.error_dict,
315 'errors': errors.error_dict,
316 })
316 })
317
317
318 return render_ctx
318 return render_ctx
319
319
320 @view_config(route_name='reset_password_confirmation',
320 @view_config(route_name='reset_password_confirmation',
321 request_method='GET')
321 request_method='GET')
322 def password_reset_confirmation(self):
322 def password_reset_confirmation(self):
323 if self.request.GET and self.request.GET.get('key'):
323 if self.request.GET and self.request.GET.get('key'):
324 try:
324 try:
325 user = User.get_by_auth_token(self.request.GET.get('key'))
325 user = User.get_by_auth_token(self.request.GET.get('key'))
326 data = {'email': user.email}
326 data = {'email': user.email}
327 UserModel().reset_password(data)
327 UserModel().reset_password(data)
328 self.session.flash(
328 self.session.flash(
329 _('Your password reset was successful, '
329 _('Your password reset was successful, '
330 'a new password has been sent to your email'),
330 'a new password has been sent to your email'),
331 queue='success')
331 queue='success')
332 except Exception as e:
332 except Exception as e:
333 log.error(e)
333 log.error(e)
334 return HTTPFound(self.request.route_path('reset_password'))
334 return HTTPFound(self.request.route_path('reset_password'))
335
335
336 return HTTPFound(self.request.route_path('login'))
336 return HTTPFound(self.request.route_path('login'))
General Comments 0
You need to be logged in to leave comments. Login now