mirror of
https://github.com/pretix/pretix.git
synced 2026-08-07 10:17:49 +00:00
[SECURITY] Enforce session validation on oauth authorize endpoint
This commit is contained in:
@@ -34,6 +34,7 @@ from oauth2_provider.views import (
|
||||
|
||||
from pretix.api.models import OAuthApplication
|
||||
from pretix.base.models import Organizer
|
||||
from pretix.control.views.user import RecentAuthenticationRequiredMixin
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,7 +55,7 @@ class OAuthAllowForm(AllowForm):
|
||||
del self.fields['organizers']
|
||||
|
||||
|
||||
class AuthorizationView(BaseAuthorizationView):
|
||||
class AuthorizationView(RecentAuthenticationRequiredMixin, BaseAuthorizationView):
|
||||
template_name = "pretixcontrol/auth/oauth_authorization.html"
|
||||
form_class = OAuthAllowForm
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ class PermissionMiddleware:
|
||||
url = resolve(request.path_info)
|
||||
url_name = url.url_name
|
||||
|
||||
if not request.path.startswith(get_script_prefix() + 'control'):
|
||||
if not request.path.startswith(get_script_prefix() + 'control') and not (url.namespace.startswith("api-") and url_name == "authorize"):
|
||||
# This middleware should only touch the /control subpath
|
||||
return self.get_response(request)
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from pretix.base.models import Organizer
|
||||
@@ -48,6 +50,19 @@ def test_session_auth_with_teams(client, user, team):
|
||||
assert len(resp.data['results']) == 1
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_session_auth_relative_timeout(client, user, team):
|
||||
client.login(email=user.email, password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_long_session'] = False
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 6
|
||||
session['pretix_auth_last_used'] = int(time.time()) - 3600 * 3 - 60
|
||||
session.save()
|
||||
|
||||
resp = client.get('/api/v1/organizers/')
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_token_invalid(client):
|
||||
client.credentials(HTTP_AUTHORIZATION='Token ABCDE')
|
||||
|
||||
+150
-2
@@ -1,5 +1,4 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
# # This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
@@ -34,6 +33,7 @@
|
||||
|
||||
import base64
|
||||
import json
|
||||
import time
|
||||
from urllib.parse import quote
|
||||
|
||||
import pytest
|
||||
@@ -81,9 +81,81 @@ def test_authorize_require_login(client, application: OAuthApplication):
|
||||
assert resp['Location'].startswith('/control/login')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_require_login_after_absolute_timeout(client, admin_user, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_long_session'] = False
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 12 - 60
|
||||
session.save()
|
||||
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s' % (
|
||||
application.client_id, quote('https://example.org')
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
assert resp['Location'].startswith('/control/login')
|
||||
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_require_recent_auth(client, admin_user, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_long_session'] = True
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 - 60
|
||||
session['pretix_auth_last_used'] = int(time.time())
|
||||
session.save()
|
||||
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s' % (
|
||||
application.client_id, quote('https://example.org')
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
assert resp['Location'].startswith('/control/reauth')
|
||||
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_require_login_after_relative_timeout(client, admin_user, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_long_session'] = False
|
||||
session['pretix_auth_login_time'] = int(time.time()) - 3600 * 3 - 60
|
||||
session.save()
|
||||
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s' % (
|
||||
application.client_id, quote('https://example.org')
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
assert resp['Location'].startswith('/control/reauth')
|
||||
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_invalid_redirect_uri(client, admin_user, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s' % (
|
||||
application.client_id, quote('https://example.org')
|
||||
))
|
||||
@@ -93,6 +165,9 @@ def test_authorize_invalid_redirect_uri(client, admin_user, application: OAuthAp
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_missing_response_type(client, admin_user, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -103,9 +178,13 @@ def test_authorize_missing_response_type(client, admin_user, application: OAuthA
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_require_organizer(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
print(resp.headers)
|
||||
assert resp.status_code == 200
|
||||
resp = client.post('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
@@ -122,6 +201,9 @@ def test_authorize_require_organizer(client, admin_user, organizer, application:
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_denied(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -140,6 +222,9 @@ def test_authorize_denied(client, admin_user, organizer, application: OAuthAppli
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_disallow_response_token(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=token' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -150,6 +235,9 @@ def test_authorize_disallow_response_token(client, admin_user, organizer, applic
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_read_scope(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -174,6 +262,9 @@ def test_authorize_read_scope(client, admin_user, organizer, application: OAuthA
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_state(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&state=asdadf' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -194,6 +285,9 @@ def test_authorize_state(client, admin_user, organizer, application: OAuthApplic
|
||||
@pytest.mark.django_db
|
||||
def test_authorize_default_scope(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -219,6 +313,9 @@ def test_authorize_default_scope(client, admin_user, organizer, application: OAu
|
||||
@pytest.mark.django_db
|
||||
def test_token_from_code_without_auth(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -246,6 +343,9 @@ def test_token_from_code_without_auth(client, admin_user, organizer, application
|
||||
@pytest.mark.django_db
|
||||
def test_token_from_code(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -285,6 +385,9 @@ def test_use_token_for_access_one_organizer(client, admin_user, organizer, appli
|
||||
t2.members.add(admin_user)
|
||||
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -327,6 +430,9 @@ def test_use_token_for_access_two_organizers(client, admin_user, organizer, appl
|
||||
t2.members.add(admin_user)
|
||||
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -368,6 +474,9 @@ def test_use_token_for_access_two_organizers(client, admin_user, organizer, appl
|
||||
@pytest.mark.django_db
|
||||
def test_token_refresh(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -410,6 +519,9 @@ def test_token_refresh(client, admin_user, organizer, application: OAuthApplicat
|
||||
@pytest.mark.django_db
|
||||
def test_allow_write(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -442,6 +554,9 @@ def test_allow_write(client, admin_user, organizer, application: OAuthApplicatio
|
||||
@pytest.mark.django_db
|
||||
def test_allow_read_only(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -474,6 +589,9 @@ def test_allow_read_only(client, admin_user, organizer, application: OAuthApplic
|
||||
@pytest.mark.django_db
|
||||
def test_token_revoke_refresh_token(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -518,6 +636,9 @@ def test_token_revoke_refresh_token(client, admin_user, organizer, application:
|
||||
@pytest.mark.django_db
|
||||
def test_token_revoke_access_token(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -566,6 +687,9 @@ def test_token_revoke_access_token(client, admin_user, organizer, application: O
|
||||
@pytest.mark.django_db
|
||||
def test_user_revoke(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -595,6 +719,9 @@ def test_user_revoke(client, admin_user, organizer, application: OAuthApplicatio
|
||||
|
||||
at = OAuthAccessToken.objects.get(token=access_token)
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.post('/control/settings/oauth/authorized/{}/revoke'.format(at.pk), data={
|
||||
})
|
||||
assert resp.status_code == 302
|
||||
@@ -613,6 +740,9 @@ def test_user_revoke(client, admin_user, organizer, application: OAuthApplicatio
|
||||
@pytest.mark.django_db
|
||||
def test_allow_profile_only(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&scope=profile' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
@@ -642,3 +772,21 @@ def test_allow_profile_only(client, admin_user, organizer, application: OAuthApp
|
||||
assert resp.status_code == 403
|
||||
resp = client.get('/api/v1/me', HTTP_AUTHORIZATION='Bearer %s' % access_token)
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_reject_other_response_types(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
session = client.session
|
||||
session['pretix_auth_login_time'] = int(time.time())
|
||||
session.save()
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code+id_token' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
assert 'error=unauthorized_client' in resp['Location']
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=id_token' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
assert 'error=unsupported_response_type' in resp['Location']
|
||||
|
||||
@@ -757,7 +757,7 @@ class SessionTimeOutTest(TestCase):
|
||||
# Regression test added after a security problem in 1.9.1
|
||||
# The problem was that, once the relative timeout happened, the user was redirected
|
||||
# to /control/reauth/, but loading /control/reauth/ was already considered to be
|
||||
# "session activitiy". Therefore, after loding /control/reauth/, the session was no longer
|
||||
# "session activity". Therefore, after loding /control/reauth/, the session was no longer
|
||||
# in the timeout state and the user was able to access pages again without re-entering the
|
||||
# password.
|
||||
session = self.client.session
|
||||
|
||||
Reference in New Issue
Block a user