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