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