diff --git a/apps/permission/backends.py b/apps/permission/backends.py index 07e132f5..6d79827a 100644 --- a/apps/permission/backends.py +++ b/apps/permission/backends.py @@ -21,7 +21,7 @@ class PermissionBackend(ModelBackend): Manage permissions of users """ supports_object_permissions = True - supports_anonymous_user = True + supports_anonymous_user = False supports_inactive_user = False @staticmethod @@ -33,6 +33,7 @@ class PermissionBackend(ModelBackend): :param t: The type of the permissions: view, change, add or delete :return: The queryset of the permissions of the user (memoized) grouped by clubs """ + # Permission for auth if hasattr(request, 'oauth2') and request.oauth2 is not None and 'scope' in request.oauth2: # OAuth2 Authentication user = request.oauth2['user'] @@ -46,6 +47,21 @@ class PermissionBackend(ModelBackend): if int(club_id) == membership_obj.club_id: query |= Q(pk=permission_id, mask__rank__lte=request.oauth2['mask']) return query + + # Restreint token permission to his scope + elif hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'): + user = request.auth.user + + def permission_filter(membership_obj): + query = Q(pk=-1) + for scope in request.auth.scope.split(' '): + if scope == "openid" or scope == "0_0": + continue + permission_id, club_id = scope.split('_') + if int(club_id) == membership_obj.club_id: + query |= Q(pk=permission_id) + return query + else: user = request.user @@ -79,7 +95,6 @@ class PermissionBackend(ModelBackend): :param type: The type of the permissions: view, change, add or delete :return: A generator of the requested permissions """ - if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'): # OAuth2 Authentication user = request.auth.user diff --git a/apps/permission/scopes.py b/apps/permission/scopes.py index cd88f18f..764ecd51 100644 --- a/apps/permission/scopes.py +++ b/apps/permission/scopes.py @@ -54,7 +54,7 @@ class PermissionScopes(BaseScopes): return [] scopes = [f"{p.id}_{p.membership.club.id}" for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')] - scopes = ['0_0'] + scopes = ['0_0'] # always default return scopes @@ -143,36 +143,6 @@ class PermissionOAuth2Validator(OAuth2Validator): request.scopes = valid_scopes return valid_scopes - def validate_code_scopes(self, client_id, scopes, client, request, *args, **kwargs): - """ - For Authorization Code scope are scope of the user - """ - valid_scopes = set() - req = get_current_request() - request.oauth2 = {} - request.oauth2['user'] = req.user - request.oauth2['scope'] = scopes - # mask implementation - request.oauth2['mask'] = req.session.load()['permission_mask'] - - for t in Permission.PERMISSION_TYPES: - for p in PermissionBackend.get_raw_permissions(request, t[0]): - scope = f"{p.id}_{p.membership.club.id}" - if scope in scopes: - valid_scopes.add(scope) - - # Always give one scope to generate token - if not valid_scopes: - valid_scopes.add('0_0') - request.scopes = valid_scopes - return valid_scopes - - def validate_implicit_scopes(self, client_id, scopes, client, request, *args, **kwargs): - """ - Implicit flow it's just degraded Authorization flow - """ - return self.validate_code_scopes(client_id, scopes, client, request, args, kwargs) - def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): """ User can request as many scope as he wants, including invalid scopes, @@ -187,19 +157,28 @@ class PermissionOAuth2Validator(OAuth2Validator): if hasattr(request, 'grant_type') and request.grant_type == 'password': return self.validate_ropb_scopes(client_id, scopes, client, request, args, kwargs) - if hasattr(request, '_params') and request._params['response_type'] == 'code': - return self.validate_code_scopes(client_id, scopes, client, request, args, kwargs) + # Authorization code and Implicit are the same for scope, OIDC it's only a layer - if hasattr(request, '_params') and request._params['response_type'] == 'token': - return self.validate_implicit_scopes(client_id, scopes, client, request, args, kwargs) + valid_scopes = set() + req = get_current_request() + request.oauth2 = {} + request.oauth2['user'] = req.user + request.oauth2['scope'] = scopes + # mask implementation + request.oauth2['mask'] = req.session.load()['permission_mask'] 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(request, t[0]): scope = f"{p.id}_{p.membership.club.id}" if scope in scopes: valid_scopes.add(scope) - if '0_0' in scopes: + # We grant openid scope if user is active + if 'openid' in scopes and req.user.is_active: + valid_scopes.add('openid') + + # Always give one scope to generate token + if not valid_scopes: valid_scopes.add('0_0') request.scopes = valid_scopes diff --git a/apps/permission/tests/test_oauth2_flow.py b/apps/permission/tests/test_oauth2_flow.py index ffd022dd..48c18e2d 100644 --- a/apps/permission/tests/test_oauth2_flow.py +++ b/apps/permission/tests/test_oauth2_flow.py @@ -442,9 +442,3 @@ class OAuth2FlowTestCase(TestCase): # Token can have access, it shouldn't have the useless scope self.assertEqual(token.scope, self.base_scope) - - def test_oidc_flow(self): - """ - Ensure OIDC Flow work - """ - pass diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index aa8b9905..65ad5304 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -273,9 +273,9 @@ OAUTH2_PROVIDER = { 'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14), 'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0) 'OIDC_ENABLED': True, + 'OIDC_RP_INITIATED_LOGOUT_ENABLED': False, 'OIDC_RSA_PRIVATE_KEY': os.getenv('OIDC_RSA_PRIVATE_KEY', 'CHANGE_ME_IN_ENV_SETTINGS').replace('\\n', '\n'), # for multilines - 'SCOPES': { 'openid': "OpenID Connect scope" }, } # Take control on how widget templates are sourced