Commit 4e30f805 authored by ynerant's avatar ynerant
Browse files

Merge branch 'optional-scopes' into 'beta'

Implement optional scopes : clients can request scopes, but they are not guaranteed to get them

See merge request !192
parents 546e422e 9048a416
Pipeline #9698 failed with stages
in 10 minutes and 18 seconds
......@@ -7,8 +7,11 @@ from django.contrib.auth.models import User
from django.utils import timezone
from rest_framework import serializers
from member.api.serializers import ProfileSerializer, MembershipSerializer
from member.models import Membership
from note.api.serializers import NoteSerializer
from note.models import Alias
from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend
class UserSerializer(serializers.ModelSerializer):
......@@ -45,18 +48,30 @@ class OAuthSerializer(serializers.ModelSerializer):
"""
normalized_name = serializers.SerializerMethodField()
profile = ProfileSerializer()
profile = serializers.SerializerMethodField()
note = NoteSerializer()
note = serializers.SerializerMethodField()
memberships = serializers.SerializerMethodField()
def get_normalized_name(self, obj):
return Alias.normalize(obj.username)
def get_profile(self, obj):
# Display the profile of the user only if we have rights to see it.
return ProfileSerializer().to_representation(obj.profile) \
if PermissionBackend.has_perm(get_current_request(), obj.profile, 'view') else None
def get_note(self, obj):
# Display the note of the user only if we have rights to see it.
return NoteSerializer().to_representation(obj.note) \
if PermissionBackend.has_perm(get_current_request(), obj.note, 'view') else None
def get_memberships(self, obj):
# Display only memberships that we are allowed to see.
return serializers.ListSerializer(child=MembershipSerializer()).to_representation(
obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now()))
obj.memberships.filter(date_start__lte=timezone.now(), date_end__gte=timezone.now())
.filter(PermissionBackend.filter_queryset(get_current_request(), Membership, 'view')))
class Meta:
model = User
......
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from oauth2_provider.oauth2_validators import OAuth2Validator
from oauth2_provider.scopes import BaseScopes
from member.models import Club
from note_kfet.middlewares import get_current_request
......@@ -32,3 +32,26 @@ class PermissionScopes(BaseScopes):
return []
return [f"{p.id}_{p.membership.club.id}"
for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
class PermissionOAuth2Validator(OAuth2Validator):
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
"""
User can request as many scope as he wants, including invalid scopes,
but it will have only the permissions he has.
This allows clients to request more permission to get finally a
subset of permissions.
"""
valid_scopes = set()
for t in Permission.PERMISSION_TYPES:
for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0]):
scope = f"{p.id}_{p.membership.club.id}"
if scope in scopes:
valid_scopes.add(scope)
request.scopes = valid_scopes
return valid_scopes
......@@ -41,8 +41,14 @@ On a ensuite besoin de définir nos propres scopes afin d'avoir des permissions
OAUTH2_PROVIDER = {
'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes',
'OAUTH2_VALIDATOR_CLASS': "permission.scopes.PermissionOAuth2Validator",
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
}
Cela a pour effet d'avoir des scopes sous la forme ``PERMISSION_CLUB``,
et de demander des scopes facultatives (voir plus bas).
Un jeton de rafraîchissement expire de plus au bout de 14 jours, si non-renouvelé.
On ajoute enfin les routes dans ``urls.py`` :
.. code:: python
......@@ -94,6 +100,27 @@ du format renvoyé.
Vous pouvez donc contrôler le plus finement possible les permissions octroyées à vos
jetons.
.. danger::
Demander des scopes n'implique pas de les avoir.
Lorsque des scopes sont demandées par un client, la Note
va considérer l'ensemble des permissions accessibles parmi
ce qui est demandé. Dans vos programmes, vous devrez donc
vérifier les permissions acquises (communiquées lors de la
récupération du jeton d'accès à partir du grant code),
et prévoir un comportement dans le cas où des permissions
sont manquantes.
Cela offre un intérêt supérieur par rapport au protocole
OAuth2 classique, consistant à demander trop de permissions
et agir en conséquence.
Par exemple, vous pourriez demander la permission d'accéder
aux membres d'un club ou de faire des transactions, et agir
uniquement dans le cas où l'utilisateur connecté possède la
permission problématique.
Avec Django-allauth
###################
......@@ -116,6 +143,7 @@ installées (sur votre propre client), puis de bien ajouter l'application social
SOCIALACCOUNT_PROVIDERS = {
'notekfet': {
# 'DOMAIN': 'note.crans.org',
'SCOPE': ['1_1', '2_1'],
},
...
}
......@@ -123,6 +151,10 @@ installées (sur votre propre client), puis de bien ajouter l'application social
Le paramètre ``DOMAIN`` permet de changer d'instance de Note Kfet. Par défaut, il
se connectera à ``note.crans.org`` si vous ne renseignez rien.
Le paramètre ``SCOPE`` permet de définir les scopes à demander.
Dans l'exemple ci-dessous, les permissions d'accéder à l'utilisateur
et au profil sont demandées.
En créant l'application sur la note, vous pouvez renseigner
``https://monsite.example.com/accounts/notekfet/login/callback/`` en URL de redirection,
à adapter selon votre configuration.
......
......@@ -7,6 +7,8 @@
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
from datetime import timedelta
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Quick-start development settings - unsuitable for production
......@@ -248,6 +250,8 @@ REST_FRAMEWORK = {
# OAuth2 Provider
OAUTH2_PROVIDER = {
'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes',
'OAUTH2_VALIDATOR_CLASS': "permission.scopes.PermissionOAuth2Validator",
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
}
# Take control on how widget templates are sourced
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment