Skip to content

Commit cae2aab

Browse files
committed
🚧(resource-server) Allow documents API via RS
This provides a base configuration to allow to access all "/documents" API via OIDC resource server authentication.
1 parent dd6e0b5 commit cae2aab

File tree

4 files changed

+116
-0
lines changed

4 files changed

+116
-0
lines changed

src/backend/core/api/permissions.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
"""Permission handlers for the impress core app."""
22

3+
from django.conf import settings
34
from django.core import exceptions
45
from django.db.models import Q
56
from django.http import Http404
67

8+
from lasuite.oidc_resource_server.authentication import ResourceServerAuthentication
79
from rest_framework import permissions
810

911
from core.models import DocumentAccess, RoleChoices, get_trashbin_cutoff
@@ -134,3 +136,38 @@ def has_object_permission(self, request, view, obj):
134136
raise Http404
135137

136138
return has_permission
139+
140+
141+
class ResourceServerClientPermission(permissions.BasePermission):
142+
"""
143+
Permission class for resource server views.
144+
145+
This provides a way to open the resource server views to a limited set of
146+
Service Providers.
147+
148+
Note: we might add a more complex permission system in the future, based on
149+
the Service Provider ID and the requested scopes.
150+
"""
151+
152+
def has_permission(self, request, view):
153+
"""
154+
Check if the user is authenticated and the token introspection
155+
provides an authorized Service Provider.
156+
"""
157+
if not isinstance(
158+
request.successful_authenticator, ResourceServerAuthentication
159+
):
160+
# Not a resource server request
161+
return True
162+
163+
# Check if the user is authenticated
164+
if not request.user.is_authenticated:
165+
return False
166+
167+
if view.action not in view.resource_server_actions:
168+
return False
169+
170+
# When used as a resource server, the request has a token audience
171+
return (
172+
request.resource_server_token_audience in settings.OIDC_RS_ALLOWED_AUDIENCES
173+
)

src/backend/core/api/viewsets.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import rest_framework as drf
2626
from botocore.exceptions import ClientError
2727
from lasuite.malware_detection import malware_detection
28+
from lasuite.oidc_resource_server.authentication import ResourceServerAuthentication
2829
from rest_framework import filters, status, viewsets
2930
from rest_framework import response as drf_response
3031
from rest_framework.permissions import AllowAny
@@ -431,6 +432,7 @@ class DocumentViewSet(
431432
pagination_class = Pagination
432433
permission_classes = [
433434
permissions.DocumentAccessPermission,
435+
permissions.ResourceServerClientPermission,
434436
]
435437
queryset = models.Document.objects.all()
436438
serializer_class = serializers.DocumentSerializer
@@ -440,6 +442,22 @@ class DocumentViewSet(
440442
list_serializer_class = serializers.ListDocumentSerializer
441443
trashbin_serializer_class = serializers.ListDocumentSerializer
442444
tree_serializer_class = serializers.ListDocumentSerializer
445+
resource_server_actions = {
446+
"list",
447+
"retrieve",
448+
"create_for_owner",
449+
}
450+
451+
def get_authenticators(self):
452+
"""Allow resource server authentication for very specific actions."""
453+
authenticators = super().get_authenticators()
454+
455+
# self.action does not exist yet
456+
action = self.action_map[self.request.method.lower()]
457+
if action in self.resource_server_actions:
458+
authenticators.append(ResourceServerAuthentication())
459+
460+
return authenticators
443461

444462
def annotate_is_favorite(self, queryset):
445463
"""

src/backend/core/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from django.urls import include, path, re_path
55

66
from lasuite.oidc_login.urls import urlpatterns as oidc_urls
7+
from lasuite.oidc_resource_server.urls import urlpatterns as resource_server_urls
78
from rest_framework.routers import DefaultRouter
89

910
from core.api import viewsets
@@ -44,6 +45,7 @@
4445
[
4546
*router.urls,
4647
*oidc_urls,
48+
*resource_server_urls,
4749
re_path(
4850
r"^documents/(?P<resource_id>[0-9a-z-]*)/",
4951
include(document_related_router.urls),

src/backend/impress/settings.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,65 @@ class Base(Configuration):
586586
default=True, environ_name="ALLOW_LOGOUT_GET_METHOD", environ_prefix=None
587587
)
588588

589+
# OIDC - Docs as a resource server
590+
OIDC_OP_URL = values.Value(
591+
default=None, environ_name="OIDC_OP_URL", environ_prefix=None
592+
)
593+
OIDC_OP_INTROSPECTION_ENDPOINT = values.Value(
594+
environ_name="OIDC_OP_INTROSPECTION_ENDPOINT", environ_prefix=None
595+
)
596+
OIDC_VERIFY_SSL = values.BooleanValue(
597+
default=True, environ_name="OIDC_VERIFY_SSL", environ_prefix=None
598+
)
599+
OIDC_TIMEOUT = values.IntegerValue(
600+
default=3, environ_name="OIDC_TIMEOUT", environ_prefix=None
601+
)
602+
OIDC_PROXY = values.Value(None, environ_name="OIDC_PROXY", environ_prefix=None)
603+
604+
OIDC_RS_BACKEND_CLASS = "lasuite.oidc_resource_server.backend.ResourceServerBackend"
605+
OIDC_RS_AUDIENCE_CLAIM = values.Value( # The claim used to identify the audience
606+
default="client_id", environ_name="OIDC_RS_AUDIENCE_CLAIM", environ_prefix=None
607+
)
608+
OIDC_RS_PRIVATE_KEY_STR = values.Value(
609+
default=None,
610+
environ_name="OIDC_RS_PRIVATE_KEY_STR",
611+
environ_prefix=None,
612+
)
613+
OIDC_RS_ENCRYPTION_KEY_TYPE = values.Value(
614+
default="RSA",
615+
environ_name="OIDC_RS_ENCRYPTION_KEY_TYPE",
616+
environ_prefix=None,
617+
)
618+
OIDC_RS_ENCRYPTION_ALGO = values.Value(
619+
default="RSA-OAEP",
620+
environ_name="OIDC_RS_ENCRYPTION_ALGO",
621+
environ_prefix=None,
622+
)
623+
OIDC_RS_ENCRYPTION_ENCODING = values.Value(
624+
default="A256GCM",
625+
environ_name="OIDC_RS_ENCRYPTION_ENCODING",
626+
environ_prefix=None,
627+
)
628+
OIDC_RS_CLIENT_ID = values.Value(
629+
None, environ_name="OIDC_RS_CLIENT_ID", environ_prefix=None
630+
)
631+
OIDC_RS_CLIENT_SECRET = values.Value(
632+
None,
633+
environ_name="OIDC_RS_CLIENT_SECRET",
634+
environ_prefix=None,
635+
)
636+
OIDC_RS_SIGNING_ALGO = values.Value(
637+
default="ES256", environ_name="OIDC_RS_SIGNING_ALGO", environ_prefix=None
638+
)
639+
OIDC_RS_SCOPES = values.ListValue(
640+
[], environ_name="OIDC_RS_SCOPES", environ_prefix=None
641+
)
642+
OIDC_RS_ALLOWED_AUDIENCES = values.ListValue(
643+
default=[],
644+
environ_name="OIDC_RS_ALLOWED_AUDIENCES",
645+
environ_prefix=None,
646+
)
647+
589648
# AI service
590649
AI_FEATURE_ENABLED = values.BooleanValue(
591650
default=False, environ_name="AI_FEATURE_ENABLED", environ_prefix=None

0 commit comments

Comments
 (0)