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