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