# Infomaniak Connector (kDrive + Calendar + Contacts today; Mail reserved) + UDB-Integration ## Beschreibung und Kontext PowerOn besitzt das Provider-Connector-Pattern fuer externe Datenanbindungen (Microsoft, Google, ClickUp). Infomaniak war bisher nicht angebunden. Ziel: ein neuer `InfomaniakConnector`, der Daten aus den Infomaniak-Services bereitstellt. Heute aktiv: - **kDrive** (Pendant zu OneDrive / Google Drive) - **Calendar** (Pendant zu Outlook-Kalender / Google Calendar; .ics-Download) - **Contacts** (Pendant zu Outlook-Kontakte / Google Contacts; .vcf-Download) Blockiert (Scope ist auf der PAT, Adapter wartet auf Vendor): - **Mail** -- alle erschoepfend getesteten Pfade scheitern. Final-Befund vom 2026-04-28: | Pfad | Status | |---|---| | `api.infomaniak.com/1/mail` | 404 nginx (existiert nicht) | | `api.infomaniak.com/2/mail?account_id=...` | 404 nginx (existiert nicht) | | `mail.infomaniak.com/api/mail?account_id=...` | 302 -> `login.infomaniak.com/authorize` (OAuth Web-Session, nicht PAT) | | `mail.infomaniak.com/api/mail/?account_id=...` | 301 -> `http://mail.infomaniak.com:5000` (interner Cyrus-IMAP-API-Port, von aussen nicht erreichbar) | | `mail.infomaniak.com/api/pim/mail` | 302 -> OAuth | | `mail.infomaniak.com/api/pim/mailbox` | 302 -> OAuth | | `mail.infomaniak.com/api/pim/folder` | 302 -> OAuth | Konsequenz: PowerOn kann mit dem heutigen PAT-Mechanismus die Infomaniak-Mailbox **nicht** ansprechen. Der Scope `workspace:mail` bleibt im Standard-PAT-Setup, sodass der MailAdapter ohne Token-Rotation aufgeschaltet werden kann, sobald Infomaniak einen PAT-tauglichen Endpoint freischaltet (oder wir entscheiden, die Mail-Connection ueber einen separaten OAuth-Flow oder ein App-Passwort ueber IMAP/SMTP einzubinden -- separater Build). Die Verbindung ist eine reine **Daten-Connection** (`TokenPurpose.DATA_CONNECTION`). Infomaniak ist explizit **kein** Login-Provider fuer PowerOn. Zusaetzlich wurden zwei kleine Inkonsistenzen in der ClickUp-UDB-Anzeige behoben (`_SERVICE_ICONS` + `_SERVICE_TO_SOURCE_TYPE`). ## Architektur-Pivot 2026-04-28: OAuth -> Personal Access Token **Befund**: Infomaniaks `login.infomaniak.com/authorize` akzeptiert nur Identity-Scopes (`openid`, `profile`, `email`, `phone`). Bearer-Tokens, die gegen die Daten-APIs (`/2/drive/...`, `/1/mail/...`) funktionieren, koennen ueber OAuth nicht ausgestellt werden -- der Versuch quittiert mit `error=invalid_scope`. Datenzugriff laeuft bei Infomaniak ausschliesslich ueber **Personal Access Tokens (PATs)**, die der Endnutzer manuell im Infomaniak-Manager erstellt. **Konsequenz**: kompletter Umbau des Auth-Teils des Connectors. Das Provider-Connector-Pattern und die Adapter (`KdriveAdapter`, `MailAdapter`) bleiben unveraendert -- sie konsumieren ohnehin nur einen Bearer-Token. Was aussortiert wurde: - OAuth-Routen (`/api/infomaniak/auth/connect[/callback]`) -> entfernt. - Token-Refresh-Logik (`refreshInfomaniakToken`, Background-Refresh-Branch) -> entfernt; PATs sind langlebig, kein Rotations-Lifecycle. - Scope-Definition `infomaniakDataScopes` -> entfernt. - `Service_INFOMANIAK_*` Env-Variablen (Client-ID/Secret/Redirect-URI) -> entfernt; PowerOn benoetigt keine Infomaniak-App-Registrierung mehr. - Frontend-OAuth-Popup -> ersetzt durch Modal mit Token-Eingabe + Deeplink zum Infomaniak-Manager. **Neuer Auth-Flow**: 1. FE: User klickt "Infomaniak" -> `POST /api/connections/` mit `authority=infomaniak` legt PENDING-Connection an. 2. FE: Modal oeffnet sich mit Schritt-fuer-Schritt-Anleitung + Link `https://manager.infomaniak.com/v3/ng/accounts/token/list` und Token-Eingabefeld. 3. FE: User fuegt PAT ein -> `POST /api/infomaniak/connections/{id}/token`. 4. BE: Validiert PAT in zwei Schritten: - `resolveOwnerIdentity()` ruft PIM Calendar (Scope `workspace:calendar`) auf, faellt bei leerer Owner-Liste auf PIM Contacts zurueck (Scope `workspace:contact`). Beide Endpoints liefern den ersten Owner-Record als `{account_id, name, user_id}`. Wenn keiner antwortet -> 400 fail-loud (PAT ist fuer kDrive unbrauchbar, weil `/2/drive` `account_id` zwingend braucht und kein Drive-Endpoint die Identity ohne `user_info`-Scope rausgibt). - `GET /2/drive?account_id={resolved}` -- erwartet 200, andernfalls 400 (`drive`-Scope fehlt) bzw. 502 (Infomaniak antwortet unerwartet). Mit der vorab resolvten `account_id` ist der Probe deterministisch und braucht keine 422-Tolerance mehr. Token landet als `Token` mit 10-Jahres-Horizont (`tokenStatus`-Anzeige, analog ClickUp). `externalUsername` und `externalId` werden fuer die UI-Anzeige gesetzt; **`externalId` ist nicht Vertragspartner des kDrive-Adapters** (siehe "Kritische Details"). 5. FE: bei Erfolg -> Modal schliesst, Liste refresht; bei Fehler -> Connection bleibt PENDING, Modal zeigt Fehlermeldung; bei Cancel -> Connection wird via DELETE wieder entfernt. ## Fokus und kritische Details - **API-Pfad-Konvention** (Adapter-Path != API-Path): - kDrive (`api.infomaniak.com`): Adapter-Path `/{driveId}/{fileId}`, API `/2/drive/{driveId}/files/{fileId}`. `account_id` muss bei jedem Listing-Call als Query-Arg mit. **Wichtig**: Ein User kann kDrives in mehreren Infomaniak-Accounts besitzen (typischerweise einer in der kSuite + ein Standalone- oder Free-tier-kDrive auf einem **anderen** account_id). Der Adapter resolvt deshalb on demand ueber `resolveAccessibleAccountIds()` (-> `GET /1/accounts`) **alle** erreichbaren account_ids, ruft `/2/drive?account_id=X` fuer jede auf und unioniert die Drive-Listings mit Dedup ueber `driveId`. Cache auf der Adapter-Instanz (`_ensureAccountIds`). `/1/accounts` erfordert den PAT-Scope `accounts`; ohne ihn schlaegt schon der Submit-Pre-Flight fehl. - Calendar (`calendar.infomaniak.com/api/pim`): Adapter-Path `/{calendarId}/{eventId}`. **Achtung**: die naheliegenden Nested-Routes (`/calendar/{id}/event`, `/calendar/{id}/event/{id}`, `/calendar/{id}/event/{id}/export`) sind **NICHT** PAT-faehig -- sie 302-en zur OAuth-Login-Seite. Korrekte PAT-Pfade: - Listing: `/api/pim/event?calendar_id={id}&from=YYYY-MM-DD HH:MM:SS&to=...` mit Pflicht-Range, max **3 Monate** (Vendor-Constraint). Adapter wahlt fix 90-Tage-Window (heute -30 / +60). - Detail: `/api/pim/event/{eventId}` (ohne calendar-Praefix) - Export `.ics`: `/api/pim/event/{eventId}/export` - Contacts (`contacts.infomaniak.com/api/pim`): Adapter-Path `/{addressBookId}/{contactId}`. Listing der AddressBooks und der Contacts geht via PAT, **aber Detail- und Export-Endpoints (`/addressbook/{book}/contact/{id}` und `.../export`) sind nicht PAT-faehig** (500 bzw. 302 OAuth). Konsequenzen: - Listing braucht zwingend `&with=emails,phones,addresses,details`, sonst kommen die relevanten Felder leer (Default-Response listet nur Stammdaten). - `.vcf`-Download wird im Adapter **selbst synthetisiert** aus dem Listing-Record (vCard 3.0). Implementierung in `_renderInfomaniakVcard()`. - Geteilte Organisations-Adressbuecher haben einen leeren Namen -- der Adapter setzt dort einen Platzhalter "Organisation". `ServiceAdapter.browse` unterscheidet die Tiefe via Segment-Count. - **Multi-Host**: `_infomaniakGet` / `_infomaniakDownload` akzeptieren ein optionales `baseUrl`, sodass Calendar gegen `calendar.infomaniak.com`, Contacts gegen `contacts.infomaniak.com` und kDrive gegen `api.infomaniak.com` laufen. - **Zwei Resolver, getrennte Verantwortung**: - `resolveOwnerIdentity(token)` -> Display-Name + kSuite-`account_id`, rein fuer das UI-Label der Connection. Erste Quelle PIM Calendar, Fallback PIM Contacts; nimmt den ersten Owner-Record (`user_id > 0` und `isinstance(account_id, int)`). - `resolveAccessibleAccountIds(token)` -> Liste **aller** account_ids, die der PAT erreicht. Ein einzelner `GET /1/accounts`-Call. Vom `KdriveAdapter` benutzt, weil ein Standalone-/Free-tier-kDrive auf einem **anderen** account_id liegt als die kSuite und die kSuite-`account_id` aus PIM den Drive nicht abdeckt. Beide raisen `InfomaniakIdentityError` mit einer scope-spezifischen Fehlermeldung, sodass der Submit-Endpoint pro fehlendem Scope eine eigene 400-Message zurueckgeben kann. - **`externalId` ist UI-State, kein Adapter-Vertragspartner**: Submit speichert `externalId = str(kSuiteAccountId)` fuer die ConnectionsPage (Anzeige + Konsistenz mit anderen Providern), aber der KdriveAdapter liest ihn nie -- er fragt zur Laufzeit `/1/accounts`. - **Antwort-Wrapping**: Erfolgreiche Responses sind als `{result: 'success', data: ...}` gewrappt -- `_unwrapData()` normalisiert. - **Token-Validation** im Submit-Endpoint: drei harte 200-Schritte, jeder probet genau einen Scope: 1. `resolveAccessibleAccountIds` -> probet `accounts`-Scope. 2. `resolveOwnerIdentity` -> probet `workspace:calendar`/ `workspace:contact`-Scope (mindestens einer noetig). 3. `/2/drive?account_id={first}` -> probet `drive`-Scope. Jeder Schritt liefert eine eigene 400-Message, die exakt den fehlenden Scope nennt. 401/403 -> 400 mit Scope-Hinweis; alles Unerwartete -> 502. - **Token-Persistenz**: `expiresAt = now + 10*365*24*3600`, `tokenRefresh = None`; `getTokenStatusForConnection` zeigt damit `active`, kein "none". ## Ziel und Nicht-Ziele - Ziel: Infomaniak-Daten (kDrive + Mail) wie MSFT/Google im UDB browsbar. - Ziel: ConnectionsPage hat Button "Infomaniak" mit PAT-Modal. - Ziel: Setup ohne Operator-Interaktion (keine globale App-Registrierung). - Ziel: ClickUp-UDB-Inkonsistenz beheben (Service-Icon + Source-Type-Mapping). - NICHT: Infomaniak als PowerOn-Login. - NICHT: Upload-Implementierung in kDrive/Mail. - NICHT: Auto-Refresh fuer Infomaniak (PATs sind langlebig; bei Ablauf legt der User eine neue Connection an). ## Betroffene Module - Gateway: - `modules/datamodels/datamodelUam.py` (Enum erweitert) - `modules/auth/oauthProviderConfig.py` (Infomaniak-Scopes raus) - `modules/auth/tokenManager.py` (Infomaniak-Init + Refresh raus) - `modules/auth/tokenRefreshService.py` (Infomaniak aus Background-Refresh raus) - `modules/connectors/providerInfomaniak/connectorInfomaniak.py` (Adapter unveraendert) - `modules/connectors/connectorResolver.py` (Registry, unveraendert) - `modules/routes/routeSecurityInfomaniak.py` (komplett neu: PAT-Submit-Endpoint) - `modules/routes/routeDataConnections.py` (`connect_service` antwortet bei Infomaniak mit 400 + Hinweis) - `gateway/.env` + `env_dev/int/prod[_forgejo].env` (Service_INFOMANIAK_* raus) - Frontend: - `src/api/connectionApi.ts` (`submitInfomaniakToken` neu) - `src/hooks/useConnections.ts` (`createInfomaniakConnection` + `submitInfomaniakToken` ersetzen `createInfomaniakConnectionAndAuth`, OAuth-Popup-Branch fuer Infomaniak entfernt) - `src/pages/basedata/ConnectionsPage.tsx` (PAT-Modal) - `src/components/UnifiedDataBar/SourcesTab.tsx` (unveraendert seit Phase 1) - Doku: - `wiki/d-guides/infomaniak-oauth-setup.md` -> geloescht - `wiki/d-guides/infomaniak-token-setup.md` -> neu - DB-Migration: nein. ## Entscheidungen | Datum | Entscheidung | Begruendung | |------------|--------------|-------------| | 2026-04-26 | Self-contained Connector (httpx im Modul, kein eigener Service) | Folgt Google/MSFT-Pattern, nicht ClickUp-Pattern | | 2026-04-26 | Nur DATA_CONNECTION, kein Login | User explicitly: "wir benoetigen nur den userconnection auth" | | 2026-04-26 | `kdrive` + `mail` als Service-Namen | Konsistent mit Infomaniak-Branding | | 2026-04-28 | OAuth raus, Personal Access Token rein | Infomaniak's `/authorize` unterstuetzt fuer Datenzugriff keine Scopes; nur PATs liefern brauchbare Bearer-Tokens (`error=invalid_scope` bei OAuth-Versuch) | | 2026-04-28 | Token-Validierung gegen `/1/profile` | Leichter Endpoint, liefert User-Identity (`id`, `login`, `email`) zur Anzeige im FE; gibt 401 bei ungueltigem PAT | | 2026-04-28 | `/1/profile` raus, Validierung gegen `/2/drive` + `/1/mail` | Profile braucht zusaetzlich Scope `user_info`; mit Drive- + Mail-Probes verifizieren wir nur die fuer Adapter benoetigten Scopes | | 2026-04-28 | `/1/mail` raus, Validierung gegen `calendar.infomaniak.com` + `/2/drive` | `/1/mail` existiert nicht (404 nginx); `mail.infomaniak.com/api/mail` redirected zu OAuth (302). Calendar-PIM-Endpoint funktioniert mit PAT und liefert Identity (account_id/user_id/name) gleich mit. | | 2026-04-28 | `account_id` in `UserConnection.externalId` persistieren (zurueckgenommen 2026-04-28 abends) | War als Cache-Optimierung gedacht, hat aber zwei Concerns vermischt: (1) UI-Anzeige der Connection und (2) Adapter-Vertragspartner. Existierende Connections konnten dadurch mit einem Token-Fingerprint statt account_id korrumpiert werden, was im kDrive-Browse zu 422 fuehrte. | | 2026-04-28 | `KdriveAdapter` resolvt `account_id` zur Laufzeit selbst (`_ensureAccountId`) ueber `resolveOwnerIdentity()` | Saubere Trennung: `externalId` ist nur UI-State, der Adapter ist self-contained und braucht keine Connection-Felder zu lesen. Damit gibt es keinen Migrationspfad und keine Korruption mehr -- der Adapter heilt sich automatisch. Cache auf Adapter-Instanz vermeidet wiederholte API-Calls innerhalb desselben Requests. | | 2026-04-28 | Identity-Resolver (Calendar -> Contacts) zentral in `resolveOwnerIdentity()` | Beide PIM-Endpoints liefern dieselbe Owner-Struktur (`user_id`/`account_id`/`name`); ein gemeinsamer Resolver verhindert, dass Submit und Adapter divergieren. Die Sequenz Calendar -> Contacts deckt alle realistischen Setups ab (mindestens einer der beiden Scopes muss auf der PAT sein, damit kDrive ueberhaupt nutzbar ist). | | 2026-04-29 | Calendar-Events ueber `/api/pim/event?calendar_id=...` (nicht `/calendar/{id}/event`) | Live-Test 2026-04-29: nested Route 302 zu OAuth, flat Route 200. Die offizielle PAT-faehige Route fordert `from`/`to` als Y-m-d H:i:s mit max-3-Monats-Range -- Adapter waehlt fixes 90-Tage-Window. | | 2026-04-29 | `.vcf`-Download per Hand synthetisieren (`_renderInfomaniakVcard`) | Alle Contacts-Detail-/Export-Endpoints sind nicht PAT-faehig (500 bzw. 302 OAuth). Wir holen das Listing mit `with=emails,phones,addresses,details` (PAT-faehig) und rendern vCard 3.0 selbst -- konsistent mit MSFT/Google-Contacts-Adapter, der dieselbe Synthese betreibt. | | 2026-04-28 | MailAdapter und ContactAdapter NICHT registrieren bis Endpoint gefunden | 302-Redirects zu OAuth wuerden im UDB als kaputter Service erscheinen; lieber gar nicht zeigen. | | 2026-04-28 | ContactAdapter aktivieren via `contacts.infomaniak.com/api/pim/addressbook` | Nachgereichter curl-Test zeigte 200 + JSON mit derselben Struktur wie Calendar (`addressbooks[].id/name/account_id/...`). Singular-Pfad funktioniert (`/contact` und `/contacts` reden weiter mit OAuth). Adapter ist 1:1 analog `CalendarAdapter`, nur mit `addressbook` statt `calendar` und `.vcf` statt `.ics`. | | 2026-04-28 | `expiresAt = now + 10y` fuer PATs | Analog ClickUp, sonst markiert `getTokenStatusForConnection` die Connection als "none" | | 2026-04-28 | Connection up-front, dann PAT-Submit (statt Submit-erst-dann-erstellen) | Nutzt vorhandenen `POST /api/connections/`-Pfad ohne Sonderbehandlung; Cancel rollback via DELETE auf Modal-Schliessen | ## Umsetzungs-Checkliste - [x] AuthAuthority-Enum erweitert - [x] InfomaniakConnector + KdriveAdapter (resolvt `account_id` zur Laufzeit ueber `resolveOwnerIdentity()`) + CalendarAdapter + ContactAdapter - [x] `resolveOwnerIdentity()` als gemeinsamer Identity-Helper im Connector-Modul (Calendar -> Contacts Fallback, raised `InfomaniakIdentityError` bei totalem Fehlschlag) - [x] ConnectorResolver-Registry (alle Adapter werden uniform mit `accessToken` konstruiert; keine Sonderbehandlung in `getServiceAdapter`) - [x] Setup-Guide (PAT-basiert, Calendar + Contacts als zusaetzliche aktive Services) - [x] PAT-Submit-Endpoint `POST /api/infomaniak/connections/{id}/token` (Pre-Flight: `resolveOwnerIdentity` + Drive-Probe mit resolvter `account_id` -- harter 200-Pfad, kein 422-Tolerance-Hack mehr) - [x] OAuth-Routen entfernt - [x] Infomaniak-Refresh aus tokenManager + tokenRefreshService entfernt - [x] Infomaniak-Scopes aus `oauthProviderConfig` entfernt - [x] `Service_INFOMANIAK_*` aus allen .env-Dateien entfernt - [x] DataConnections-Dispatch (Infomaniak-Branch in `connect_service` -> 400) - [x] FE-Hook umgebaut (`createInfomaniakConnection` + `submitInfomaniakToken`) - [x] FE-OAuth-Popup-Branch fuer Infomaniak entfernt - [x] ConnectionsPage-PAT-Modal - [x] UDB-Integration (Authority-Icon, Service-Icons, Source-Colors) - [x] ClickUp-UDB-Fix - [ ] Manueller End-to-End-Test (Token im Manager erstellen, in Modal pasten, kDrive browsen, Calendar browsen, Contacts browsen, Datei + .ics + .vcf downloaden) - [x] Mail-API-Pfad verifiziert -- alle 7 erschoepfend getesteten Pfade scheitern (siehe "Blockiert" oben). Adapter pausiert bis Infomaniak einen PAT-faehigen Endpoint freischaltet. - [ ] `wiki/b-reference/connectors.md` (falls vorhanden) ergaenzen ## Akzeptanzkriterien | # | Kriterium (Given-When-Then) | Prio | |---|-----------------------------|------| | 1 | Given Nutzer auf ConnectionsPage, When er auf "Infomaniak" klickt, Then oeffnet sich ein Modal mit Token-Eingabe und Deeplink zu `manager.infomaniak.com/v3/ng/accounts/token/list` | must | | 2 | Given gueltiger PAT mit Scopes `drive`+(`workspace:calendar` ODER `workspace:contact`) im Modal, When Submit, Then ist die UserConnection `ACTIVE`, Token gespeichert, `externalId=account_id`, `externalUsername=Owner-Anzeigename aus PIM` | must | | 3 | Given ungueltiger PAT oder fehlender `drive`-Scope ODER weder `workspace:calendar` noch `workspace:contact`, When Submit, Then bleibt Connection PENDING, Modal zeigt 400-Detail mit Scope-Hinweis | must | | 4 | Given aktive Connection, When im UDB die Authority expandiert wird, Then werden Services `kdrive`, `calendar` und `contact` mit Icons angezeigt | must | | 4b | Given aktive Connection (auch eine, deren `externalId` aus historischen Gruenden NICHT `account_id` ist), When im UDB `kdrive` expandiert wird, Then erscheinen Drives ohne `422`-Fehler (Adapter resolvt `account_id` zur Laufzeit ueber `resolveOwnerIdentity`) | must | | 4c | Given aktive Connection, When im UDB `calendar` expandiert wird, Then erscheinen Kalender, dann Events; Event-Download liefert `.ics` | must | | 4d | Given aktive Connection, When im UDB `contact` expandiert wird, Then erscheinen Adressbuecher, dann Kontakte; Kontakt-Download liefert `.vcf` | must | | 5 | Given Modal offen, When User auf Cancel/X klickt, Then wird die soeben erstellte PENDING-Connection wieder geloescht | should | | 6 | Given ClickUp-Service-Node im UDB, Then ist das Service-Icon das Klemmbrett (Fix) | should | ## Testplan | ID | AC | Art | Automatisiert | Repo-Pfad | Status | |----|----|-----|---------------|-----------|--------| | T1 | 1-3 | manual | nein | UI: ConnectionsPage Modal | pending | | T2 | 4 | manual | nein | UI: UDB SourcesTab | pending | | T3 | 5 | manual | nein | UI: ConnectionsPage Modal Cancel | pending | | T4 | 6 | manual | nein | UI: UDB SourcesTab | pending | ## Links - PR: tbd - Setup-Guide: `wiki/d-guides/infomaniak-token-setup.md` ## Abschluss - [ ] `b-reference/` aktualisiert - [ ] `TOPICS.md` aktualisiert - [ ] Dokument nach `z-archive/` verschoben