mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-11-08 15:59:50 +01:00
Add test for oauth2 flow, add temporary ROPB for NoteApp #137
This commit is contained in:
@@ -26,7 +26,7 @@ class PermissionBackend(ModelBackend):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@memoize
|
@memoize
|
||||||
def get_raw_permissions(request, t):
|
def get_raw_permissions(request, t): # noqa: C901
|
||||||
"""
|
"""
|
||||||
Query permissions of a certain type for a user, then memoize it.
|
Query permissions of a certain type for a user, then memoize it.
|
||||||
:param request: The current request
|
:param request: The current request
|
||||||
@@ -42,7 +42,7 @@ class PermissionBackend(ModelBackend):
|
|||||||
if 'mask' in request.GET:
|
if 'mask' in request.GET:
|
||||||
try:
|
try:
|
||||||
rank = int(request.GET['mask'])
|
rank = int(request.GET['mask'])
|
||||||
except:
|
except ValueError:
|
||||||
rank = 42
|
rank = 42
|
||||||
query &= Q(mask__rank__lte=rank)
|
query &= Q(mask__rank__lte=rank)
|
||||||
for scope in request.auth.scope.split(' '):
|
for scope in request.auth.scope.split(' '):
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from .models import Permission
|
|||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class PermissionScopes(BaseScopes):
|
class PermissionScopes(BaseScopes):
|
||||||
"""
|
"""
|
||||||
An OAuth2 scope is defined by a permission object and a club.
|
An OAuth2 scope is defined by a permission object and a club.
|
||||||
@@ -83,8 +84,12 @@ class PermissionOAuth2Validator(OAuth2Validator):
|
|||||||
|
|
||||||
valid_scopes = set()
|
valid_scopes = set()
|
||||||
|
|
||||||
|
# simple patch for have functionnal ROPB flow
|
||||||
|
# TODO rewrite
|
||||||
|
r = get_current_request()
|
||||||
|
r.user = request.user
|
||||||
for t in Permission.PERMISSION_TYPES:
|
for t in Permission.PERMISSION_TYPES:
|
||||||
for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0]):
|
for p in PermissionBackend.get_raw_permissions(r, t[0]):
|
||||||
scope = f"{p.id}_{p.membership.club.id}"
|
scope = f"{p.id}_{p.membership.club.id}"
|
||||||
if scope in scopes:
|
if scope in scopes:
|
||||||
valid_scopes.add(scope)
|
valid_scopes.add(scope)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class OAuth2TestCase(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = User.objects.create(
|
self.user = User.objects.create(
|
||||||
username="toto",
|
username="toto",
|
||||||
|
password="toto1234",
|
||||||
)
|
)
|
||||||
self.application = Application.objects.create(
|
self.application = Application.objects.create(
|
||||||
name="Test",
|
name="Test",
|
||||||
@@ -92,3 +93,39 @@ class OAuth2TestCase(TestCase):
|
|||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
self.assertIn(self.application, resp.context['scopes'])
|
self.assertIn(self.application, resp.context['scopes'])
|
||||||
self.assertIn('1_1', resp.context['scopes'][self.application]) # Now the user has this permission
|
self.assertIn('1_1', resp.context['scopes'][self.application]) # Now the user has this permission
|
||||||
|
|
||||||
|
def test_oidc(self):
|
||||||
|
"""
|
||||||
|
Ensure OIDC work
|
||||||
|
"""
|
||||||
|
# Create access token that has access to our own user detail
|
||||||
|
token = AccessToken.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
application=self.application,
|
||||||
|
scope="openid",
|
||||||
|
token=get_random_string(64),
|
||||||
|
expires=timezone.now() + timedelta(days=365),
|
||||||
|
)
|
||||||
|
|
||||||
|
# No access without token
|
||||||
|
resp = self.client.get('/o/userinfo/') # userinfo endpoint
|
||||||
|
self.assertEqual(resp.status_code, 401)
|
||||||
|
|
||||||
|
# Valid token
|
||||||
|
resp = self.client.get('/o/userinfo/', **{'Authorization': f'Bearer {token.token}'})
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
# Create membership to test api
|
||||||
|
NoteUser.objects.create(user=self.user)
|
||||||
|
membership = Membership.objects.create(user=self.user, club_id=1)
|
||||||
|
membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
|
||||||
|
membership.save()
|
||||||
|
|
||||||
|
# Token can always be use to see yourself
|
||||||
|
resp = self.client.get('/api/me/',
|
||||||
|
**{'Authorization': f'Bearer {token.token}'})
|
||||||
|
|
||||||
|
# Token is not granted to see other api
|
||||||
|
resp = self.client.get(f'/api/user/{self.user.pk}/',
|
||||||
|
**{'Authorization': f'Bearer {token.token}'})
|
||||||
|
self.assertEqual(resp.status_code, 404)
|
||||||
115
apps/permission/tests/test_oauth2_flow.py
Normal file
115
apps/permission/tests/test_oauth2_flow.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
from member.models import Membership, Club
|
||||||
|
from note.models import NoteUser
|
||||||
|
from oauth2_provider.models import Application
|
||||||
|
|
||||||
|
from ..models import Role, Permission
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth2TestCase(TestCase):
|
||||||
|
fixtures = ('initial', )
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create(
|
||||||
|
username="toto",
|
||||||
|
password="toto1234",
|
||||||
|
)
|
||||||
|
|
||||||
|
NoteUser.objects.create(user=self.user)
|
||||||
|
membership = Membership.objects.create(user=self.user, club_id=1)
|
||||||
|
membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE"))
|
||||||
|
membership.save()
|
||||||
|
|
||||||
|
bde = Club.objects.get(name="BDE")
|
||||||
|
view_user_perm = Permission.objects.get(pk=1) # View own user detail
|
||||||
|
|
||||||
|
self.base_scope = f'{view_user_perm.pk}_{bde.pk}'
|
||||||
|
|
||||||
|
def test_oauth2_authorization_code_flow(self):
|
||||||
|
"""
|
||||||
|
Ensure OAuth2 Authorization Code Flow work
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_oauth2_implicit_flow(self):
|
||||||
|
"""
|
||||||
|
Ensure OAuth2 Implicit Flow work
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_oauth2_resource_owner_password_credentials_flow(self):
|
||||||
|
"""
|
||||||
|
Ensure OAuth2 Resource Owner Password Credentials Flow work
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_oauth2_client_credentials(self):
|
||||||
|
"""
|
||||||
|
Ensure OAuth2 Client Credentials work
|
||||||
|
"""
|
||||||
|
app = Application.objects.create(
|
||||||
|
name="Test credentials",
|
||||||
|
client_type=Application.CLIENT_CONFIDENTIAL,
|
||||||
|
authorization_grant_type=Application.GRANT_CLIENT_CREDENTIALS,
|
||||||
|
user=self.user,
|
||||||
|
hash_client_secret=False,
|
||||||
|
algorithm=Application.NO_ALGORITHM,
|
||||||
|
)
|
||||||
|
|
||||||
|
# No token without credential
|
||||||
|
resp = self.client.post('/o/token/',
|
||||||
|
data={"grant_type": "client_credentials"},
|
||||||
|
**{"Content-Type": 'application/x-www-form-urlencoded'}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 401)
|
||||||
|
|
||||||
|
# Access with credential
|
||||||
|
credential = base64.b64encode(f'{app.client_id}:{app.client_secret}'.encode('utf-8')).decode()
|
||||||
|
|
||||||
|
resp = self.client.post('/o/token/',
|
||||||
|
data={"grant_type": "client_credentials"},
|
||||||
|
**{'HTTP_Authorization': f'Basic {credential}',
|
||||||
|
"Content-Type": 'application/x-www-form-urlencoded'}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
token = resp.json()['access_token']
|
||||||
|
|
||||||
|
# Token is valid but has no right
|
||||||
|
resp = self.client.get('/api/user/{self.user.pk}',
|
||||||
|
**{'Authorization': f'Bearer {token}'}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 403)
|
||||||
|
|
||||||
|
# RFC6749 4.4.2 allows use of scope in client credential flow
|
||||||
|
resp = self.client.post('/o/token/',
|
||||||
|
data={"grant_type": "client_credentials",
|
||||||
|
"scope": self.base_scope},
|
||||||
|
**{'http_Authorization': f'Basic {credential}',
|
||||||
|
"Content-Type": 'application/x-www-form-urlencoded'}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
token = resp.json()['access_token']
|
||||||
|
|
||||||
|
# Now app can see his creator
|
||||||
|
resp = self.client.post(f'/api/user/{self.user.pk}/',
|
||||||
|
**{'Authorization': f'Bearer {token}'})
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
def test_oidc_flow(self):
|
||||||
|
"""
|
||||||
|
Ensure OIDC Flow work
|
||||||
|
"""
|
||||||
|
pass
|
||||||
Reference in New Issue
Block a user