##// END OF EJS Templates
login: Move method to store user in session out of login controller....
johbo -
r28:48f65d03 default
parent child Browse files
Show More
@@ -1,290 +1,291 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-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 """
21 """
22 Login controller for rhodeocode
22 Login controller for rhodeocode
23 """
23 """
24
24
25 import datetime
25 import datetime
26 import formencode
26 import formencode
27 import logging
27 import logging
28 import urlparse
28 import urlparse
29 import uuid
29 import uuid
30
30
31 from formencode import htmlfill
31 from formencode import htmlfill
32 from webob.exc import HTTPFound
32 from webob.exc import HTTPFound
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from pylons import request, session, tmpl_context as c, url
35 from pylons import request, session, tmpl_context as c, url
36 from recaptcha.client.captcha import submit
36 from recaptcha.client.captcha import submit
37
37
38 import rhodecode.lib.helpers as h
38 import rhodecode.lib.helpers as h
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
41 from rhodecode.authentication.base import loadplugin
41 from rhodecode.authentication.base import loadplugin
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils2 import safe_str
44 from rhodecode.lib.utils2 import safe_str
45 from rhodecode.model.db import User
45 from rhodecode.model.db import User
46 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
46 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
47 from rhodecode.model.login_session import LoginSession
47 from rhodecode.model.login_session import LoginSession
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.settings import SettingsModel
49 from rhodecode.model.settings import SettingsModel
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class LoginController(BaseController):
55 def _store_user_in_session(username, remember=False):
56
57 def __before__(self):
58 super(LoginController, self).__before__()
59
60 def _store_user_in_session(self, username, remember=False):
61 user = User.get_by_username(username, case_insensitive=True)
56 user = User.get_by_username(username, case_insensitive=True)
62 auth_user = AuthUser(user.user_id)
57 auth_user = AuthUser(user.user_id)
63 auth_user.set_authenticated()
58 auth_user.set_authenticated()
64 cs = auth_user.get_cookie_store()
59 cs = auth_user.get_cookie_store()
65 session['rhodecode_user'] = cs
60 session['rhodecode_user'] = cs
66 user.update_lastlogin()
61 user.update_lastlogin()
67 Session().commit()
62 Session().commit()
68
63
69 # If they want to be remembered, update the cookie
64 # If they want to be remembered, update the cookie
70 if remember:
65 if remember:
71 _year = (datetime.datetime.now() +
66 _year = (datetime.datetime.now() +
72 datetime.timedelta(seconds=60 * 60 * 24 * 365))
67 datetime.timedelta(seconds=60 * 60 * 24 * 365))
73 session._set_cookie_expires(_year)
68 session._set_cookie_expires(_year)
74
69
75 session.save()
70 session.save()
76
71
77 log.info('user %s is now authenticated and stored in '
72 log.info('user %s is now authenticated and stored in '
78 'session, session attrs %s', username, cs)
73 'session, session attrs %s', username, cs)
79
74
80 # dumps session attrs back to cookie
75 # dumps session attrs back to cookie
81 session._update_cookie_out()
76 session._update_cookie_out()
82 # we set new cookie
77 # we set new cookie
83 headers = None
78 headers = None
84 if session.request['set_cookie']:
79 if session.request['set_cookie']:
85 # send set-cookie headers back to response to update cookie
80 # send set-cookie headers back to response to update cookie
86 headers = [('Set-Cookie', session.request['cookie_out'])]
81 headers = [('Set-Cookie', session.request['cookie_out'])]
87 return headers
82 return headers
88
83
84
85 class LoginController(BaseController):
86
87 def __before__(self):
88 super(LoginController, self).__before__()
89
89 def _validate_came_from(self, came_from):
90 def _validate_came_from(self, came_from):
90 if not came_from:
91 if not came_from:
91 return came_from
92 return came_from
92
93
93 parsed = urlparse.urlparse(came_from)
94 parsed = urlparse.urlparse(came_from)
94 server_parsed = urlparse.urlparse(url.current())
95 server_parsed = urlparse.urlparse(url.current())
95 allowed_schemes = ['http', 'https']
96 allowed_schemes = ['http', 'https']
96 if parsed.scheme and parsed.scheme not in allowed_schemes:
97 if parsed.scheme and parsed.scheme not in allowed_schemes:
97 log.error('Suspicious URL scheme detected %s for url %s' %
98 log.error('Suspicious URL scheme detected %s for url %s' %
98 (parsed.scheme, parsed))
99 (parsed.scheme, parsed))
99 came_from = url('home')
100 came_from = url('home')
100 elif server_parsed.netloc != parsed.netloc:
101 elif server_parsed.netloc != parsed.netloc:
101 log.error('Suspicious NETLOC detected %s for url %s server url '
102 log.error('Suspicious NETLOC detected %s for url %s server url '
102 'is: %s' % (parsed.netloc, parsed, server_parsed))
103 'is: %s' % (parsed.netloc, parsed, server_parsed))
103 came_from = url('home')
104 came_from = url('home')
104 if any(bad_str in parsed.path for bad_str in ('\r', '\n')):
105 if any(bad_str in parsed.path for bad_str in ('\r', '\n')):
105 log.error('Header injection detected `%s` for url %s server url ' %
106 log.error('Header injection detected `%s` for url %s server url ' %
106 (parsed.path, parsed))
107 (parsed.path, parsed))
107 came_from = url('home')
108 came_from = url('home')
108 return came_from
109 return came_from
109
110
110 def _redirect_to_origin(self, location, headers=None):
111 def _redirect_to_origin(self, location, headers=None):
111 request.GET.pop('came_from', None)
112 request.GET.pop('came_from', None)
112 raise HTTPFound(location=location, headers=headers)
113 raise HTTPFound(location=location, headers=headers)
113
114
114 def _set_came_from(self):
115 def _set_came_from(self):
115 _default_came_from = url('home')
116 _default_came_from = url('home')
116 came_from = self._validate_came_from(
117 came_from = self._validate_came_from(
117 safe_str(request.GET.get('came_from', '')))
118 safe_str(request.GET.get('came_from', '')))
118 c.came_from = came_from or _default_came_from
119 c.came_from = came_from or _default_came_from
119
120
120 def index(self):
121 def index(self):
121 self._set_came_from()
122 self._set_came_from()
122
123
123 not_default = c.rhodecode_user.username != User.DEFAULT_USER
124 not_default = c.rhodecode_user.username != User.DEFAULT_USER
124 ip_allowed = c.rhodecode_user.ip_allowed
125 ip_allowed = c.rhodecode_user.ip_allowed
125
126
126 # redirect if already logged in
127 # redirect if already logged in
127 if c.rhodecode_user.is_authenticated and not_default and ip_allowed:
128 if c.rhodecode_user.is_authenticated and not_default and ip_allowed:
128 raise self._redirect_to_origin(location=c.came_from)
129 raise self._redirect_to_origin(location=c.came_from)
129
130
130 if request.POST:
131 if request.POST:
131 # import Login Form validator class
132 # import Login Form validator class
132 login_form = LoginForm()()
133 login_form = LoginForm()()
133 try:
134 try:
134 session.invalidate()
135 session.invalidate()
135 c.form_result = login_form.to_python(dict(request.POST))
136 c.form_result = login_form.to_python(dict(request.POST))
136 # form checks for username/password, now we're authenticated
137 # form checks for username/password, now we're authenticated
137 headers = self._store_user_in_session(
138 headers = _store_user_in_session(
138 username=c.form_result['username'],
139 username=c.form_result['username'],
139 remember=c.form_result['remember'])
140 remember=c.form_result['remember'])
140 raise self._redirect_to_origin(
141 raise self._redirect_to_origin(
141 location=c.came_from, headers=headers)
142 location=c.came_from, headers=headers)
142 except formencode.Invalid as errors:
143 except formencode.Invalid as errors:
143 defaults = errors.value
144 defaults = errors.value
144 # remove password from filling in form again
145 # remove password from filling in form again
145 del defaults['password']
146 del defaults['password']
146 return htmlfill.render(
147 return htmlfill.render(
147 render('/login.html'),
148 render('/login.html'),
148 defaults=errors.value,
149 defaults=errors.value,
149 errors=errors.error_dict or {},
150 errors=errors.error_dict or {},
150 prefix_error=False,
151 prefix_error=False,
151 encoding="UTF-8",
152 encoding="UTF-8",
152 force_defaults=False)
153 force_defaults=False)
153 except UserCreationError as e:
154 except UserCreationError as e:
154 # container auth or other auth functions that create users on
155 # container auth or other auth functions that create users on
155 # the fly can throw this exception signaling that there's issue
156 # the fly can throw this exception signaling that there's issue
156 # with user creation, explanation should be provided in
157 # with user creation, explanation should be provided in
157 # Exception itself
158 # Exception itself
158 h.flash(e, 'error')
159 h.flash(e, 'error')
159
160
160 # 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.
161 from rhodecode.authentication.base import authenticate, HTTP_TYPE
162 from rhodecode.authentication.base import authenticate, HTTP_TYPE
162 try:
163 try:
163 log.debug('Running PRE-AUTH for container based authentication')
164 log.debug('Running PRE-AUTH for container based authentication')
164 auth_info = authenticate(
165 auth_info = authenticate(
165 '', '', request.environ, HTTP_TYPE, skip_missing=True)
166 '', '', request.environ, HTTP_TYPE, skip_missing=True)
166 except UserCreationError as e:
167 except UserCreationError as e:
167 log.error(e)
168 log.error(e)
168 h.flash(e, 'error')
169 h.flash(e, 'error')
169 # render login, with flash message about limit
170 # render login, with flash message about limit
170 return render('/login.html')
171 return render('/login.html')
171
172
172 if auth_info:
173 if auth_info:
173 headers = self._store_user_in_session(auth_info.get('username'))
174 headers = _store_user_in_session(auth_info.get('username'))
174 raise self._redirect_to_origin(
175 raise self._redirect_to_origin(
175 location=c.came_from, headers=headers)
176 location=c.came_from, headers=headers)
176 return render('/login.html')
177 return render('/login.html')
177
178
178 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
179 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
179 'hg.register.manual_activate')
180 'hg.register.manual_activate')
180 def register(self):
181 def register(self):
181 c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
182 c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
182 .AuthUser.permissions['global']
183 .AuthUser.permissions['global']
183
184
184 settings = SettingsModel().get_all_settings()
185 settings = SettingsModel().get_all_settings()
185 captcha_private_key = settings.get('rhodecode_captcha_private_key')
186 captcha_private_key = settings.get('rhodecode_captcha_private_key')
186 c.captcha_active = bool(captcha_private_key)
187 c.captcha_active = bool(captcha_private_key)
187 c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
188 c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
188 c.register_message = settings.get('rhodecode_register_message') or ''
189 c.register_message = settings.get('rhodecode_register_message') or ''
189 c.form_data = {}
190 c.form_data = {}
190
191
191 if request.POST:
192 if request.POST:
192 register_form = RegisterForm()()
193 register_form = RegisterForm()()
193 try:
194 try:
194 form_result = register_form.to_python(dict(request.POST))
195 form_result = register_form.to_python(dict(request.POST))
195 form_result['active'] = c.auto_active
196 form_result['active'] = c.auto_active
196
197
197 if c.captcha_active:
198 if c.captcha_active:
198 response = submit(
199 response = submit(
199 request.POST.get('recaptcha_challenge_field'),
200 request.POST.get('recaptcha_challenge_field'),
200 request.POST.get('recaptcha_response_field'),
201 request.POST.get('recaptcha_response_field'),
201 private_key=captcha_private_key,
202 private_key=captcha_private_key,
202 remoteip=self.ip_addr)
203 remoteip=self.ip_addr)
203 if c.captcha_active and not response.is_valid:
204 if c.captcha_active and not response.is_valid:
204 _value = form_result
205 _value = form_result
205 _msg = _('bad captcha')
206 _msg = _('bad captcha')
206 error_dict = {'recaptcha_field': _msg}
207 error_dict = {'recaptcha_field': _msg}
207 raise formencode.Invalid(_msg, _value, None,
208 raise formencode.Invalid(_msg, _value, None,
208 error_dict=error_dict)
209 error_dict=error_dict)
209
210
210 UserModel().create_registration(form_result)
211 UserModel().create_registration(form_result)
211 h.flash(_('You have successfully registered with RhodeCode'),
212 h.flash(_('You have successfully registered with RhodeCode'),
212 category='success')
213 category='success')
213 Session().commit()
214 Session().commit()
214 return redirect(url('login_home'))
215 return redirect(url('login_home'))
215
216
216 except formencode.Invalid as errors:
217 except formencode.Invalid as errors:
217 return htmlfill.render(
218 return htmlfill.render(
218 render('/register.html'),
219 render('/register.html'),
219 defaults=errors.value,
220 defaults=errors.value,
220 errors=errors.error_dict or {},
221 errors=errors.error_dict or {},
221 prefix_error=False,
222 prefix_error=False,
222 encoding="UTF-8",
223 encoding="UTF-8",
223 force_defaults=False)
224 force_defaults=False)
224 except UserCreationError as e:
225 except UserCreationError as e:
225 # container auth or other auth functions that create users on
226 # container auth or other auth functions that create users on
226 # the fly can throw this exception signaling that there's issue
227 # the fly can throw this exception signaling that there's issue
227 # with user creation, explanation should be provided in
228 # with user creation, explanation should be provided in
228 # Exception itself
229 # Exception itself
229 h.flash(e, 'error')
230 h.flash(e, 'error')
230
231
231 return render('/register.html')
232 return render('/register.html')
232
233
233 def password_reset(self):
234 def password_reset(self):
234 settings = SettingsModel().get_all_settings()
235 settings = SettingsModel().get_all_settings()
235 captcha_private_key = settings.get('rhodecode_captcha_private_key')
236 captcha_private_key = settings.get('rhodecode_captcha_private_key')
236 c.captcha_active = bool(captcha_private_key)
237 c.captcha_active = bool(captcha_private_key)
237 c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
238 c.captcha_public_key = settings.get('rhodecode_captcha_public_key')
238
239
239 if request.POST:
240 if request.POST:
240 password_reset_form = PasswordResetForm()()
241 password_reset_form = PasswordResetForm()()
241 try:
242 try:
242 form_result = password_reset_form.to_python(dict(request.POST))
243 form_result = password_reset_form.to_python(dict(request.POST))
243 if c.captcha_active:
244 if c.captcha_active:
244 response = submit(
245 response = submit(
245 request.POST.get('recaptcha_challenge_field'),
246 request.POST.get('recaptcha_challenge_field'),
246 request.POST.get('recaptcha_response_field'),
247 request.POST.get('recaptcha_response_field'),
247 private_key=captcha_private_key,
248 private_key=captcha_private_key,
248 remoteip=self.ip_addr)
249 remoteip=self.ip_addr)
249 if c.captcha_active and not response.is_valid:
250 if c.captcha_active and not response.is_valid:
250 _value = form_result
251 _value = form_result
251 _msg = _('bad captcha')
252 _msg = _('bad captcha')
252 error_dict = {'recaptcha_field': _msg}
253 error_dict = {'recaptcha_field': _msg}
253 raise formencode.Invalid(_msg, _value, None,
254 raise formencode.Invalid(_msg, _value, None,
254 error_dict=error_dict)
255 error_dict=error_dict)
255 UserModel().reset_password_link(form_result)
256 UserModel().reset_password_link(form_result)
256 h.flash(_('Your password reset link was sent'),
257 h.flash(_('Your password reset link was sent'),
257 category='success')
258 category='success')
258 return redirect(url('login_home'))
259 return redirect(url('login_home'))
259
260
260 except formencode.Invalid as errors:
261 except formencode.Invalid as errors:
261 return htmlfill.render(
262 return htmlfill.render(
262 render('/password_reset.html'),
263 render('/password_reset.html'),
263 defaults=errors.value,
264 defaults=errors.value,
264 errors=errors.error_dict or {},
265 errors=errors.error_dict or {},
265 prefix_error=False,
266 prefix_error=False,
266 encoding="UTF-8",
267 encoding="UTF-8",
267 force_defaults=False)
268 force_defaults=False)
268
269
269 return render('/password_reset.html')
270 return render('/password_reset.html')
270
271
271 def password_reset_confirmation(self):
272 def password_reset_confirmation(self):
272 if request.GET and request.GET.get('key'):
273 if request.GET and request.GET.get('key'):
273 try:
274 try:
274 user = User.get_by_auth_token(request.GET.get('key'))
275 user = User.get_by_auth_token(request.GET.get('key'))
275 data = {'email': user.email}
276 data = {'email': user.email}
276 UserModel().reset_password(data)
277 UserModel().reset_password(data)
277 h.flash(_(
278 h.flash(_(
278 'Your password reset was successful, '
279 'Your password reset was successful, '
279 'a new password has been sent to your email'),
280 'a new password has been sent to your email'),
280 category='success')
281 category='success')
281 except Exception as e:
282 except Exception as e:
282 log.error(e)
283 log.error(e)
283 return redirect(url('reset_password'))
284 return redirect(url('reset_password'))
284
285
285 return redirect(url('login_home'))
286 return redirect(url('login_home'))
286
287
287 @CSRFRequired()
288 @CSRFRequired()
288 def logout(self):
289 def logout(self):
289 LoginSession().destroy_user_session()
290 LoginSession().destroy_user_session()
290 return redirect(url('home'))
291 return redirect(url('home'))
General Comments 0
You need to be logged in to leave comments. Login now