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