(backend) add ServiceProvider

This adds the ServiceProvider notion to allow to better
manage which teams is available for each service provider.
This commit is contained in:
Quentin BEY
2024-11-04 11:32:41 +01:00
committed by BEY Quentin
parent 512d9fe82c
commit a041296f8a
27 changed files with 1392 additions and 10 deletions

View File

@@ -4,6 +4,7 @@ from rest_framework import exceptions, serializers
from timezone_field.rest_framework import TimeZoneSerializerField
from core import models
from core.models import ServiceProvider
class ContactSerializer(serializers.ModelSerializer):
@@ -205,6 +206,9 @@ class TeamSerializer(serializers.ModelSerializer):
"""Serialize teams."""
abilities = serializers.SerializerMethodField(read_only=True)
service_providers = serializers.PrimaryKeyRelatedField(
queryset=ServiceProvider.objects.all(), many=True, required=False
)
class Meta:
model = models.Team
@@ -215,6 +219,7 @@ class TeamSerializer(serializers.ModelSerializer):
"abilities",
"created_at",
"updated_at",
"service_providers",
]
read_only_fields = [
"id",
@@ -226,6 +231,13 @@ class TeamSerializer(serializers.ModelSerializer):
def create(self, validated_data):
"""Create a new team with organization enforcement."""
# When called as a resource server, we enforce the team service provider
if sp_audience := self.context.get("from_service_provider_audience", None):
service_provider, _created = models.ServiceProvider.objects.get_or_create(
audience_id=sp_audience
)
validated_data["service_providers"] = [service_provider]
# Note: this is not the purpose of this API to check the user has an organization
return super().create(
validated_data=validated_data

View File

@@ -18,6 +18,7 @@ from rest_framework.permissions import AllowAny
from core import models
from ..resource_server.authentication import ResourceServerAuthentication
from . import permissions, serializers
SIMILARITY_THRESHOLD = 0.04
@@ -247,15 +248,52 @@ class TeamViewSet(
queryset = models.Team.objects.all()
pagination_class = None
def _get_service_provider_audience(self):
"""Return the audience of the Service Provider from the OIDC introspected token."""
if not isinstance(
self.request.successful_authenticator, ResourceServerAuthentication
):
# We could check request.resource_server_token_audience here, but it's
# more explicit to check the authenticator type and assert the attribute
# existence.
return None
# When used as a resource server, the request has a token audience
service_provider_audience = self.request.resource_server_token_audience
if not service_provider_audience: # should not happen
raise exceptions.AuthenticationFailed(
"Resource server token audience not found in request"
)
return service_provider_audience
def get_queryset(self):
"""Custom queryset to get user related teams."""
user_role_query = models.TeamAccess.objects.filter(
user=self.request.user, team=OuterRef("pk")
).values("role")[:1]
return models.Team.objects.filter(accesses__user=self.request.user).annotate(
user_role=Subquery(user_role_query)
return (
models.Team.objects.prefetch_related("accesses", "service_providers")
.filter(
accesses__user=self.request.user,
)
.annotate(user_role=Subquery(user_role_query))
)
def get_serializer_context(self):
"""Extra context provided to the serializer class."""
context = super().get_serializer_context()
# When used as a resource server, we need to know the audience to automatically:
# - add the Service Provider to the team "scope" on creation
context["from_service_provider_audience"] = (
self._get_service_provider_audience()
)
return context
def perform_create(self, serializer):
"""Set the current user as owner of the newly created team."""
team = serializer.save()