diff --git a/CHANGELOG.md b/CHANGELOG.md index ae1fa2ed2..a43ef24fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to ## [Unreleased] +### Fixed + +- 📝(doc) drop the removed main workspace from the resource server example + ## [v0.20.0] - 2026-07-15 ### Added diff --git a/Makefile b/Makefile index a7bce9794..bc9a3b846 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ create-env-local-files: @touch env.d/development/kc_postgresql.local .PHONY: create-env-local-files -create-docker-network: ## create the docker network if it doesn't exist +create-docker-network: ## create the shared lasuite-network if it doesn't exist @docker network create lasuite-network || true .PHONY: create-docker-network diff --git a/compose.yaml b/compose.yaml index c4007e414..186114880 100644 --- a/compose.yaml +++ b/compose.yaml @@ -19,12 +19,7 @@ services: redis: image: redis:5 ports: - - "6379:6379" - - mailcatcher: - image: sj26/mailcatcher:latest - ports: - - "1081:1080" + - "6479:6379" minio: user: ${DOCKER_USER:-1000} @@ -33,8 +28,8 @@ services: - MINIO_ROOT_USER=drive - MINIO_ROOT_PASSWORD=password ports: - - "9000:9000" - - "9001:9001" + - "9100:9000" + - "9101:9001" healthcheck: test: ["CMD", "mc", "ready", "local"] interval: 1s @@ -44,6 +39,13 @@ services: command: minio server --console-address :9001 /data volumes: - ./data/media:/data + networks: + default: {} + # Reachable from the meet stack, which uploads recordings to the presigned + # URLs Drive hands out. + lasuite: + aliases: + - drive-minio createbuckets: image: minio/mc @@ -75,20 +77,23 @@ services: - env.d/development/postgresql - env.d/development/postgresql.${ENV_OVERRIDE:-local} ports: - - "8071:8000" + - "8171:8000" volumes: - ./src/backend:/app - ./data/static:/data/static - /app/.venv networks: - - lasuite - - default + default: {} + # Not "app-dev": the meet stack has a backend of its own, and its + # containers use on this network too. This is the external API endpoint + # meet pushes recordings to. + lasuite: + aliases: + - drive-app-dev depends_on: postgresql: condition: service_healthy restart: true - mailcatcher: - condition: service_started redis: condition: service_started createbuckets: @@ -96,9 +101,9 @@ services: celery-dev: condition: service_started collabora: - condition: service_healthy + condition: service_started onlyoffice: - condition: service_healthy + condition: service_started ds-proxy: image: demarchenumerique/ds-proxy:v2.0.0-alpha.4 @@ -154,16 +159,17 @@ services: nginx: image: nginx:1.25 ports: - - "8083:8083" + - "8085:8083" networks: default: {} lasuite: + # Not "nginx": the meet stack has an nginx of its own, and its backend + # containers use this network too. aliases: - - nginx + - drive-nginx volumes: - ./docker/files/development/etc/nginx/conf.d:/etc/nginx/conf.d:ro depends_on: - - keycloak - app-dev frontend-dev: @@ -173,14 +179,14 @@ services: dockerfile: ./src/frontend/Dockerfile target: drive-dev args: - API_ORIGIN: "http://localhost:8071" + API_ORIGIN: "http://localhost:8171" image: drive:frontend-development volumes: - ./src/frontend/:/home/frontend/ - /home/frontend/node_modules - /home/frontend/apps/drive/node_modules ports: - - "3000:3000" + - "3100:3000" crowdin: image: crowdin/cli:3.16.0 @@ -200,54 +206,13 @@ services: volumes: - ".:/app" - kc_postgresql: - image: postgres:14.3 - healthcheck: - test: ["CMD-SHELL", "pg_isready"] - interval: 1s - timeout: 2s - retries: 300 - ports: - - "6433:5432" - env_file: - - env.d/development/kc_postgresql - - env.d/development/kc_postgresql.local - - keycloak: - image: quay.io/keycloak/keycloak:26.3.2 - volumes: - - ./docker/auth/realm.json:/opt/keycloak/data/import/realm.json - command: - - start-dev - - --features=preview - - --import-realm - - --proxy=edge - - --hostname=http://localhost:8083 - - --hostname-strict=false - environment: - KEYCLOAK_ADMIN: admin - KEYCLOAK_ADMIN_PASSWORD: admin - KC_DB: postgres - KC_DB_URL_HOST: kc_postgresql - KC_DB_URL_DATABASE: keycloak - KC_DB_PASSWORD: pass - KC_DB_USERNAME: drive - KC_DB_SCHEMA: public - PROXY_ADDRESS_FORWARDING: "true" - ports: - - "8080:8080" - depends_on: - - kc_postgresql - networks: - - lasuite - - default - collabora: image: collabora/code:latest healthcheck: test: ["CMD-SHELL", "bash -c 'echo -e \"GET /hosting/discovery HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n\" > /dev/tcp/localhost/9980'"] interval: 30s retries: 5 + start_interval: 2s start_period: 60s timeout: 10s ports: diff --git a/docs/resource_server.md b/docs/resource_server.md index 905169936..477bd5bca 100644 --- a/docs/resource_server.md +++ b/docs/resource_server.md @@ -30,7 +30,7 @@ Default configuration: EXTERNAL_API = { "items": { "enabled": True, - "actions": ["list", "retrieve", "children", "upload_ended"], + "actions": ["create", "list", "retrieve", "children", "upload_ended"], }, "item_access": { "enabled": False, @@ -63,38 +63,21 @@ Then you can requests some routes that are available at `/external_api/v1.0/*`, #### Upload a file -Here is an example of a view that create a file on the main workspace in Drive. +Here is an example of a view that creates a file at the root of the user's Drive. ```python @method_decorator(refresh_oidc_access_token) def upload_file(self, request): """ - Create a new file in the main workspace. + Create a new file at the root of the user's Drive. """ # Get the access token from the session access_token = request.session.get('oidc_access_token') - # Get the main workspace - response = requests.get( - f"{settings.DRIVE_API}/items/", - headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}, - ) - response.raise_for_status() - data = response.json() - items = data['results'] - main_workspace = None - for item in items: - if item['main_workspace']: - main_workspace = item - break - - if not main_workspace: - return drf.response.Response(status=404, data={"error": "No main workspace found"}) - - # Create a new file in the main workspace + # Create a new file at the root response = requests.post( - f"{settings.DRIVE_API}/items/{main_workspace['id']}/children/", + f"{settings.DRIVE_API}/items/", json={ "type": "file", "filename": "test.txt", @@ -127,16 +110,17 @@ Here is an example of a view that create a file on the main workspace in Drive. ) response.raise_for_status() - return drf.response.Response(data) + return drf.response.Response(item) ``` + #### Create a new folder -Using the same logic as the previous example, you can create a folder in the main workspace. +Using the same logic as the previous example, you can create a folder at the root. ```python response = requests.post( - f"{settings.DRIVE_API}/items/{main_workspace['id']}/children/", + f"{settings.DRIVE_API}/items/", json={ "type": "folder", "title": "My folder", @@ -145,6 +129,19 @@ response = requests.post( ) ``` +To create an item inside an existing folder, post to its `children/` route. + +```python +response = requests.post( + f"{settings.DRIVE_API}/items/{parent_id}/children/", + json={ + "type": "folder", + "title": "My subfolder", + }, + headers={"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}, +) +``` + #### Get user information The same way, you can use the /me endpoint to get user information. diff --git a/docs/setup-local-network.md b/docs/setup-local-network.md index 109c11e2c..b66e73b15 100644 --- a/docs/setup-local-network.md +++ b/docs/setup-local-network.md @@ -70,7 +70,7 @@ In `src/frontend/apps/drive/.env.development`, override the environment variable ```env NEXT_PUBLIC_S3_DOMAIN_REPLACE=http://:9000 -NEXT_PUBLIC_API_ORIGIN=http://:8071 +NEXT_PUBLIC_API_ORIGIN=http://:8171 ``` ## 5. Update the backend OIDC settings diff --git a/env.d/development/common b/env.d/development/common index 04a9ec2e5..38e83ff19 100644 --- a/env.d/development/common +++ b/env.d/development/common @@ -2,8 +2,18 @@ DJANGO_ALLOWED_HOSTS=* DJANGO_SECRET_KEY=ThisIsAnExampleKeyForDevPurposeOnly DJANGO_SETTINGS_MODULE=drive.settings +DJANGO_CONFIGURATION=Development DJANGO_SUPERUSER_PASSWORD=admin +# CSRF +# The Development config hardcodes upstream's dev ports (3000 for the frontend, +# 8072 for the backend) and only appends what this variable provides. Here the +# frontend is published on 3100 because 3000 belongs to the meet stack, so every +# unsafe method (PATCH/POST/DELETE) is cross-origin and Django rejects it with +# "Origin checking failed" unless 3100 is listed. Read under the default DJANGO_ +# prefix, comma-separated. +DJANGO_CSRF_TRUSTED_ORIGINS=http://localhost:3100 + # Logging # Set to DEBUG level for dev only LOGGING_LEVEL_HANDLERS_CONSOLE=INFO @@ -16,54 +26,65 @@ PYTHONPATH=/app # Drive settings # Mail +# Shared mailcatcher owned by the meet stack, reachable over lasuite-network. +# These settings are declared without `environ_prefix=None` in settings.py, so +# django-configurations reads them under the default DJANGO_ prefix. DJANGO_EMAIL_BRAND_NAME="La Suite Numérique" DJANGO_EMAIL_HOST="mailcatcher" -DJANGO_EMAIL_LOGO_IMG="http://localhost:3000/assets/logo-suite-numerique.png" +DJANGO_EMAIL_LOGO_IMG="http://localhost:3100/assets/logo-suite-numerique.png" DJANGO_EMAIL_PORT=1025 -DJANGO_EMAIL_URL_APP="http://localhost:3000" +DJANGO_EMAIL_URL_APP="http://localhost:3100" # User reconciliation -USER_RECONCILIATION_FORM_URL="http://localhost:3000" +USER_RECONCILIATION_FORM_URL="http://localhost:3100" # Media +# Drive's MinIO is published on 9100/9101 on the host to avoid the meet stack. +# MEDIA_BASE_URL is browser-facing and must hit drive's own nginx, published on +# 8085 (it listens on 8083 inside the container). STORAGES_STATICFILES_BACKEND=django.contrib.staticfiles.storage.StaticFilesStorage AWS_S3_ACCESS_KEY_ID=drive AWS_S3_SECRET_ACCESS_KEY=password -AWS_S3_REGION_NAME=eu-east-1 +AWS_STORAGE_BUCKET_NAME=drive-media-storage +AWS_S3_REGION_NAME=us-east-1 AWS_S3_SIGNATURE_VERSION=s3v4 -AWS_S3_DOMAIN_REPLACE=http://localhost:9000 +AWS_S3_DOMAIN_REPLACE=http://localhost:9100 AWS_S3_ENDPOINT_URL=http://minio:9000 # To use with ds-proxy # AWS_S3_DOMAIN_REPLACE=http://localhost:4444/upstream # AWS_S3_ENDPOINT_URL=http://ds-proxy:4444/upstream -MEDIA_BASE_URL=http://localhost:8083 - -# OIDC -OIDC_OP_JWKS_ENDPOINT=http://nginx:8083/realms/drive/protocol/openid-connect/certs -OIDC_OP_AUTHORIZATION_ENDPOINT=http://localhost:8083/realms/drive/protocol/openid-connect/auth -OIDC_OP_TOKEN_ENDPOINT=http://nginx:8083/realms/drive/protocol/openid-connect/token -OIDC_OP_USER_ENDPOINT=http://nginx:8083/realms/drive/protocol/openid-connect/userinfo +MEDIA_BASE_URL=http://localhost:8085 + +# OIDC — shared Keycloak (single instance owned by the meet stack, realm "meet") +# Browser-facing endpoints go through meet's nginx on localhost:8083. +# Back-channel endpoints reach the shared Keycloak directly over lasuite-network. +OIDC_OP_URL=http://localhost:8083/realms/meet +OIDC_OP_AUTHORIZATION_ENDPOINT=http://localhost:8083/realms/meet/protocol/openid-connect/auth +OIDC_OP_JWKS_ENDPOINT=http://keycloak:8080/realms/meet/protocol/openid-connect/certs +OIDC_OP_TOKEN_ENDPOINT=http://keycloak:8080/realms/meet/protocol/openid-connect/token +OIDC_OP_USER_ENDPOINT=http://keycloak:8080/realms/meet/protocol/openid-connect/userinfo +OIDC_OP_INTROSPECTION_ENDPOINT=http://keycloak:8080/realms/meet/protocol/openid-connect/token/introspect OIDC_RP_CLIENT_ID=drive OIDC_RP_CLIENT_SECRET=ThisIsAnExampleKeyForDevPurposeOnly OIDC_RP_SIGN_ALGO=RS256 OIDC_RP_SCOPES="openid email" -LOGIN_REDIRECT_URL=http://localhost:3000 -LOGIN_REDIRECT_URL_FAILURE=http://localhost:3000 -LOGOUT_REDIRECT_URL=http://localhost:3000 +LOGIN_REDIRECT_URL=http://localhost:3100 +LOGIN_REDIRECT_URL_FAILURE=http://localhost:3100 +LOGOUT_REDIRECT_URL=http://localhost:3100 -OIDC_REDIRECT_ALLOWED_HOSTS="localhost:8083,localhost:3000" -OIDC_AUTH_REQUEST_EXTRA_PARAMS={"acr_values": "eidas1"} +OIDC_REDIRECT_ALLOWED_HOSTS=localhost:8085,localhost:3100 -# Resource Server Backend -OIDC_OP_URL=http://localhost:8083/realms/drive -OIDC_OP_INTROSPECTION_ENDPOINT = http://nginx:8083/realms/drive/protocol/openid-connect/token/introspect -OIDC_RESOURCE_SERVER_ENABLED=False +# Resource server: lets other La Suite applications (meet) call +# /external_api/v1.0/* with an access token belonging to one of their users. +# The audience is the introspected `client_id`, i.e. the client the token was +# issued to — "meet" here. +OIDC_RESOURCE_SERVER_ENABLED=True OIDC_RS_CLIENT_ID=drive OIDC_RS_CLIENT_SECRET=ThisIsAnExampleKeyForDevPurposeOnly -OIDC_RS_AUDIENCE_CLAIM="client_id" # The claim used to identify the audience -OIDC_RS_ALLOWED_AUDIENCES="" +OIDC_RS_AUDIENCE_CLAIM=client_id +OIDC_RS_ALLOWED_AUDIENCES=meet # Frontend FRONTEND_THEME=dsfr-light @@ -111,3 +132,4 @@ MALWARE_DETECTION_DUMMY_SLEEP=10 # Must be a valid Fernet key (32 url-safe base64-encoded bytes) # To create one, use the bin/fernetkey command. # OIDC_STORE_REFRESH_TOKEN_KEY="your-32-byte-encryption-key==" + diff --git a/env.d/development/postgresql b/env.d/development/postgresql index 5fb03305c..ec4454b98 100644 --- a/env.d/development/postgresql +++ b/env.d/development/postgresql @@ -9,3 +9,4 @@ DB_NAME=drive DB_USER=dinum DB_PASSWORD=pass DB_PORT=5432 + diff --git a/src/backend/drive/settings.py b/src/backend/drive/settings.py index 124cfb5f6..77649d48c 100755 --- a/src/backend/drive/settings.py +++ b/src/backend/drive/settings.py @@ -431,6 +431,7 @@ class Base(Configuration): ".odt", ".oform", ".oft", + ".ogg", ".one", ".opj", ".opju", @@ -581,6 +582,7 @@ class Base(Configuration): "application/gzip", "application/json", "application/msword", + "application/ogg", "application/pdf", "application/pgp-signature", "application/pkcs10", @@ -685,6 +687,7 @@ class Base(Configuration): "audio/basic", "audio/mp4", "audio/mpeg", + "audio/ogg", "audio/wav", "audio/webm", "audio/x-aac", @@ -726,6 +729,7 @@ class Base(Configuration): "video/mp2t", "video/mp4", "video/mpeg", + "video/ogg", "video/quicktime", "video/webm", "video/x-m4v", @@ -1145,7 +1149,6 @@ class Base(Configuration): environ_name="OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION", environ_prefix=None, ) - OIDC_STORE_ACCESS_TOKEN = values.BooleanValue( default=False, environ_name="OIDC_STORE_ACCESS_TOKEN", environ_prefix=None ) diff --git a/src/frontend/apps/drive/.env.development b/src/frontend/apps/drive/.env.development index a78d72b38..4d024667c 100644 --- a/src/frontend/apps/drive/.env.development +++ b/src/frontend/apps/drive/.env.development @@ -1,3 +1,3 @@ NEXT_PUBLIC_S3_DOMAIN_REPLACE=http://localhost:9000 -NEXT_PUBLIC_API_ORIGIN=http://localhost:8071 +NEXT_PUBLIC_API_ORIGIN=http://localhost:8171 NEXT_PUBLIC_POSTHOG_TEST_MODE=true