##// END OF EJS Templates
login: don't use mutable globals as defauls
marcink -
r78:e1849c1a default
parent child Browse files
Show More
@@ -1,336 +1,339 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 or url('home')
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=None, errors=None):
191 defaults = defaults or {}
192 errors = errors or {}
193
191 settings = SettingsModel().get_all_settings()
194 settings = SettingsModel().get_all_settings()
192 captcha_public_key = settings.get('rhodecode_captcha_public_key')
195 captcha_public_key = settings.get('rhodecode_captcha_public_key')
193 captcha_private_key = settings.get('rhodecode_captcha_private_key')
196 captcha_private_key = settings.get('rhodecode_captcha_private_key')
194 captcha_active = bool(captcha_private_key)
197 captcha_active = bool(captcha_private_key)
195 register_message = settings.get('rhodecode_register_message') or ''
198 register_message = settings.get('rhodecode_register_message') or ''
196 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
199 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
197 .AuthUser.permissions['global']
200 .AuthUser.permissions['global']
198
201
199 render_ctx = self._get_template_context()
202 render_ctx = self._get_template_context()
200 render_ctx.update({
203 render_ctx.update({
201 'defaults': defaults,
204 'defaults': defaults,
202 'errors': errors,
205 'errors': errors,
203 'auto_active': auto_active,
206 'auto_active': auto_active,
204 'captcha_active': captcha_active,
207 'captcha_active': captcha_active,
205 'captcha_public_key': captcha_public_key,
208 'captcha_public_key': captcha_public_key,
206 'register_message': register_message,
209 'register_message': register_message,
207 })
210 })
208 return render_ctx
211 return render_ctx
209
212
210 @view_config(
213 @view_config(
211 route_name='register', request_method='POST',
214 route_name='register', request_method='POST',
212 renderer='rhodecode:templates/register.html')
215 renderer='rhodecode:templates/register.html')
213 def register_post(self):
216 def register_post(self):
214 captcha_private_key = SettingsModel().get_setting_by_name(
217 captcha_private_key = SettingsModel().get_setting_by_name(
215 'rhodecode_captcha_private_key')
218 'rhodecode_captcha_private_key')
216 captcha_active = bool(captcha_private_key)
219 captcha_active = bool(captcha_private_key)
217 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
220 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
218 .AuthUser.permissions['global']
221 .AuthUser.permissions['global']
219
222
220 register_form = RegisterForm()()
223 register_form = RegisterForm()()
221 try:
224 try:
222 form_result = register_form.to_python(self.request.params)
225 form_result = register_form.to_python(self.request.params)
223 form_result['active'] = auto_active
226 form_result['active'] = auto_active
224
227
225 if captcha_active:
228 if captcha_active:
226 response = submit(
229 response = submit(
227 self.request.params.get('recaptcha_challenge_field'),
230 self.request.params.get('recaptcha_challenge_field'),
228 self.request.params.get('recaptcha_response_field'),
231 self.request.params.get('recaptcha_response_field'),
229 private_key=captcha_private_key,
232 private_key=captcha_private_key,
230 remoteip=get_ip_addr(self.request.environ))
233 remoteip=get_ip_addr(self.request.environ))
231 if captcha_active and not response.is_valid:
234 if captcha_active and not response.is_valid:
232 _value = form_result
235 _value = form_result
233 _msg = _('bad captcha')
236 _msg = _('bad captcha')
234 error_dict = {'recaptcha_field': _msg}
237 error_dict = {'recaptcha_field': _msg}
235 raise formencode.Invalid(_msg, _value, None,
238 raise formencode.Invalid(_msg, _value, None,
236 error_dict=error_dict)
239 error_dict=error_dict)
237
240
238 new_user = UserModel().create_registration(form_result)
241 new_user = UserModel().create_registration(form_result)
239 event = UserRegistered(user=new_user, session=self.session)
242 event = UserRegistered(user=new_user, session=self.session)
240 self.request.registry.notify(event)
243 self.request.registry.notify(event)
241 self.session.flash(
244 self.session.flash(
242 _('You have successfully registered with RhodeCode'),
245 _('You have successfully registered with RhodeCode'),
243 queue='success')
246 queue='success')
244 Session().commit()
247 Session().commit()
245
248
246 redirect_ro = self.request.route_path('login')
249 redirect_ro = self.request.route_path('login')
247 raise HTTPFound(redirect_ro)
250 raise HTTPFound(redirect_ro)
248
251
249 except formencode.Invalid as errors:
252 except formencode.Invalid as errors:
250 del errors.value['password']
253 del errors.value['password']
251 del errors.value['password_confirmation']
254 del errors.value['password_confirmation']
252 return self.register(
255 return self.register(
253 defaults=errors.value, errors=errors.error_dict)
256 defaults=errors.value, errors=errors.error_dict)
254
257
255 except UserCreationError as e:
258 except UserCreationError as e:
256 # container auth or other auth functions that create users on
259 # container auth or other auth functions that create users on
257 # the fly can throw this exception signaling that there's issue
260 # the fly can throw this exception signaling that there's issue
258 # with user creation, explanation should be provided in
261 # with user creation, explanation should be provided in
259 # Exception itself
262 # Exception itself
260 self.session.flash(e, queue='error')
263 self.session.flash(e, queue='error')
261 return self.register()
264 return self.register()
262
265
263 @view_config(
266 @view_config(
264 route_name='reset_password', request_method=('GET', 'POST'),
267 route_name='reset_password', request_method=('GET', 'POST'),
265 renderer='rhodecode:templates/password_reset.html')
268 renderer='rhodecode:templates/password_reset.html')
266 def password_reset(self):
269 def password_reset(self):
267 settings = SettingsModel().get_all_settings()
270 settings = SettingsModel().get_all_settings()
268 captcha_private_key = settings.get('rhodecode_captcha_private_key')
271 captcha_private_key = settings.get('rhodecode_captcha_private_key')
269 captcha_active = bool(captcha_private_key)
272 captcha_active = bool(captcha_private_key)
270 captcha_public_key = settings.get('rhodecode_captcha_public_key')
273 captcha_public_key = settings.get('rhodecode_captcha_public_key')
271
274
272 render_ctx = {
275 render_ctx = {
273 'captcha_active': captcha_active,
276 'captcha_active': captcha_active,
274 'captcha_public_key': captcha_public_key,
277 'captcha_public_key': captcha_public_key,
275 'defaults': {},
278 'defaults': {},
276 'errors': {},
279 'errors': {},
277 }
280 }
278
281
279 if self.request.POST:
282 if self.request.POST:
280 password_reset_form = PasswordResetForm()()
283 password_reset_form = PasswordResetForm()()
281 try:
284 try:
282 form_result = password_reset_form.to_python(
285 form_result = password_reset_form.to_python(
283 self.request.params)
286 self.request.params)
284 if captcha_active:
287 if captcha_active:
285 response = submit(
288 response = submit(
286 self.request.params.get('recaptcha_challenge_field'),
289 self.request.params.get('recaptcha_challenge_field'),
287 self.request.params.get('recaptcha_response_field'),
290 self.request.params.get('recaptcha_response_field'),
288 private_key=captcha_private_key,
291 private_key=captcha_private_key,
289 remoteip=get_ip_addr(self.request.environ))
292 remoteip=get_ip_addr(self.request.environ))
290 if captcha_active and not response.is_valid:
293 if captcha_active and not response.is_valid:
291 _value = form_result
294 _value = form_result
292 _msg = _('bad captcha')
295 _msg = _('bad captcha')
293 error_dict = {'recaptcha_field': _msg}
296 error_dict = {'recaptcha_field': _msg}
294 raise formencode.Invalid(_msg, _value, None,
297 raise formencode.Invalid(_msg, _value, None,
295 error_dict=error_dict)
298 error_dict=error_dict)
296
299
297 # Generate reset URL and send mail.
300 # Generate reset URL and send mail.
298 user_email = form_result['email']
301 user_email = form_result['email']
299 user = User.get_by_email(user_email)
302 user = User.get_by_email(user_email)
300 password_reset_url = self.request.route_url(
303 password_reset_url = self.request.route_url(
301 'reset_password_confirmation',
304 'reset_password_confirmation',
302 _query={'key': user.api_key})
305 _query={'key': user.api_key})
303 UserModel().reset_password_link(
306 UserModel().reset_password_link(
304 form_result, password_reset_url)
307 form_result, password_reset_url)
305
308
306 # Display success message and redirect.
309 # Display success message and redirect.
307 self.session.flash(
310 self.session.flash(
308 _('Your password reset link was sent'),
311 _('Your password reset link was sent'),
309 queue='success')
312 queue='success')
310 return HTTPFound(self.request.route_path('login'))
313 return HTTPFound(self.request.route_path('login'))
311
314
312 except formencode.Invalid as errors:
315 except formencode.Invalid as errors:
313 render_ctx.update({
316 render_ctx.update({
314 'defaults': errors.value,
317 'defaults': errors.value,
315 'errors': errors.error_dict,
318 'errors': errors.error_dict,
316 })
319 })
317
320
318 return render_ctx
321 return render_ctx
319
322
320 @view_config(route_name='reset_password_confirmation',
323 @view_config(route_name='reset_password_confirmation',
321 request_method='GET')
324 request_method='GET')
322 def password_reset_confirmation(self):
325 def password_reset_confirmation(self):
323 if self.request.GET and self.request.GET.get('key'):
326 if self.request.GET and self.request.GET.get('key'):
324 try:
327 try:
325 user = User.get_by_auth_token(self.request.GET.get('key'))
328 user = User.get_by_auth_token(self.request.GET.get('key'))
326 data = {'email': user.email}
329 data = {'email': user.email}
327 UserModel().reset_password(data)
330 UserModel().reset_password(data)
328 self.session.flash(
331 self.session.flash(
329 _('Your password reset was successful, '
332 _('Your password reset was successful, '
330 'a new password has been sent to your email'),
333 'a new password has been sent to your email'),
331 queue='success')
334 queue='success')
332 except Exception as e:
335 except Exception as e:
333 log.error(e)
336 log.error(e)
334 return HTTPFound(self.request.route_path('reset_password'))
337 return HTTPFound(self.request.route_path('reset_password'))
335
338
336 return HTTPFound(self.request.route_path('login'))
339 return HTTPFound(self.request.route_path('login'))
General Comments 0
You need to be logged in to leave comments. Login now