##// END OF EJS Templates
login: inline _redirect_to_origin...
Mads Kiilerich -
r5508:b98f4431 stable
parent child Browse files
Show More
@@ -1,270 +1,267 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.controllers.login
15 kallithea.controllers.login
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 Login controller for Kallithea
18 Login controller for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Apr 22, 2010
22 :created_on: Apr 22, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import logging
29 import logging
30 import formencode
30 import formencode
31 import urlparse
31 import urlparse
32
32
33 from formencode import htmlfill
33 from formencode import htmlfill
34 from webob.exc import HTTPFound, HTTPBadRequest
34 from webob.exc import HTTPFound, HTTPBadRequest
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36 from pylons.controllers.util import redirect
36 from pylons.controllers.util import redirect
37 from pylons import request, session, tmpl_context as c, url
37 from pylons import request, session, tmpl_context as c, url
38
38
39 import kallithea.lib.helpers as h
39 import kallithea.lib.helpers as h
40 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
40 from kallithea.lib.auth import AuthUser, HasPermissionAnyDecorator
41 from kallithea.lib.base import BaseController, log_in_user, render
41 from kallithea.lib.base import BaseController, log_in_user, render
42 from kallithea.lib.exceptions import UserCreationError
42 from kallithea.lib.exceptions import UserCreationError
43 from kallithea.lib.utils2 import safe_str
43 from kallithea.lib.utils2 import safe_str
44 from kallithea.model.db import User, Setting
44 from kallithea.model.db import User, Setting
45 from kallithea.model.forms import \
45 from kallithea.model.forms import \
46 LoginForm, RegisterForm, PasswordResetRequestForm, PasswordResetConfirmationForm
46 LoginForm, RegisterForm, PasswordResetRequestForm, PasswordResetConfirmationForm
47 from kallithea.model.user import UserModel
47 from kallithea.model.user import UserModel
48 from kallithea.model.meta import Session
48 from kallithea.model.meta import Session
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class LoginController(BaseController):
54 class LoginController(BaseController):
55
55
56 def __before__(self):
56 def __before__(self):
57 super(LoginController, self).__before__()
57 super(LoginController, self).__before__()
58
58
59 def _validate_came_from(self, came_from):
59 def _validate_came_from(self, came_from):
60 """Return True if came_from is valid and can and should be used"""
60 """Return True if came_from is valid and can and should be used"""
61 if not came_from:
61 if not came_from:
62 return False
62 return False
63
63
64 parsed = urlparse.urlparse(came_from)
64 parsed = urlparse.urlparse(came_from)
65 server_parsed = urlparse.urlparse(url.current())
65 server_parsed = urlparse.urlparse(url.current())
66 allowed_schemes = ['http', 'https']
66 allowed_schemes = ['http', 'https']
67 if parsed.scheme and parsed.scheme not in allowed_schemes:
67 if parsed.scheme and parsed.scheme not in allowed_schemes:
68 log.error('Suspicious URL scheme detected %s for url %s',
68 log.error('Suspicious URL scheme detected %s for url %s',
69 parsed.scheme, parsed)
69 parsed.scheme, parsed)
70 return False
70 return False
71 if server_parsed.netloc != parsed.netloc:
71 if server_parsed.netloc != parsed.netloc:
72 log.error('Suspicious NETLOC detected %s for url %s server url '
72 log.error('Suspicious NETLOC detected %s for url %s server url '
73 'is: %s' % (parsed.netloc, parsed, server_parsed))
73 'is: %s' % (parsed.netloc, parsed, server_parsed))
74 return False
74 return False
75 return True
75 return True
76
76
77 def _redirect_to_origin(self, origin):
78 '''redirect to the original page, preserving any get arguments given'''
79 request.GET.pop('came_from', None)
80 raise HTTPFound(location=url(origin, **request.GET))
81
82 def index(self):
77 def index(self):
83 c.came_from = safe_str(request.GET.get('came_from', ''))
78 c.came_from = safe_str(request.GET.pop('came_from', ''))
84 if not self._validate_came_from(c.came_from):
79 if self._validate_came_from(c.came_from):
85 c.came_from = url('home')
80 came_from = url(c.came_from, **request.GET)
81 else:
82 c.came_from = came_from = url('home')
86
83
87 not_default = self.authuser.username != User.DEFAULT_USER
84 not_default = self.authuser.username != User.DEFAULT_USER
88 ip_allowed = AuthUser.check_ip_allowed(self.authuser, self.ip_addr)
85 ip_allowed = AuthUser.check_ip_allowed(self.authuser, self.ip_addr)
89
86
90 # redirect if already logged in
87 # redirect if already logged in
91 if self.authuser.is_authenticated and not_default and ip_allowed:
88 if self.authuser.is_authenticated and not_default and ip_allowed:
92 return self._redirect_to_origin(c.came_from)
89 raise HTTPFound(location=came_from)
93
90
94 if request.POST:
91 if request.POST:
95 # import Login Form validator class
92 # import Login Form validator class
96 login_form = LoginForm()
93 login_form = LoginForm()
97 try:
94 try:
98 c.form_result = login_form.to_python(dict(request.POST))
95 c.form_result = login_form.to_python(dict(request.POST))
99 # form checks for username/password, now we're authenticated
96 # form checks for username/password, now we're authenticated
100 username = c.form_result['username']
97 username = c.form_result['username']
101 user = User.get_by_username(username, case_insensitive=True)
98 user = User.get_by_username(username, case_insensitive=True)
102 except formencode.Invalid as errors:
99 except formencode.Invalid as errors:
103 defaults = errors.value
100 defaults = errors.value
104 # remove password from filling in form again
101 # remove password from filling in form again
105 del defaults['password']
102 del defaults['password']
106 return htmlfill.render(
103 return htmlfill.render(
107 render('/login.html'),
104 render('/login.html'),
108 defaults=errors.value,
105 defaults=errors.value,
109 errors=errors.error_dict or {},
106 errors=errors.error_dict or {},
110 prefix_error=False,
107 prefix_error=False,
111 encoding="UTF-8",
108 encoding="UTF-8",
112 force_defaults=False)
109 force_defaults=False)
113 except UserCreationError as e:
110 except UserCreationError as e:
114 # container auth or other auth functions that create users on
111 # container auth or other auth functions that create users on
115 # the fly can throw this exception signaling that there's issue
112 # the fly can throw this exception signaling that there's issue
116 # with user creation, explanation should be provided in
113 # with user creation, explanation should be provided in
117 # Exception itself
114 # Exception itself
118 h.flash(e, 'error')
115 h.flash(e, 'error')
119 else:
116 else:
120 log_in_user(user, c.form_result['remember'],
117 log_in_user(user, c.form_result['remember'],
121 is_external_auth=False)
118 is_external_auth=False)
122 return self._redirect_to_origin(c.came_from)
119 raise HTTPFound(location=came_from)
123
120
124 return render('/login.html')
121 return render('/login.html')
125
122
126 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
123 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
127 'hg.register.manual_activate')
124 'hg.register.manual_activate')
128 def register(self):
125 def register(self):
129 c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
126 c.auto_active = 'hg.register.auto_activate' in User.get_default_user()\
130 .AuthUser.permissions['global']
127 .AuthUser.permissions['global']
131
128
132 settings = Setting.get_app_settings()
129 settings = Setting.get_app_settings()
133 captcha_private_key = settings.get('captcha_private_key')
130 captcha_private_key = settings.get('captcha_private_key')
134 c.captcha_active = bool(captcha_private_key)
131 c.captcha_active = bool(captcha_private_key)
135 c.captcha_public_key = settings.get('captcha_public_key')
132 c.captcha_public_key = settings.get('captcha_public_key')
136
133
137 if request.POST:
134 if request.POST:
138 register_form = RegisterForm()()
135 register_form = RegisterForm()()
139 try:
136 try:
140 form_result = register_form.to_python(dict(request.POST))
137 form_result = register_form.to_python(dict(request.POST))
141 form_result['active'] = c.auto_active
138 form_result['active'] = c.auto_active
142
139
143 if c.captcha_active:
140 if c.captcha_active:
144 from kallithea.lib.recaptcha import submit
141 from kallithea.lib.recaptcha import submit
145 response = submit(request.POST.get('recaptcha_challenge_field'),
142 response = submit(request.POST.get('recaptcha_challenge_field'),
146 request.POST.get('recaptcha_response_field'),
143 request.POST.get('recaptcha_response_field'),
147 private_key=captcha_private_key,
144 private_key=captcha_private_key,
148 remoteip=self.ip_addr)
145 remoteip=self.ip_addr)
149 if c.captcha_active and not response.is_valid:
146 if c.captcha_active and not response.is_valid:
150 _value = form_result
147 _value = form_result
151 _msg = _('Bad captcha')
148 _msg = _('Bad captcha')
152 error_dict = {'recaptcha_field': _msg}
149 error_dict = {'recaptcha_field': _msg}
153 raise formencode.Invalid(_msg, _value, None,
150 raise formencode.Invalid(_msg, _value, None,
154 error_dict=error_dict)
151 error_dict=error_dict)
155
152
156 UserModel().create_registration(form_result)
153 UserModel().create_registration(form_result)
157 h.flash(_('You have successfully registered into Kallithea'),
154 h.flash(_('You have successfully registered into Kallithea'),
158 category='success')
155 category='success')
159 Session().commit()
156 Session().commit()
160 return redirect(url('login_home'))
157 return redirect(url('login_home'))
161
158
162 except formencode.Invalid as errors:
159 except formencode.Invalid as errors:
163 return htmlfill.render(
160 return htmlfill.render(
164 render('/register.html'),
161 render('/register.html'),
165 defaults=errors.value,
162 defaults=errors.value,
166 errors=errors.error_dict or {},
163 errors=errors.error_dict or {},
167 prefix_error=False,
164 prefix_error=False,
168 encoding="UTF-8",
165 encoding="UTF-8",
169 force_defaults=False)
166 force_defaults=False)
170 except UserCreationError as e:
167 except UserCreationError as e:
171 # container auth or other auth functions that create users on
168 # container auth or other auth functions that create users on
172 # the fly can throw this exception signaling that there's issue
169 # the fly can throw this exception signaling that there's issue
173 # with user creation, explanation should be provided in
170 # with user creation, explanation should be provided in
174 # Exception itself
171 # Exception itself
175 h.flash(e, 'error')
172 h.flash(e, 'error')
176
173
177 return render('/register.html')
174 return render('/register.html')
178
175
179 def password_reset(self):
176 def password_reset(self):
180 settings = Setting.get_app_settings()
177 settings = Setting.get_app_settings()
181 captcha_private_key = settings.get('captcha_private_key')
178 captcha_private_key = settings.get('captcha_private_key')
182 c.captcha_active = bool(captcha_private_key)
179 c.captcha_active = bool(captcha_private_key)
183 c.captcha_public_key = settings.get('captcha_public_key')
180 c.captcha_public_key = settings.get('captcha_public_key')
184
181
185 if request.POST:
182 if request.POST:
186 password_reset_form = PasswordResetRequestForm()()
183 password_reset_form = PasswordResetRequestForm()()
187 try:
184 try:
188 form_result = password_reset_form.to_python(dict(request.POST))
185 form_result = password_reset_form.to_python(dict(request.POST))
189 if c.captcha_active:
186 if c.captcha_active:
190 from kallithea.lib.recaptcha import submit
187 from kallithea.lib.recaptcha import submit
191 response = submit(request.POST.get('recaptcha_challenge_field'),
188 response = submit(request.POST.get('recaptcha_challenge_field'),
192 request.POST.get('recaptcha_response_field'),
189 request.POST.get('recaptcha_response_field'),
193 private_key=captcha_private_key,
190 private_key=captcha_private_key,
194 remoteip=self.ip_addr)
191 remoteip=self.ip_addr)
195 if c.captcha_active and not response.is_valid:
192 if c.captcha_active and not response.is_valid:
196 _value = form_result
193 _value = form_result
197 _msg = _('Bad captcha')
194 _msg = _('Bad captcha')
198 error_dict = {'recaptcha_field': _msg}
195 error_dict = {'recaptcha_field': _msg}
199 raise formencode.Invalid(_msg, _value, None,
196 raise formencode.Invalid(_msg, _value, None,
200 error_dict=error_dict)
197 error_dict=error_dict)
201 redirect_link = UserModel().send_reset_password_email(form_result)
198 redirect_link = UserModel().send_reset_password_email(form_result)
202 h.flash(_('A password reset confirmation code has been sent'),
199 h.flash(_('A password reset confirmation code has been sent'),
203 category='success')
200 category='success')
204 return redirect(redirect_link)
201 return redirect(redirect_link)
205
202
206 except formencode.Invalid as errors:
203 except formencode.Invalid as errors:
207 return htmlfill.render(
204 return htmlfill.render(
208 render('/password_reset.html'),
205 render('/password_reset.html'),
209 defaults=errors.value,
206 defaults=errors.value,
210 errors=errors.error_dict or {},
207 errors=errors.error_dict or {},
211 prefix_error=False,
208 prefix_error=False,
212 encoding="UTF-8",
209 encoding="UTF-8",
213 force_defaults=False)
210 force_defaults=False)
214
211
215 return render('/password_reset.html')
212 return render('/password_reset.html')
216
213
217 def password_reset_confirmation(self):
214 def password_reset_confirmation(self):
218 # This controller handles both GET and POST requests, though we
215 # This controller handles both GET and POST requests, though we
219 # only ever perform the actual password change on POST (since
216 # only ever perform the actual password change on POST (since
220 # GET requests are not allowed to have side effects, and do not
217 # GET requests are not allowed to have side effects, and do not
221 # receive automatic CSRF protection).
218 # receive automatic CSRF protection).
222
219
223 # The template needs the email address outside of the form.
220 # The template needs the email address outside of the form.
224 c.email = request.params.get('email')
221 c.email = request.params.get('email')
225
222
226 if not request.POST:
223 if not request.POST:
227 return htmlfill.render(
224 return htmlfill.render(
228 render('/password_reset_confirmation.html'),
225 render('/password_reset_confirmation.html'),
229 defaults=dict(request.params),
226 defaults=dict(request.params),
230 encoding='UTF-8')
227 encoding='UTF-8')
231
228
232 form = PasswordResetConfirmationForm()()
229 form = PasswordResetConfirmationForm()()
233 try:
230 try:
234 form_result = form.to_python(dict(request.POST))
231 form_result = form.to_python(dict(request.POST))
235 except formencode.Invalid as errors:
232 except formencode.Invalid as errors:
236 return htmlfill.render(
233 return htmlfill.render(
237 render('/password_reset_confirmation.html'),
234 render('/password_reset_confirmation.html'),
238 defaults=errors.value,
235 defaults=errors.value,
239 errors=errors.error_dict or {},
236 errors=errors.error_dict or {},
240 prefix_error=False,
237 prefix_error=False,
241 encoding='UTF-8')
238 encoding='UTF-8')
242
239
243 if not UserModel().verify_reset_password_token(
240 if not UserModel().verify_reset_password_token(
244 form_result['email'],
241 form_result['email'],
245 form_result['timestamp'],
242 form_result['timestamp'],
246 form_result['token'],
243 form_result['token'],
247 ):
244 ):
248 return htmlfill.render(
245 return htmlfill.render(
249 render('/password_reset_confirmation.html'),
246 render('/password_reset_confirmation.html'),
250 defaults=form_result,
247 defaults=form_result,
251 errors={'token': _('Invalid password reset token')},
248 errors={'token': _('Invalid password reset token')},
252 prefix_error=False,
249 prefix_error=False,
253 encoding='UTF-8')
250 encoding='UTF-8')
254
251
255 UserModel().reset_password(form_result['email'], form_result['password'])
252 UserModel().reset_password(form_result['email'], form_result['password'])
256 h.flash(_('Successfully updated password'), category='success')
253 h.flash(_('Successfully updated password'), category='success')
257 return redirect(url('login_home'))
254 return redirect(url('login_home'))
258
255
259 def logout(self):
256 def logout(self):
260 session.delete()
257 session.delete()
261 log.info('Logging out and deleting session for user')
258 log.info('Logging out and deleting session for user')
262 redirect(url('home'))
259 redirect(url('home'))
263
260
264 def authentication_token(self):
261 def authentication_token(self):
265 """Return the CSRF protection token for the session - just like it
262 """Return the CSRF protection token for the session - just like it
266 could have been screen scraped from a page with a form.
263 could have been screen scraped from a page with a form.
267 Only intended for testing but might also be useful for other kinds
264 Only intended for testing but might also be useful for other kinds
268 of automation.
265 of automation.
269 """
266 """
270 return h.authentication_token()
267 return h.authentication_token()
@@ -1,73 +1,73 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base/root.html"/>
2 <%inherit file="base/root.html"/>
3
3
4 <%block name="title">
4 <%block name="title">
5 ${_('Log In')}
5 ${_('Log In')}
6 </%block>
6 </%block>
7
7
8 <div id="login" class="panel panel-default">
8 <div id="login" class="panel panel-default">
9 <%include file="/base/flash_msg.html"/>
9 <%include file="/base/flash_msg.html"/>
10 <!-- login -->
10 <!-- login -->
11 <div class="panel-heading title withlogo">
11 <div class="panel-heading title withlogo">
12 %if c.site_name:
12 %if c.site_name:
13 <h5>${_('Log In to %s') % c.site_name}</h5>
13 <h5>${_('Log In to %s') % c.site_name}</h5>
14 %else:
14 %else:
15 <h5>${_('Log In')}</h5>
15 <h5>${_('Log In')}</h5>
16 %endif
16 %endif
17 </div>
17 </div>
18 <div class="panel-body inner">
18 <div class="panel-body inner">
19 ${h.form(h.url.current(**request.GET))}
19 ${h.form(h.url.current(came_from=c.came_from, **request.GET))}
20 <div class="form">
20 <div class="form">
21 <i class="icon-lock"></i>
21 <i class="icon-lock"></i>
22 <!-- fields -->
22 <!-- fields -->
23
23
24 <div class="form-horizontal">
24 <div class="form-horizontal">
25 <div class="form-group">
25 <div class="form-group">
26 <label class="control-label col-sm-5" for="username">${_('Username')}:</label>
26 <label class="control-label col-sm-5" for="username">${_('Username')}:</label>
27 <div class="input col-sm-7">
27 <div class="input col-sm-7">
28 ${h.text('username',class_='form-control focus large')}
28 ${h.text('username',class_='form-control focus large')}
29 </div>
29 </div>
30
30
31 </div>
31 </div>
32 <div class="form-group">
32 <div class="form-group">
33 <label class="control-label col-sm-5" for="password">${_('Password')}:</label>
33 <label class="control-label col-sm-5" for="password">${_('Password')}:</label>
34 <div class="input col-sm-7">
34 <div class="input col-sm-7">
35 ${h.password('password',class_='form-control focus large')}
35 ${h.password('password',class_='form-control focus large')}
36 </div>
36 </div>
37
37
38 </div>
38 </div>
39 <div class="form-group">
39 <div class="form-group">
40 <div class="col-sm-offset-5 col-sm-7">
40 <div class="col-sm-offset-5 col-sm-7">
41 <div class="checkbox">
41 <div class="checkbox">
42 <label for="remember">
42 <label for="remember">
43 <input type="checkbox" id="remember" name="remember"/>
43 <input type="checkbox" id="remember" name="remember"/>
44 ${_('Remember me')}
44 ${_('Remember me')}
45 </label>
45 </label>
46 </div>
46 </div>
47 </div>
47 </div>
48 </div>
48 </div>
49 </div>
49 </div>
50 <!-- end fields -->
50 <!-- end fields -->
51 <!-- links -->
51 <!-- links -->
52 <div class="links">
52 <div class="links">
53 ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
53 ${h.link_to(_('Forgot your password ?'),h.url('reset_password'))}
54 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
54 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
55 /
55 /
56 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
56 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
57 %endif
57 %endif
58 <span class="buttons">
58 <span class="buttons">
59 ${h.submit('sign_in',_('Sign In'),class_="btn btn-default")}
59 ${h.submit('sign_in',_('Sign In'),class_="btn btn-default")}
60 </span>
60 </span>
61 </div>
61 </div>
62
62
63 <!-- end links -->
63 <!-- end links -->
64 </div>
64 </div>
65 ${h.end_form()}
65 ${h.end_form()}
66 <script type="text/javascript">
66 <script type="text/javascript">
67 $(document).ready(function(){
67 $(document).ready(function(){
68 $('#username').focus();
68 $('#username').focus();
69 });
69 });
70 </script>
70 </script>
71 </div>
71 </div>
72 <!-- end login -->
72 <!-- end login -->
73 </div>
73 </div>
General Comments 0
You need to be logged in to leave comments. Login now