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