From 65b194d432e096e27001b2cf51082bc40119c574 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Mon, 23 Feb 2026 18:39:51 +0100 Subject: [PATCH] wip introduce add-in endpoints and views --- src/backend/core/addons/__init__.py | 1 + src/backend/core/addons/service.py | 124 ++++++++++++++++++ src/backend/core/addons/views.py | 57 ++++++++ src/backend/core/addons/viewsets.py | 47 +++++++ .../core/external_api/authentication.py | 13 ++ src/backend/core/external_api/viewsets.py | 1 + src/backend/core/templates/addons/error.html | 17 +++ .../templates/addons/redirect_success.html | 17 +++ src/backend/core/urls.py | 28 ++++ .../locale/de_DE/LC_MESSAGES/django.mo | Bin 10376 -> 11311 bytes .../locale/de_DE/LC_MESSAGES/django.po | 52 +++++++- .../locale/en_US/LC_MESSAGES/django.mo | Bin 9427 -> 10281 bytes .../locale/en_US/LC_MESSAGES/django.po | 52 +++++++- .../locale/fr_FR/LC_MESSAGES/django.mo | Bin 10544 -> 11453 bytes .../locale/fr_FR/LC_MESSAGES/django.po | 52 +++++++- .../locale/nl_NL/LC_MESSAGES/django.mo | Bin 9866 -> 10730 bytes .../locale/nl_NL/LC_MESSAGES/django.po | 52 +++++++- src/backend/meet/settings.py | 60 +++++++++ .../dev-keycloak/values.meet.yaml.gotmpl | 1 + src/helm/meet/templates/ingress.yaml | 28 ++++ 20 files changed, 594 insertions(+), 8 deletions(-) create mode 100644 src/backend/core/addons/__init__.py create mode 100644 src/backend/core/addons/service.py create mode 100644 src/backend/core/addons/views.py create mode 100644 src/backend/core/addons/viewsets.py create mode 100644 src/backend/core/templates/addons/error.html create mode 100644 src/backend/core/templates/addons/redirect_success.html diff --git a/src/backend/core/addons/__init__.py b/src/backend/core/addons/__init__.py new file mode 100644 index 00000000..f784ffb1 --- /dev/null +++ b/src/backend/core/addons/__init__.py @@ -0,0 +1 @@ +"""Meet core add-ons module.""" diff --git a/src/backend/core/addons/service.py b/src/backend/core/addons/service.py new file mode 100644 index 00000000..a3008888 --- /dev/null +++ b/src/backend/core/addons/service.py @@ -0,0 +1,124 @@ +"""Authentication session management for add-ons using temporary cache-based sessions.""" + +import secrets +from datetime import datetime, timedelta, timezone +from enum import Enum +from logging import getLogger + +from django.conf import settings +from django.core.cache import cache +from django.core.exceptions import SuspiciousOperation + +from core.models import User +from core.services.jwt_token import JwtTokenService + +logger = getLogger(__name__) + + +class SessionState(str, Enum): + """Add-on authentication session states.""" + + PENDING = "pending" + AUTHENTICATED = "authenticated" + + +class TokenExchangeService: + """Manage temporary authentication sessions for add-on JWT token exchange.""" + + def __init__(self): + """Initialize the service with the configured token service.""" + + self._token_service = JwtTokenService( + secret_key=settings.ADDONS_JWT_SECRET_KEY, + algorithm=settings.ADDONS_JWT_ALG, + issuer=settings.ADDONS_JWT_ISSUER, + audience=settings.ADDONS_JWT_AUDIENCE, + expiration_seconds=settings.ADDONS_JWT_EXPIRATION_SECONDS, + token_type=settings.ADDONS_JWT_TOKEN_TYPE, + ) + + def _get_cache_key(self, session_id: str) -> str: + """Generate cache key for a session ID.""" + return f"{settings.ADDONS_SESSION_KEY_PREFIX}_{session_id}" + + def init_session(self) -> str: + """Create a new pending authentication session and return its ID.""" + + session_id = secrets.token_urlsafe(settings.ADDONS_SESSION_ID_LENGTH) + expires_at = datetime.now(timezone.utc) + timedelta( + seconds=settings.ADDONS_SESSION_TIMEOUT + ) + + session_data = { + "state": SessionState.PENDING, + "expires_at": expires_at.isoformat(), + } + + cache_key = self._get_cache_key(session_id) + cache.set( + cache_key, + session_data, + timeout=settings.ADDONS_SESSION_TIMEOUT, + ) + + return session_id + + def get_session(self, session_id: str) -> dict: + """Retrieve session data and clear it if authenticated.""" + + cache_key = self._get_cache_key(session_id) + data = cache.get(cache_key) + + if not data: + return {} + + if data.get("state") == SessionState.AUTHENTICATED: + self.clear_session(session_id) + + # Return copy without internal fields + internal_fields = {"expires_at"} + return {k: v for k, v in data.items() if k not in internal_fields} + + def clear_session(self, session_id: str) -> None: + """Remove session data from cache.""" + + cache_key = self._get_cache_key(session_id) + cache.delete(cache_key) + + def set_access_token(self, user: User, session_id: str): + """Generate and store access token for an authenticated user session.""" + + cache_key = self._get_cache_key(session_id) + existing_data = cache.get(cache_key) + + if not existing_data: + raise SuspiciousOperation("Session not found.") + + expires_at = existing_data.get("expires_at", None) + + if not expires_at: + self.clear_session(session_id) + raise SuspiciousOperation("Invalid session data.") + + remaining_seconds = int( + ( + datetime.fromisoformat(expires_at) - datetime.now(timezone.utc) + ).total_seconds() + ) + + if remaining_seconds <= 0: + self.clear_session(session_id) + raise SuspiciousOperation("Session expired.") + + if existing_data.get("state") != SessionState.PENDING: + self.clear_session(session_id) + raise SuspiciousOperation("Access token already set.") + + response = self._token_service.generate_jwt(user, settings.ADDONS_SCOPES) + new_data = { + **existing_data, + **response, + "state": SessionState.AUTHENTICATED, + } + + cache.set(cache_key, new_data, timeout=remaining_seconds) diff --git a/src/backend/core/addons/views.py b/src/backend/core/addons/views.py new file mode 100644 index 00000000..7e594301 --- /dev/null +++ b/src/backend/core/addons/views.py @@ -0,0 +1,57 @@ +"""Add-ons views.""" + +from django.conf import settings +from django.core.exceptions import SuspiciousOperation +from django.shortcuts import redirect, render +from django.utils.translation import gettext_lazy as _ +from django.views.decorators.http import require_http_methods + +from core.addons.service import SessionState, TokenExchangeService + + +def render_error(request, message, status=400): + """Render simple error page.""" + return render(request, "addons/error.html", {"message": message}, status=status) + + +@require_http_methods(["GET"]) +def transit_page(request): + """Initialize authentication flow for add-on session.""" + + session_id = request.GET.get("session_id") + + if not session_id: + return render_error(request, _("Session ID is required."), status=400) + + data = TokenExchangeService().get_session(session_id) + + if not data: + return render_error(request, _("Session not found or expired."), status=404) + + if data.get("state") != SessionState.PENDING: + return render_error(request, _("Invalid session state."), status=400) + + request.session[settings.ADDONS_SESSION_KEY_AUTH] = session_id + + return_to = request.build_absolute_uri("/addons/redirect") + return redirect(f"/api/{settings.API_VERSION}/authenticate/?returnTo={return_to}") + + +@require_http_methods(["GET"]) +def redirect_page(request): + """Complete authentication and close the popup window.""" + + if not request.user.is_authenticated: + return render_error(request, _("Authentication required."), status=401) + + session_id = request.session.pop(settings.ADDONS_SESSION_KEY_AUTH, None) + + if not session_id: + return render_error(request, _("No active session found."), status=404) + + try: + TokenExchangeService().set_access_token(request.user, session_id) + except SuspiciousOperation: + return render_error(request, _("Invalid or expired session."), status=400) + + return render(request, "addons/redirect_success.html") diff --git a/src/backend/core/addons/viewsets.py b/src/backend/core/addons/viewsets.py new file mode 100644 index 00000000..9a04463e --- /dev/null +++ b/src/backend/core/addons/viewsets.py @@ -0,0 +1,47 @@ +"""Add-ons API endpoints""" + +from logging import getLogger + +from rest_framework import ( + response as drf_response, +) +from rest_framework import status as drf_status +from rest_framework import viewsets + +from core.addons.service import TokenExchangeService + +logger = getLogger(__name__) + + +class AuthSessionViewSet(viewsets.ViewSet): + """ViewSet for managing add-on authentication sessions via token exchange.""" + + authentication_classes = [] + permission_classes = [] + throttle_classes = [] + + def create(self, request): + """Create a new pending authentication session.""" + session_id = TokenExchangeService().init_session() + return drf_response.Response( + {"session_id": session_id}, status=drf_status.HTTP_201_CREATED + ) + + def retrieve(self, request, pk=None): + """Retrieve authentication session data by session ID.""" + data = TokenExchangeService().get_session(pk) + + if not data: + return drf_response.Response( + {"detail": "Session not found or expired."}, + status=drf_status.HTTP_404_NOT_FOUND, + ) + + return drf_response.Response(data, status=drf_status.HTTP_200_OK) + + def destroy(self, request, pk=None): + """Delete an authentication session by session ID.""" + TokenExchangeService().clear_session(pk) + return drf_response.Response( + {"status": "ok"}, status=drf_status.HTTP_204_NO_CONTENT + ) diff --git a/src/backend/core/external_api/authentication.py b/src/backend/core/external_api/authentication.py index c16f6573..0309e860 100644 --- a/src/backend/core/external_api/authentication.py +++ b/src/backend/core/external_api/authentication.py @@ -214,6 +214,19 @@ class ApplicationJWTAuthentication(BaseJWTAuthentication): raise exceptions.AuthenticationFailed("Invalid token type.") +class AddonsJWTAuthentication(BaseJWTAuthentication): + """JWT authentication for addons API access. + + Validates JWT tokens issued by addons for authenticating users. + Tokens must include user_id to identify the authenticated user. + """ + + secret_key = settings.ADDONS_JWT_SECRET_KEY + algorithm = settings.ADDONS_JWT_ALG + issuer = settings.ADDONS_JWT_ISSUER + audience = settings.ADDONS_JWT_AUDIENCE + + class ResourceServerBackend(LaSuiteBackend): """OIDC Resource Server backend for user creation and retrieval.""" diff --git a/src/backend/core/external_api/viewsets.py b/src/backend/core/external_api/viewsets.py index 5a0e550c..bba2564f 100644 --- a/src/backend/core/external_api/viewsets.py +++ b/src/backend/core/external_api/viewsets.py @@ -173,6 +173,7 @@ class RoomViewSet( authentication_classes = [ authentication.ApplicationJWTAuthentication, + authentication.AddonsJWTAuthentication, ResourceServerAuthentication, ] permission_classes = [ diff --git a/src/backend/core/templates/addons/error.html b/src/backend/core/templates/addons/error.html new file mode 100644 index 00000000..f749ae43 --- /dev/null +++ b/src/backend/core/templates/addons/error.html @@ -0,0 +1,17 @@ +{% load i18n %} +{% get_current_language as LANGUAGE %} + + + + + {% trans "Error" %} + + +
+

{{ title|default:_("Error") }}

+

{{ message|default:_("Something went wrong.") }}

+ +
+ + + diff --git a/src/backend/core/templates/addons/redirect_success.html b/src/backend/core/templates/addons/redirect_success.html new file mode 100644 index 00000000..769e73cc --- /dev/null +++ b/src/backend/core/templates/addons/redirect_success.html @@ -0,0 +1,17 @@ +{% load i18n %} +{% get_current_language as LANGUAGE %} + + + + + {% trans "Authentication Success" %} + + + +

{% trans "Session stored successfully. This window will close automatically." %}

+

{% trans "If it doesn't close" %}, {% trans "click here" %}.

+ + + diff --git a/src/backend/core/urls.py b/src/backend/core/urls.py index d8493e5a..c81c4229 100644 --- a/src/backend/core/urls.py +++ b/src/backend/core/urls.py @@ -6,6 +6,8 @@ from django.urls import include, path from lasuite.oidc_login.urls import urlpatterns as oidc_urls from rest_framework.routers import DefaultRouter +from core.addons import views as addons_views +from core.addons import viewsets as addons_viewsets from core.api import get_frontend_configuration, viewsets from core.external_api import viewsets as external_viewsets @@ -26,12 +28,24 @@ external_router.register( basename="external_application", ) +# - Addons API +addons_router = DefaultRouter() +addons_router.register( + "addons/sessions", + addons_viewsets.AuthSessionViewSet, + basename="addons_auth_sessions", +) + external_router.register( "rooms", external_viewsets.RoomViewSet, basename="external_room", ) + +addons_urls = addons_router.urls if settings.ADDONS_ENABLED else [] + + urlpatterns = [ path( f"api/{settings.API_VERSION}/", @@ -39,12 +53,26 @@ urlpatterns = [ [ *router.urls, *oidc_urls, + *addons_urls, path("config/", get_frontend_configuration, name="config"), ] ), ), ] +if settings.ADDONS_ENABLED: + urlpatterns.append( + path( + "addons/", + include( + [ + path("transit/", addons_views.transit_page, name="transit_page"), + path("redirect/", addons_views.redirect_page, name="redirect_page"), + ] + ), + ), + ) + if settings.EXTERNAL_API_ENABLED: urlpatterns.append( path( diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.mo b/src/backend/locale/de_DE/LC_MESSAGES/django.mo index fd9e4b59579e2c7b170531b155045c63ee1b7e0e..22522cd6d346c05efe033bd8e886f88e14033ce0 100644 GIT binary patch delta 3566 zcmZA3Yj9L&9mnw}2@pa^Qf}eW22N5MDkKX{VymSoAzV_R1tDO8g6($q+2l0KIpLhM zEWxzfPIWAdj>?Ws9kn>4<7E_OjE;TL(ixjktF<%Ku``Y>EiY7bEWJz>+rFsZ-`Nwy zK9m3XJm>aY{?Bt39^3hoy`}SWr+vjx9wDwHmQOS0Gj?rT89B$HlZp z)hxqR*pZy?#dgjus^ib2o-gA{{2F%SBx->F!TD$!joDBCW&xF>oR6Yj{5v+`8_1ve zfWr;g$i+3-gX+j8=COzK5H+*!<34-}`7_N-vku$wT3myg=$+U=|0Y9)?3%-gp?fx=3e;yZbejYWzcTfX)cP{zY z2y4k3gEtL$BQ8Z%|i`*2c~d8-ipVu z3n%B3e>HHK3vwB)sDlB#9xW;}$FUC|M{UACVJH3-wHfD=e>K>L8!<#>?nzX~=dc4c zi~Z1v%FtsaDjTSrME=Z69Q5KP)XZx+VN#|MHIp_}hn=_v_o1FUfokx}SdCvt`ZQ1A zNATyUC3_Xy@FJ?cQvKq}jGD2A3)iD=tU#?*7iuYP!G%@E^x`<@dzSFU;EQ+@ZeVM7 zV*!i!1JtH#LJ3zeDi$8a6~7_}!Zp*G+9xD{)h$^^FKXE@KJCioUA1E!T<(h|<1Lbl9m)LL&t&Fo%O zCPuLz$5Cte4C=YxAY(Kyqn>{sHIUiNhUA#Vr~%!KC-HVvd*@ICc?C->pZO;hHnOQ@ z%d3GFhK->D7MqOW_$p($sR&2)oE0R&!P6lLVmUSu>d3X?Fu!kKspwT73m2J1R1pH&0ueoJCI zR&jnmp^lFdyNT_@Mj}lp#Txu0#HWd?i}n+x6n~2vsnS~@4iGBzAEz)rx#|#5Ur*>; z-%DsCs%VyEIWC(yYOq~IH}MdmWxAbER`(GqgZlmtQmH2*;$cE*QQ1-<-k_?ocg!b< zMTAnmpHR_0_!QymgtCViBebtnW>kp(laf?TNTrq7O8@ajn1!Q6AECmR6EBNCpH5v! ziid2Kc$c%}@h%@nOMH@0xtq{uJ5{z(VYSR1#87ha9?TQ!KQ6r-b#f=+5Qhkrc+=o6 z;$A{OI8#Nx8MBiU?e1Zsjo3l-5GvZ#+D{FHHgG?&iO3Sys>>r(1_%{C@3`#bsLw>7 zVuo;u2Z;v=6@8j2_YsFxp=_vNjwR->CaK?y`h8Iu(ZSi0bH@CFGd%H+y4R{!uP;Qy z){ERsI&yu_87yQnHVn;&E(i8-!3}ISRer9%bz0sJ?Zn0U-&IZD6a;=y{%OP0*SL|B z^=;^_h@6b>mq!{4U0b{(>AahD{J^nCN7aB6(mEZb%+$T)f)hsR$fnA#Hu?+FnaDk2 zryg~3e!Ue+%{K3^HWlP*O5A###Q>nSrh;*O5mZ26fvZS(v?HX3%lA*V>V_%c;IS-q5p8HCLl zK3&LJ&pLx{bc_L|%>Nz=rY?p`eIZPhN0)weiFMK=arTopoFSVd{nkrOR4r?&DJ7S~ z&Ugu;c!pQzV5VNG3~%by4-bcQR)%wRe&{G1RibPVrJRkfWn(y-trtd2rRWCPcuD4K mO_;WxX7YX*GO}`0TUYa@sF)7f6>(Nvo2$%yqPK0^jQ;{jfdn=H delta 2639 zcmYk+drXye9LMn=2ZRH0IS7aq!~wY|iX4QZ4jNZMAoG$JP{9xc4HSr=bkWnAEZ5R4 z&+xJ`x7O?ryR43FwQ_Fh(yW}_`NI+2HpymNIn!3z`*WVB#&3L|*YEf2JkRg){XV?W zw0ng$K0RX8&<2S#;&iw%hp{J$3vD9Wn9cYduEn|-W3urG7T{^zioc;A*>H<7k=TK8 z*p2*|9bBg42qxh{^cZ8ASLwuZ;{@J;=bZ5*&SiYf8E5gMT*if{3D)2=Y{7hN$70-z zTF3{OjOTD2ev6NxpH(Gb5aXHOoT8J%jnC1C*HIJ9iMRD(3FA_1#varQK12@1e2qL} zCXhogR~&C(A>#;stoN0mo-5+9K@$_Cu)Z-&WS@XZq!7{j#;P$ z_>j4b9}}@2Q*k}c!~xX8p2a*ofLg#ww3x%3rBjAus23!2(Tj6YV;`3ktU@iY3AKJ@#;2V7$510`Ds0UA>c6JeYxA_6Jkv~uqy2z&w=c9hN5%s=KbYUOzX9g^;bLc#S%E@ue!V{<$ zPM~&l8Tm5Jb?5$Hs8mF;Dy1M6Q^WYg@NvfZtg;VZ!6KY_tNnE};G>M6K-H9WjZUb@ zZnJahMy+%PDyLo~U8V?i)YYg5S7ITyqJFm*b&^L=HFX}fv2oN1xNo=bCtxMx47^9* ze;Xa`{CU*QU&dZMgD+qy|CPv!`OW8B zWntpI_Vd|D9h)NbSafRW@EfxlGjS00f&-}B9Yw8t43&!SaRpvQy{MLlR4dJ>;_N^z zd>BwC=KPi0K5kUu2v*=(EXC*?`^A+=VVhy}<0vk~ z3)q8kx%LU|#I=lfp;B}aNx!*)(=av9zMqcTus@IUR~6TCLpvD4NPG)*gzusYKSky8 z0?x(D7=@|)yQ6C|P&HGBO3`u*$4#iOp%dLW%?mR)J*6o z`9Jl4TR>0wReO*qCiF#g5K5be=p&kmNIzmOMLY_w$Z+)kCNAI9_OWRQJmzeRez@M?+ zpf~QLu)weJ?*~pM^at8K+k-!Qx?O?uNwb5o$;0lznKWO}m0s%#EX%kV=<&W7Xv`c9 S-pq^%4;;=Z3SP>oi~JXK>+xj( diff --git a/src/backend/locale/de_DE/LC_MESSAGES/django.po b/src/backend/locale/de_DE/LC_MESSAGES/django.po index 051db135..0b668f15 100644 --- a/src/backend/locale/de_DE/LC_MESSAGES/django.po +++ b/src/backend/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-29 15:15+0000\n" +"POT-Creation-Date: 2026-01-26 15:40+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,30 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: core/addons/views.py:24 +msgid "Session ID is required." +msgstr "Sitzungs-ID ist erforderlich." + +#: core/addons/views.py:29 +msgid "Session not found or expired." +msgstr "Sitzung nicht gefunden oder abgelaufen." + +#: core/addons/views.py:32 +msgid "Invalid session state." +msgstr "Ungültiger Sitzungsstatus." + +#: core/addons/views.py:45 +msgid "Authentication required." +msgstr "Authentifizierung erforderlich." + +#: core/addons/views.py:50 +msgid "No active session found." +msgstr "Keine aktive Sitzung gefunden." + +#: core/addons/views.py:55 +msgid "Invalid or expired session." +msgstr "Ungültige oder abgelaufene Sitzung." + #: core/admin.py:29 msgid "Personal info" msgstr "Persönliche Informationen" @@ -408,7 +432,7 @@ msgstr "Anwendungsdomain" msgid "Application domains" msgstr "Anwendungsdomains" -#: core/recording/event/notification.py:94 +#: core/recording/event/notification.py:116 msgid "Your recording is ready" msgstr "Ihre Aufzeichnung ist bereit" @@ -417,6 +441,30 @@ msgstr "Ihre Aufzeichnung ist bereit" msgid "Video call in progress: {sender.email} is waiting for you to connect" msgstr "Videoanruf läuft: {sender.email} wartet auf Ihre Teilnahme" +#: core/templates/addons/error.html:7 core/templates/addons/error.html:11 +msgid "Error" +msgstr "Fehler" + +#: core/templates/addons/error.html:12 +msgid "Something went wrong." +msgstr "Etwas ist schiefgelaufen." + +#: core/templates/addons/error.html:13 +msgid "Close" +msgstr "Schließen" + +#: core/templates/addons/redirect_success.html:7 +msgid "Authentication Success" +msgstr "Authentifizierung erfolgreich" + +#: core/templates/addons/redirect_success.html:13 +msgid "Session stored successfully. This window will close automatically." +msgstr "Sitzung erfolgreich gespeichert. Dieses Fenster wird automatisch geschlossen." + +#: core/templates/addons/redirect_success.html:14 +msgid "If it doesn't close" +msgstr "Falls es sich nicht schließt" + #: core/templates/mail/html/invitation.html:159 #: core/templates/mail/html/screen_recording.html:159 #: core/templates/mail/text/invitation.txt:3 diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.mo b/src/backend/locale/en_US/LC_MESSAGES/django.mo index cdd88d170a1420d061c8efb8a74cd34022d98b3c..fb982e7bce4399dc6b77ac423c64b3e9baed1d6d 100644 GIT binary patch literal 10281 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 ND|yKm^;`Wb`QPn=MaBRC delta 2596 zcmZwHe@xVM9LMp`-60}yc#2A;@gwdy;11!CCd9BVeSVa--ot!CE}Yth23X_@6#%YWS3nyl&iZ9QN2{jl*zpWXW&pU-#q{rq@; zKA#(FKfNyY!~C@UhB8DfA>Q>EGm7hH^F{e&jxkX@j(1`((-=R-umTTZFP=iZ(vW3L zI<}w}Tal0HpmLD%m_3#e6)1A^gHVzl9=Cu2EAup0YOFTP^^9IvH) z5sT5w3DU|V*n~}3flr~%&RbZG=dcBHI1tLnM$~xy7{&t&$iEKB1P`?GVwT&C6{u7W zqF#IswW3#%KFt_v1xeI^6Ig|pP`?W-a{3FSn|3YoF?D>EVLNIow#TSkN9Ac$hlh~^ zYu-j~qWRc9A4hG$NmS}jVV=vFvlyj4#cXvwJD9B>kD@Yi8hg=owX+3%s0j|CwlcPz ziVoScNOsLW)Cylm?d>ttgeFnf^E=dvf49&5*ErWRg6eoPYJ%NZj!|5Odr)WKQzY4D z61#N&f1)zXgNDVYM-y8pFQ^x_KCp0uGl=s>b-wxCk~1a8C~s1<&N%G?=D4P>1{y?+I@kPOns zfj4=mGgXG{idlu3y8m5NblRh+3A~7!&Uy@@=TD+C7{izb-c3b&a}+h;`>2%tin=cU;BpKXI@hWP_1??Yab&UPXVmq~ zC~{UF#wObJSb;lGXXgm&dY&mF|GJ-ZxB^Pa2GoE(sQWpB91?Q^weoy=Z^lwoD*I9I z?Lw_+6m@tHp%(A~YQPhyGjtL4yF#vr`V0DF&i#z=Kzm(-W!Q?^ipP<$%}!K@2T`}- zP4wW0_IVQ1XiuP0e-d4&Y_1`0B8G??hIAYJ0%kLEK1OuLdQyimzs3t6CIpM{4D&-$kq@baSB*pmv4v2%-9g{6RC<+4?c;QbP+4pn_o5CVO zKPi;~LjQP8gi5(X>d%Jkr^;h|HxRcuP4lQ#x2uiF(f!wf;Lpt5Ppl+V^oODYvx4X* zT8IK-6Y(IS(_BnMiJ9eY+gOjSL_eWFshMS+Z9IV4T9uaw5VeF#kwfZ)t2#(<)l;R1 z@3pqBldfB`neY*AB9usG{^E*%pIwmnH#_NykLP?8-#d3OzNcUyk@WSs;|Fs~6F=q# UGXD1`@uV-DXfDi}{+WN)e;+{WXaE2J diff --git a/src/backend/locale/en_US/LC_MESSAGES/django.po b/src/backend/locale/en_US/LC_MESSAGES/django.po index 313f2352..9d3fcd50 100644 --- a/src/backend/locale/en_US/LC_MESSAGES/django.po +++ b/src/backend/locale/en_US/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-29 15:15+0000\n" +"POT-Creation-Date: 2026-01-26 15:40+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,30 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: core/addons/views.py:24 +msgid "Session ID is required." +msgstr "Session ID is required." + +#: core/addons/views.py:29 +msgid "Session not found or expired." +msgstr "Session not found or expired." + +#: core/addons/views.py:32 +msgid "Invalid session state." +msgstr "Invalid session state." + +#: core/addons/views.py:45 +msgid "Authentication required." +msgstr "Authentication required." + +#: core/addons/views.py:50 +msgid "No active session found." +msgstr "No active session found." + +#: core/addons/views.py:55 +msgid "Invalid or expired session." +msgstr "Invalid or expired session." + #: core/admin.py:29 msgid "Personal info" msgstr "Personal info" @@ -405,7 +429,7 @@ msgstr "Application domain" msgid "Application domains" msgstr "Application domains" -#: core/recording/event/notification.py:94 +#: core/recording/event/notification.py:116 msgid "Your recording is ready" msgstr "Your recording is ready" @@ -414,6 +438,30 @@ msgstr "Your recording is ready" msgid "Video call in progress: {sender.email} is waiting for you to connect" msgstr "Video call in progress: {sender.email} is waiting for you to connect" +#: core/templates/addons/error.html:7 core/templates/addons/error.html:11 +msgid "Error" +msgstr "Error" + +#: core/templates/addons/error.html:12 +msgid "Something went wrong." +msgstr "Something went wrong." + +#: core/templates/addons/error.html:13 +msgid "Close" +msgstr "Close" + +#: core/templates/addons/redirect_success.html:7 +msgid "Authentication Success" +msgstr "Authentication Success" + +#: core/templates/addons/redirect_success.html:13 +msgid "Session stored successfully. This window will close automatically." +msgstr "Session stored successfully. This window will close automatically." + +#: core/templates/addons/redirect_success.html:14 +msgid "If it doesn't close" +msgstr "If it doesn't close" + #: core/templates/mail/html/invitation.html:159 #: core/templates/mail/html/screen_recording.html:159 #: core/templates/mail/text/invitation.txt:3 diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.mo b/src/backend/locale/fr_FR/LC_MESSAGES/django.mo index c76470df2be646bf8d833467a95ee452481c1c01..11899a94d6d287c6fb8bb339c80dbf3d5a9ebf1c 100644 GIT binary patch delta 3544 zcmZA3YitzP8OHHr47S18#9$y$zz1_NNz6hG1sXzQf{D2$Kuj)>OS9QMwx?e2*gLZ} zZb-K`(hw!7(pB2DQOSp*ghYvC)vekOC8;oK)rM9=m8cq35Gs`>QPn0@5heXV|Ih5K zAwAl6e`jWA&gDI4#vZ-r#Qws$nUh~Il&=vBh^3Q_c@tY}_@cZw%@~Il@P6#9;D4|YO}#M(=-^SbnUmzbdhiOj5<@gy~jhg5#tfPOEqC$4fVC*4W z#qqaL9sLZ|&|9d?{2sMBe?v9=5vsvYkkvB{4cv=!P!l?eT7qN9SVB zXOWNjD_?W*GOEKFESv_o0$XqmY5)(S2A0N+7^3bUMYVe}K0b}JIX;IP;J;A=xiXXd zYlPM0jlr8bT!D*F=Q~juxC=F)eYg-acn3a>8qn`h9b7`KaV-n10nI`Ud^aZW0N#p6 zunkW&l7BUDnG>>!R@6Z!Ev2hiF`Mup$I1D|4B@-D827TZ zHJ|{$j>j-+hnI@(Z$o8r18OsNB6BR5E-DPh^r1%lO;n0sLi#etV^8AE9RC@k%&<<{ zZ1t$~7Fk8J6PMznsJ(C))!wtHrTGE2Qj)_=k^CYjhRW zKs}FuI$DZ+%qG5++9c|^e-gDBzl+MiNz{F(aX0=YK5pYCO=KJDG4DctGU=u9^Zyhl zG=t|*BmNPpq1SO8{t`9f%kl4(%Sr>8hL-cI@Gjhg%G6O*M$e#@6OWsEmAon&9NtQl<;*sCgTS zVjpog(Lp2$WsIjR`rqPlyuN4>ltMImb(kRX#6yG%12yZT+NZyit<8is-~)sPr=nSs z?Wic_%7XTSmhmfu_Lr83*GKf{8B7=bn}?}rW+CxaqMA_IULtx6s>M{omHwzME|73RTEHIOzfb4a~m;@7$DjS6+Is8t>}fLjZ-dJDr}YL zRm+x$Hs2w1#C?Q{_KzO3a@k3R)iT?Np7`X0m?6}ER5tOggI$Cr`Uw?nnqlG|;){ef zWx33zGCe++i@iiMp=V!Dfr|E&UTSrOHt-$9Rw7OC)-6PTL^?T8;qi{jUcPT7v{O@r zM?67%iBQpoRe6*cRE4sog!yu8H&(`Vy+ic3MWs()c6+xS@$Hk}yTF~gr!DBW$hcUUj56QeR+@%gMDmM*`p%f!U5LFh+AD9y1upUI3Q?7m)F z8uqeje;6{E((r9EANu{|B$d=vrmN2z7;x$0+YQaL{C*eqdf6U3OycZt&d>IYoojfx zW=%P1-Q{fN#$U@bT{n@=YriO=0rNdHzqRU4m+N=Au~+ABoK!_dT|PH;;j@er&hjjZ`bfmC7I)OZMh&U{%@KAcSsxs(l9gYn}*!ftV4sOe?LUk`H=Q*Cs)1`0U7`R delta 2644 zcmYk;eN0t#9LMn=mn%>4{E!$Bm!}H?%1ue?Qh5{PAydmpMR~|jBtd})i#EHB%F0!L zbk?+0>%XShlDf8;#oUBiIoDQOtv?))mD3_@uB9;l=>0kO)cB3>dHsH8_x#T9;rlx} z);zi?IGO5x%TNZ1Y+@qPnD?+JnhRw*)|g%RGj759IAaR&eO!hU_yk@dBbaPV(0oWGo*QF$JDzjeSFnusKTbQJA9-n4pkA;U7hx-wVh66o zH&6pPi|Kd{x8W2%gMLPpgdt4i{brm>5jVa^AKpN{U}<7lA6C+?!e;D2b?_CkDCS4x z5i^Y}f|+%k!wTANeysi~QO{L#iNSRk%ll0em0Ph5-8hIZzR`!Nj0ml+c1E8k!>~~;WGRjl|xrh{bi64wb+1Knb*@sAO?5U9CVX z*5Gc`k{(ArIF6dx1*C8D3u+?QQ7?3{pFUiU`dvG!zfN>vAM$4gf?StUIfUAilbDZV zs1B!5Gx`HLndXLb|0ZfHq8XL8ARY@MIAW+QU&Sc*;|a_|?~?F<8gQ8Q5UT&+O)Baj z`nK>=CZLin8#Ut+)QkhjJ54icKz&$>2axSFN1gU*WD(78ScvneJH%YdE6Iom{ z=iE>8gjYBZ^}G)=gH-CMD4W|+OZEclSRO?s(<#&pe?>hwiyP6+#;Vbdh6Tt(#~6^(canWrBVp;n?CwWRG>hh3-{p1^7x!vN0VTJ-bQ zJMej2iJzd7`x0u)l9`=OOCzqs)D#P)Rw3TW|*1HB(z`j0Zb#5x$7*hB<^<(UD@-Umcy~#-liknsIGOctzHui*_@T z$fgA~!>2I{kKvs-it6wZYKDJdB)XZtK2R~J6<&-QXaPpxJ=*_zqK+6Ov`p=UihcZW zFX1P2{2n5-_f3RKD#4iT&Xoh2WdpIWRM6-p`WM`6q25CTySeDtX}MIi3R)56g~}SD zmgrD}!fCQsNcC2thF~)Pmpx7`1GUF0u>>h*=ZqUm2^GGOLHkSwXlx}OCsZB@V_(S~ zdnuJC{}m-Cp8>l(##PDoFtL-+^6M1z5zWN?gmR*cP*Hx7K6ZJE_nUjEZgOrYnN*U9 z4TSbi`>eg+MXV-NwiC(&CG8GkE#Vq2%}C^5(H3T@2Iim-+~ buUfTv!=X!gg^^aSH_Q60C^Ixu^j*}y2Osv> diff --git a/src/backend/locale/fr_FR/LC_MESSAGES/django.po b/src/backend/locale/fr_FR/LC_MESSAGES/django.po index 5f8c16b2..f61a3fab 100644 --- a/src/backend/locale/fr_FR/LC_MESSAGES/django.po +++ b/src/backend/locale/fr_FR/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-29 15:15+0000\n" +"POT-Creation-Date: 2026-01-26 15:40+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: antoine.lebaud@mail.numerique.gouv.fr\n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,30 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: core/addons/views.py:24 +msgid "Session ID is required." +msgstr "L'identifiant de session est requis." + +#: core/addons/views.py:29 +msgid "Session not found or expired." +msgstr "Session introuvable ou expirée." + +#: core/addons/views.py:32 +msgid "Invalid session state." +msgstr "État de session invalide." + +#: core/addons/views.py:45 +msgid "Authentication required." +msgstr "Authentification requise." + +#: core/addons/views.py:50 +msgid "No active session found." +msgstr "Aucune session active trouvée." + +#: core/addons/views.py:55 +msgid "Invalid or expired session." +msgstr "Session invalide ou expirée." + #: core/admin.py:29 msgid "Personal info" msgstr "Informations personnelles" @@ -409,7 +433,7 @@ msgstr "Domaine d’application" msgid "Application domains" msgstr "Domaines d’application" -#: core/recording/event/notification.py:94 +#: core/recording/event/notification.py:116 msgid "Your recording is ready" msgstr "Votre enregistrement est prêt" @@ -418,6 +442,30 @@ msgstr "Votre enregistrement est prêt" msgid "Video call in progress: {sender.email} is waiting for you to connect" msgstr "Appel vidéo en cours : {sender.email} attend que vous vous connectiez" +#: core/templates/addons/error.html:7 core/templates/addons/error.html:11 +msgid "Error" +msgstr "Erreur" + +#: core/templates/addons/error.html:12 +msgid "Something went wrong." +msgstr "Une erreur s'est produite." + +#: core/templates/addons/error.html:13 +msgid "Close" +msgstr "Fermer" + +#: core/templates/addons/redirect_success.html:7 +msgid "Authentication Success" +msgstr "Authentification réussie" + +#: core/templates/addons/redirect_success.html:13 +msgid "Session stored successfully. This window will close automatically." +msgstr "Session enregistrée avec succès. Cette fenêtre se fermera automatiquement." + +#: core/templates/addons/redirect_success.html:14 +msgid "If it doesn't close" +msgstr "Si elle ne se ferme pas" + #: core/templates/mail/html/invitation.html:159 #: core/templates/mail/html/screen_recording.html:159 #: core/templates/mail/text/invitation.txt:3 diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.mo b/src/backend/locale/nl_NL/LC_MESSAGES/django.mo index afea428accc4b28a94bbcaa18f07b7546d507a7a..e26acf92edaf78e3ad3fb1056a439570fcaa134e 100644 GIT binary patch delta 3493 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 TqUlRt^hCUYg6Ouuv048EXf^Xl diff --git a/src/backend/locale/nl_NL/LC_MESSAGES/django.po b/src/backend/locale/nl_NL/LC_MESSAGES/django.po index 160e2240..4951037c 100644 --- a/src/backend/locale/nl_NL/LC_MESSAGES/django.po +++ b/src/backend/locale/nl_NL/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-12-29 15:15+0000\n" +"POT-Creation-Date: 2026-01-26 15:40+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,30 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#: core/addons/views.py:24 +msgid "Session ID is required." +msgstr "Sessie-ID is vereist." + +#: core/addons/views.py:29 +msgid "Session not found or expired." +msgstr "Sessie niet gevonden of verlopen." + +#: core/addons/views.py:32 +msgid "Invalid session state." +msgstr "Ongeldige sessiestatus." + +#: core/addons/views.py:45 +msgid "Authentication required." +msgstr "Authenticatie vereist." + +#: core/addons/views.py:50 +msgid "No active session found." +msgstr "Geen actieve sessie gevonden." + +#: core/addons/views.py:55 +msgid "Invalid or expired session." +msgstr "Ongeldige of verlopen sessie." + #: core/admin.py:29 msgid "Personal info" msgstr "Persoonlijke informatie" @@ -404,7 +428,7 @@ msgstr "Applicatiedomein" msgid "Application domains" msgstr "Applicatiedomeinen" -#: core/recording/event/notification.py:94 +#: core/recording/event/notification.py:116 msgid "Your recording is ready" msgstr "Je opname is klaar" @@ -413,6 +437,30 @@ msgstr "Je opname is klaar" msgid "Video call in progress: {sender.email} is waiting for you to connect" msgstr "Video-oproep bezig: {sender.email} wacht op je verbinding" +#: core/templates/addons/error.html:7 core/templates/addons/error.html:11 +msgid "Error" +msgstr "Fout" + +#: core/templates/addons/error.html:12 +msgid "Something went wrong." +msgstr "Er is iets misgegaan." + +#: core/templates/addons/error.html:13 +msgid "Close" +msgstr "Sluiten" + +#: core/templates/addons/redirect_success.html:7 +msgid "Authentication Success" +msgstr "Authenticatie geslaagd" + +#: core/templates/addons/redirect_success.html:13 +msgid "Session stored successfully. This window will close automatically." +msgstr "Sessie succesvol opgeslagen. Dit venster wordt automatisch gesloten." + +#: core/templates/addons/redirect_success.html:14 +msgid "If it doesn't close" +msgstr "Als het niet sluit" + #: core/templates/mail/html/invitation.html:159 #: core/templates/mail/html/screen_recording.html:159 #: core/templates/mail/text/invitation.txt:3 diff --git a/src/backend/meet/settings.py b/src/backend/meet/settings.py index eda28a76..f2b0279d 100755 --- a/src/backend/meet/settings.py +++ b/src/backend/meet/settings.py @@ -797,6 +797,66 @@ class Base(Configuration): environ_prefix=None, ) + # Addons + ADDONS_ENABLED = values.BooleanValue( + False, + environ_name="ADDONS_ENABLED", + environ_prefix=None, + ) + ADDONS_SESSION_ID_LENGTH = values.PositiveIntegerValue( + 32, + environ_name="ADDONS_SESSION_ID_LENGTH", + environ_prefix=None, + ) + # Used in cache key generation + ADDONS_SESSION_KEY_PREFIX = values.Value( + "addons_session_id", + environ_name="ADDONS_SESSION_KEY_PREFIX", + environ_prefix=None, + ) + # Used as the Django session key in transit page + ADDONS_SESSION_KEY_AUTH = values.Value( + "addons_session_id", + environ_name="ADDONS_SESSION_KEY_AUTH", + environ_prefix=None, + ) + ADDONS_SESSION_TIMEOUT = values.PositiveIntegerValue( + 600, environ_name="ADDONS_SESSION_TIMEOUT", environ_prefix=None + ) + ADDONS_JWT_SECRET_KEY = SecretFileValue( + None, environ_name="ADDONS_JWT_SECRET_KEY", environ_prefix=None + ) + ADDONS_JWT_ALG = values.Value( + "HS256", + environ_name="ADDONS_JWT_ALG", + environ_prefix=None, + ) + ADDONS_SCOPES = values.Value( + "rooms:create rooms:list", + environ_name="ADDONS_SCOPES", + environ_prefix=None, + ) + ADDONS_JWT_ISSUER = values.Value( + "lasuite-meet", + environ_name="ADDONS_JWT_ISSUER", + environ_prefix=None, + ) + ADDONS_JWT_AUDIENCE = values.Value( + None, + environ_name="ADDONS_JWT_AUDIENCE", + environ_prefix=None, + ) + ADDONS_JWT_EXPIRATION_SECONDS = values.PositiveIntegerValue( + 3600, + environ_name="ADDONS_JWT_EXPIRATION_SECONDS", + environ_prefix=None, + ) + ADDONS_JWT_TOKEN_TYPE = values.Value( + "Bearer", + environ_name="ADDONS_JWT_TOKEN_TYPE", + environ_prefix=None, + ) + # pylint: disable=invalid-name @property def ENVIRONMENT(self): diff --git a/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl b/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl index 04c2f170..e34a3075 100644 --- a/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl +++ b/src/helm/env.d/dev-keycloak/values.meet.yaml.gotmpl @@ -79,6 +79,7 @@ backend: APPLICATION_JWT_AUDIENCE: https://meet.127.0.0.1.nip.io/external-api/v1.0/ APPLICATION_JWT_SECRET_KEY: devKeyApplication APPLICATION_BASE_URL: https://meet.127.0.0.1.nip.io + ADDONS_JWT_SECRET_KEY: devKeyApplicationAddons migrate: diff --git a/src/helm/meet/templates/ingress.yaml b/src/helm/meet/templates/ingress.yaml index 8f15f761..e83459cf 100644 --- a/src/helm/meet/templates/ingress.yaml +++ b/src/helm/meet/templates/ingress.yaml @@ -88,6 +88,20 @@ spec: serviceName: {{ include "meet.backend.fullname" . }} servicePort: {{ .Values.backend.service.port }} {{- end }} + - path: /addons/ + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "meet.backend.fullname" . }} + port: + number: {{ .Values.backend.service.port }} + {{- else }} + serviceName: {{ include "meet.backend.fullname" . }} + servicePort: {{ .Values.backend.service.port }} + {{- end }} {{- with .Values.ingress.customBackends }} {{- toYaml . | nindent 10 }} {{- end }} @@ -138,6 +152,20 @@ spec: serviceName: {{ include "meet.backend.fullname" $ }} servicePort: {{ $.Values.backend.service.port }} {{- end }} + - path: /addons/ + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: Prefix + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ include "meet.backend.fullname" $ }} + port: + number: {{ $.Values.backend.service.port }} + {{- else }} + serviceName: {{ include "meet.backend.fullname" $ }} + servicePort: {{ $.Values.backend.service.port }} + {{- end }} {{- with $.Values.ingress.customBackends }} {{- toYaml . | nindent 10 }} {{- end }}