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