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