🔒️(backend) restrict resource server views
We don't want every Service Provider to be able to request every endpoint if those are not implementing a filtering on the data returned. To prevent any data leak we enforce the developers to manually "whitelist" each endpoint and add the proper filtering when needed.
This commit is contained in:
@@ -18,7 +18,6 @@ from rest_framework.permissions import AllowAny
|
||||
|
||||
from core import models
|
||||
|
||||
from ..resource_server.authentication import ResourceServerAuthentication
|
||||
from . import permissions, serializers
|
||||
|
||||
SIMILARITY_THRESHOLD = 0.04
|
||||
@@ -248,26 +247,6 @@ 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(
|
||||
@@ -282,18 +261,6 @@ class TeamViewSet(
|
||||
.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()
|
||||
|
||||
53
src/backend/core/resource_server/mixins.py
Normal file
53
src/backend/core/resource_server/mixins.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
Mixins for resource server views.
|
||||
"""
|
||||
|
||||
from rest_framework import exceptions as drf_exceptions
|
||||
|
||||
from .authentication import ResourceServerAuthentication
|
||||
|
||||
|
||||
class ResourceServerMixin:
|
||||
"""
|
||||
Mixin for resource server views:
|
||||
- Restrict the authentication class to ResourceServerAuthentication.
|
||||
- Adds the Service Provider ID to the serializer context.
|
||||
- Fetch the Service Provider ID from the OIDC introspected token stored
|
||||
in the request.
|
||||
|
||||
This Mixin *must* be used in every view that should act as a resource server.
|
||||
"""
|
||||
|
||||
authentication_classes = [ResourceServerAuthentication]
|
||||
|
||||
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 _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 drf_exceptions.AuthenticationFailed(
|
||||
"Resource server token audience not found in request"
|
||||
)
|
||||
|
||||
return service_provider_audience
|
||||
30
src/backend/people/resource_server_urls.py
Normal file
30
src/backend/people/resource_server_urls.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Resource server API URL Configuration"""
|
||||
|
||||
from django.urls import include, path
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from core.resource_server.urls import urlpatterns as resource_server_urls
|
||||
from core.resource_server_api import viewsets
|
||||
|
||||
# - Main endpoints
|
||||
# Contacts will be added later
|
||||
# Users will be added later
|
||||
router = DefaultRouter()
|
||||
router.register("teams", viewsets.TeamViewSet, basename="teams")
|
||||
|
||||
|
||||
# - Routes nested under a team
|
||||
# Invitations will be added later
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"resource-server/v1.0/",
|
||||
include(
|
||||
[
|
||||
*router.urls,
|
||||
*resource_server_urls,
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -230,7 +230,10 @@ class Base(Configuration):
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"core.resource_server.authentication.ResourceServerAuthentication",
|
||||
# "core.resource_server.authentication.ResourceServerAuthentication",
|
||||
# The resource server authentication is added on a per-view basis
|
||||
# to enforce the filtering adapted from the introspected token.
|
||||
# See ResourceServerMixin usage for more details.
|
||||
"mozilla_django_oidc.contrib.drf.OIDCAuthentication",
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
),
|
||||
|
||||
@@ -14,13 +14,17 @@ from drf_spectacular.views import (
|
||||
|
||||
from debug import urls as debug_urls
|
||||
|
||||
from . import api_urls
|
||||
from . import api_urls, resource_server_urls
|
||||
|
||||
API_VERSION = settings.API_VERSION
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
] + api_urls.urlpatterns
|
||||
urlpatterns = (
|
||||
[
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
||||
+ api_urls.urlpatterns
|
||||
+ resource_server_urls.urlpatterns
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns = (
|
||||
|
||||
Reference in New Issue
Block a user