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