##// END OF EJS Templates
removed ftp from allowed schemas...
marcink -
r2679:dffb9222 beta
parent child Browse files
Show More
@@ -1,197 +1,196 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.login
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Login controller for rhodeocode
7 7
8 8 :created_on: Apr 22, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import formencode
28 28 import datetime
29 29 import urlparse
30 30
31 31 from formencode import htmlfill
32 32 from webob.exc import HTTPFound
33 33 from pylons.i18n.translation import _
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons import request, response, session, tmpl_context as c, url
36 36
37 37 import rhodecode.lib.helpers as h
38 38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.model.db import User
41 41 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
42 42 from rhodecode.model.user import UserModel
43 43 from rhodecode.model.meta import Session
44 44
45 45
46
47 46 log = logging.getLogger(__name__)
48 47
49 48
50 49 class LoginController(BaseController):
51 50
52 51 def __before__(self):
53 52 super(LoginController, self).__before__()
54 53
55 54 def index(self):
56 55 # redirect if already logged in
57 c.came_from = request.GET.get('came_from', None)
56 c.came_from = request.GET.get('came_from')
58 57
59 58 if self.rhodecode_user.is_authenticated \
60 59 and self.rhodecode_user.username != 'default':
61 60
62 61 return redirect(url('home'))
63 62
64 63 if request.POST:
65 64 # import Login Form validator class
66 65 login_form = LoginForm()
67 66 try:
68 67 session.invalidate()
69 68 c.form_result = login_form.to_python(dict(request.POST))
70 69 # form checks for username/password, now we're authenticated
71 70 username = c.form_result['username']
72 71 user = User.get_by_username(username, case_insensitive=True)
73 72 auth_user = AuthUser(user.user_id)
74 73 auth_user.set_authenticated()
75 74 cs = auth_user.get_cookie_store()
76 75 session['rhodecode_user'] = cs
77 76 user.update_lastlogin()
78 77 Session().commit()
79 78
80 79 # If they want to be remembered, update the cookie
81 80 if c.form_result['remember'] is not False:
82 81 _year = (datetime.datetime.now() +
83 82 datetime.timedelta(seconds=60 * 60 * 24 * 365))
84 83 session._set_cookie_expires(_year)
85 84
86 85 session.save()
87 86
88 87 log.info('user %s is now authenticated and stored in '
89 88 'session, session attrs %s' % (username, cs))
90 89
91 90 # dumps session attrs back to cookie
92 91 session._update_cookie_out()
93 92
94 93 # we set new cookie
95 94 headers = None
96 95 if session.request['set_cookie']:
97 96 # send set-cookie headers back to response to update cookie
98 97 headers = [('Set-Cookie', session.request['cookie_out'])]
99 98
100 allowed_schemes = ['http', 'https', 'ftp']
101 parsed = urlparse.urlparse(c.came_from)
102 server_parsed = urlparse.urlparse(url.current())
103
104 if parsed.scheme and parsed.scheme not in allowed_schemes:
105 log.error('Suspicious URL scheme detected %s for url %s' %
106 (parsed.scheme, parsed))
107 c.came_from = url('home')
108 elif server_parsed.netloc != parsed.netloc:
109 log.error('Suspicious NETLOC detected %s for url %s'
110 'server url is: %s' %
111 (parsed.netloc, parsed, server_parsed))
112 c.came_from = url('home')
99 allowed_schemes = ['http', 'https']
113 100 if c.came_from:
101 parsed = urlparse.urlparse(c.came_from)
102 server_parsed = urlparse.urlparse(url.current())
103 if parsed.scheme and parsed.scheme not in allowed_schemes:
104 log.error(
105 'Suspicious URL scheme detected %s for url %s' %
106 (parsed.scheme, parsed))
107 c.came_from = url('home')
108 elif server_parsed.netloc != parsed.netloc:
109 log.error('Suspicious NETLOC detected %s for url %s'
110 'server url is: %s' %
111 (parsed.netloc, parsed, server_parsed))
112 c.came_from = url('home')
114 113 raise HTTPFound(location=c.came_from, headers=headers)
115 114 else:
116 115 raise HTTPFound(location=url('home'), headers=headers)
117 116
118 117 except formencode.Invalid, errors:
119 118 return htmlfill.render(
120 119 render('/login.html'),
121 120 defaults=errors.value,
122 121 errors=errors.error_dict or {},
123 122 prefix_error=False,
124 123 encoding="UTF-8")
125 124
126 125 return render('/login.html')
127 126
128 127 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
129 128 'hg.register.manual_activate')
130 129 def register(self):
131 130 c.auto_active = False
132 131 for perm in User.get_by_username('default').user_perms:
133 132 if perm.permission.permission_name == 'hg.register.auto_activate':
134 133 c.auto_active = True
135 134 break
136 135
137 136 if request.POST:
138 137
139 138 register_form = RegisterForm()()
140 139 try:
141 140 form_result = register_form.to_python(dict(request.POST))
142 141 form_result['active'] = c.auto_active
143 142 UserModel().create_registration(form_result)
144 143 h.flash(_('You have successfully registered into rhodecode'),
145 144 category='success')
146 145 Session().commit()
147 146 return redirect(url('login_home'))
148 147
149 148 except formencode.Invalid, errors:
150 149 return htmlfill.render(
151 150 render('/register.html'),
152 151 defaults=errors.value,
153 152 errors=errors.error_dict or {},
154 153 prefix_error=False,
155 154 encoding="UTF-8")
156 155
157 156 return render('/register.html')
158 157
159 158 def password_reset(self):
160 159 if request.POST:
161 160 password_reset_form = PasswordResetForm()()
162 161 try:
163 162 form_result = password_reset_form.to_python(dict(request.POST))
164 163 UserModel().reset_password_link(form_result)
165 164 h.flash(_('Your password reset link was sent'),
166 165 category='success')
167 166 return redirect(url('login_home'))
168 167
169 168 except formencode.Invalid, errors:
170 169 return htmlfill.render(
171 170 render('/password_reset.html'),
172 171 defaults=errors.value,
173 172 errors=errors.error_dict or {},
174 173 prefix_error=False,
175 174 encoding="UTF-8")
176 175
177 176 return render('/password_reset.html')
178 177
179 178 def password_reset_confirmation(self):
180 179 if request.GET and request.GET.get('key'):
181 180 try:
182 181 user = User.get_by_api_key(request.GET.get('key'))
183 182 data = dict(email=user.email)
184 183 UserModel().reset_password(data)
185 184 h.flash(_('Your password reset was successful, '
186 185 'new password has been sent to your email'),
187 186 category='success')
188 187 except Exception, e:
189 188 log.error(e)
190 189 return redirect(url('reset_password'))
191 190
192 191 return redirect(url('login_home'))
193 192
194 193 def logout(self):
195 194 session.delete()
196 195 log.info('Logging out and deleting session for user')
197 196 redirect(url('home'))
@@ -1,272 +1,291 b''
1 1 # -*- coding: utf-8 -*-
2 2 from rhodecode.tests import *
3 3 from rhodecode.model.db import User, Notification
4 4 from rhodecode.lib.utils2 import generate_api_key
5 5 from rhodecode.lib.auth import check_password
6 6 from rhodecode.lib import helpers as h
7 7 from rhodecode.model import validators
8 8
9 9
10 10 class TestLoginController(TestController):
11 11
12 12 def tearDown(self):
13 13 for n in Notification.query().all():
14 14 self.Session().delete(n)
15 15
16 16 self.Session().commit()
17 17 self.assertEqual(Notification.query().all(), [])
18 18
19 19 def test_index(self):
20 20 response = self.app.get(url(controller='login', action='index'))
21 21 self.assertEqual(response.status, '200 OK')
22 22 # Test response...
23 23
24 24 def test_login_admin_ok(self):
25 25 response = self.app.post(url(controller='login', action='index'),
26 26 {'username': 'test_admin',
27 27 'password': 'test12'})
28 28 self.assertEqual(response.status, '302 Found')
29 29 self.assertEqual(response.session['rhodecode_user'].get('username'),
30 30 'test_admin')
31 31 response = response.follow()
32 32 self.assertTrue('%s repository' % HG_REPO in response.body)
33 33
34 34 def test_login_regular_ok(self):
35 35 response = self.app.post(url(controller='login', action='index'),
36 36 {'username': 'test_regular',
37 37 'password': 'test12'})
38 38
39 39 self.assertEqual(response.status, '302 Found')
40 40 self.assertEqual(response.session['rhodecode_user'].get('username'),
41 41 'test_regular')
42 42 response = response.follow()
43 43 self.assertTrue('%s repository' % HG_REPO in response.body)
44 44 self.assertTrue('<a title="Admin" href="/_admin">' not in response.body)
45 45
46 46 def test_login_ok_came_from(self):
47 47 test_came_from = '/_admin/users'
48 48 response = self.app.post(url(controller='login', action='index',
49 49 came_from=test_came_from),
50 50 {'username': 'test_admin',
51 51 'password': 'test12'})
52 52 self.assertEqual(response.status, '302 Found')
53 53 response = response.follow()
54 54
55 55 self.assertEqual(response.status, '200 OK')
56 56 self.assertTrue('Users administration' in response.body)
57 57
58 @parameterized.expand([
59 ('data:text/html,<script>window.alert("xss")</script>',),
60 ('mailto:test@rhodecode.org',),
61 ('file:///etc/passwd',),
62 ('ftp://some.ftp.server',),
63 ('http://other.domain',),
64 ])
65 def test_login_bad_came_froms(self, url_came_from):
66 response = self.app.post(url(controller='login', action='index',
67 came_from=url_came_from),
68 {'username': 'test_admin',
69 'password': 'test12'})
70 self.assertEqual(response.status, '302 Found')
71 self.assertEqual(response._environ['paste.testing_variables']
72 ['tmpl_context'].came_from, '/')
73 response = response.follow()
74
75 self.assertEqual(response.status, '200 OK')
76
58 77 def test_login_short_password(self):
59 78 response = self.app.post(url(controller='login', action='index'),
60 79 {'username': 'test_admin',
61 80 'password': 'as'})
62 81 self.assertEqual(response.status, '200 OK')
63 82
64 83 self.assertTrue('Enter 3 characters or more' in response.body)
65 84
66 85 def test_login_wrong_username_password(self):
67 86 response = self.app.post(url(controller='login', action='index'),
68 87 {'username': 'error',
69 88 'password': 'test12'})
70 89
71 90 self.assertTrue('invalid user name' in response.body)
72 91 self.assertTrue('invalid password' in response.body)
73 92
74 93 #==========================================================================
75 94 # REGISTRATIONS
76 95 #==========================================================================
77 96 def test_register(self):
78 97 response = self.app.get(url(controller='login', action='register'))
79 98 self.assertTrue('Sign Up to RhodeCode' in response.body)
80 99
81 100 def test_register_err_same_username(self):
82 101 uname = 'test_admin'
83 102 response = self.app.post(url(controller='login', action='register'),
84 103 {'username': uname,
85 104 'password': 'test12',
86 105 'password_confirmation': 'test12',
87 106 'email': 'goodmail@domain.com',
88 107 'firstname': 'test',
89 108 'lastname': 'test'})
90 109
91 110 msg = validators.ValidUsername()._messages['username_exists']
92 111 msg = h.html_escape(msg % {'username': uname})
93 112 response.mustcontain(msg)
94 113
95 114 def test_register_err_same_email(self):
96 115 response = self.app.post(url(controller='login', action='register'),
97 116 {'username': 'test_admin_0',
98 117 'password': 'test12',
99 118 'password_confirmation': 'test12',
100 119 'email': 'test_admin@mail.com',
101 120 'firstname': 'test',
102 121 'lastname': 'test'})
103 122
104 123 msg = validators.UniqSystemEmail()()._messages['email_taken']
105 124 response.mustcontain(msg)
106 125
107 126 def test_register_err_same_email_case_sensitive(self):
108 127 response = self.app.post(url(controller='login', action='register'),
109 128 {'username': 'test_admin_1',
110 129 'password': 'test12',
111 130 'password_confirmation': 'test12',
112 131 'email': 'TesT_Admin@mail.COM',
113 132 'firstname': 'test',
114 133 'lastname': 'test'})
115 134 msg = validators.UniqSystemEmail()()._messages['email_taken']
116 135 response.mustcontain(msg)
117 136
118 137 def test_register_err_wrong_data(self):
119 138 response = self.app.post(url(controller='login', action='register'),
120 139 {'username': 'xs',
121 140 'password': 'test',
122 141 'password_confirmation': 'test',
123 142 'email': 'goodmailm',
124 143 'firstname': 'test',
125 144 'lastname': 'test'})
126 145 self.assertEqual(response.status, '200 OK')
127 146 response.mustcontain('An email address must contain a single @')
128 147 response.mustcontain('Enter a value 6 characters long or more')
129 148
130 149 def test_register_err_username(self):
131 150 response = self.app.post(url(controller='login', action='register'),
132 151 {'username': 'error user',
133 152 'password': 'test12',
134 153 'password_confirmation': 'test12',
135 154 'email': 'goodmailm',
136 155 'firstname': 'test',
137 156 'lastname': 'test'})
138 157
139 158 response.mustcontain('An email address must contain a single @')
140 159 response.mustcontain('Username may only contain '
141 160 'alphanumeric characters underscores, '
142 161 'periods or dashes and must begin with '
143 162 'alphanumeric character')
144 163
145 164 def test_register_err_case_sensitive(self):
146 165 usr = 'Test_Admin'
147 166 response = self.app.post(url(controller='login', action='register'),
148 167 {'username': usr,
149 168 'password': 'test12',
150 169 'password_confirmation': 'test12',
151 170 'email': 'goodmailm',
152 171 'firstname': 'test',
153 172 'lastname': 'test'})
154 173
155 174 response.mustcontain('An email address must contain a single @')
156 175 msg = validators.ValidUsername()._messages['username_exists']
157 176 msg = h.html_escape(msg % {'username': usr})
158 177 response.mustcontain(msg)
159 178
160 179 def test_register_special_chars(self):
161 180 response = self.app.post(url(controller='login', action='register'),
162 181 {'username': 'xxxaxn',
163 182 'password': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
164 183 'password_confirmation': 'Δ…Δ‡ΕΊΕΌΔ…Ε›Ε›Ε›Ε›',
165 184 'email': 'goodmailm@test.plx',
166 185 'firstname': 'test',
167 186 'lastname': 'test'})
168 187
169 188 msg = validators.ValidPassword()._messages['invalid_password']
170 189 response.mustcontain(msg)
171 190
172 191 def test_register_password_mismatch(self):
173 192 response = self.app.post(url(controller='login', action='register'),
174 193 {'username': 'xs',
175 194 'password': '123qwe',
176 195 'password_confirmation': 'qwe123',
177 196 'email': 'goodmailm@test.plxa',
178 197 'firstname': 'test',
179 198 'lastname': 'test'})
180 199 msg = validators.ValidPasswordsMatch()._messages['password_mismatch']
181 200 response.mustcontain(msg)
182 201
183 202 def test_register_ok(self):
184 203 username = 'test_regular4'
185 204 password = 'qweqwe'
186 205 email = 'marcin@test.com'
187 206 name = 'testname'
188 207 lastname = 'testlastname'
189 208
190 209 response = self.app.post(url(controller='login', action='register'),
191 210 {'username': username,
192 211 'password': password,
193 212 'password_confirmation': password,
194 213 'email': email,
195 214 'firstname': name,
196 215 'lastname': lastname,
197 216 'admin': True}) # This should be overriden
198 217 self.assertEqual(response.status, '302 Found')
199 218 self.checkSessionFlash(response, 'You have successfully registered into rhodecode')
200 219
201 220 ret = self.Session().query(User).filter(User.username == 'test_regular4').one()
202 221 self.assertEqual(ret.username, username)
203 222 self.assertEqual(check_password(password, ret.password), True)
204 223 self.assertEqual(ret.email, email)
205 224 self.assertEqual(ret.name, name)
206 225 self.assertEqual(ret.lastname, lastname)
207 226 self.assertNotEqual(ret.api_key, None)
208 227 self.assertEqual(ret.admin, False)
209 228
210 229 def test_forgot_password_wrong_mail(self):
211 230 bad_email = 'marcin@wrongmail.org'
212 231 response = self.app.post(
213 232 url(controller='login', action='password_reset'),
214 233 {'email': bad_email, }
215 234 )
216 235
217 236 msg = validators.ValidSystemEmail()._messages['non_existing_email']
218 237 msg = h.html_escape(msg % {'email': bad_email})
219 238 response.mustcontain()
220 239
221 240 def test_forgot_password(self):
222 241 response = self.app.get(url(controller='login',
223 242 action='password_reset'))
224 243 self.assertEqual(response.status, '200 OK')
225 244
226 245 username = 'test_password_reset_1'
227 246 password = 'qweqwe'
228 247 email = 'marcin@python-works.com'
229 248 name = 'passwd'
230 249 lastname = 'reset'
231 250
232 251 new = User()
233 252 new.username = username
234 253 new.password = password
235 254 new.email = email
236 255 new.name = name
237 256 new.lastname = lastname
238 257 new.api_key = generate_api_key(username)
239 258 self.Session().add(new)
240 259 self.Session().commit()
241 260
242 261 response = self.app.post(url(controller='login',
243 262 action='password_reset'),
244 263 {'email': email, })
245 264
246 265 self.checkSessionFlash(response, 'Your password reset link was sent')
247 266
248 267 response = response.follow()
249 268
250 269 # BAD KEY
251 270
252 271 key = "bad"
253 272 response = self.app.get(url(controller='login',
254 273 action='password_reset_confirmation',
255 274 key=key))
256 275 self.assertEqual(response.status, '302 Found')
257 276 self.assertTrue(response.location.endswith(url('reset_password')))
258 277
259 278 # GOOD KEY
260 279
261 280 key = User.get_by_username(username).api_key
262 281 response = self.app.get(url(controller='login',
263 282 action='password_reset_confirmation',
264 283 key=key))
265 284 self.assertEqual(response.status, '302 Found')
266 285 self.assertTrue(response.location.endswith(url('login_home')))
267 286
268 287 self.checkSessionFlash(response,
269 288 ('Your password reset was successful, '
270 289 'new password has been sent to your email'))
271 290
272 291 response = response.follow()
General Comments 0
You need to be logged in to leave comments. Login now