21 KiB
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/mail404 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/mail302 -> OAuth mail.infomaniak.com/api/pim/mailbox302 -> OAuth mail.infomaniak.com/api/pim/folder302 -> OAuth Konsequenz: PowerOn kann mit dem heutigen PAT-Mechanismus die Infomaniak-Mailbox nicht ansprechen. Der Scope
workspace:mailbleibt 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:
- FE: User klickt "Infomaniak" ->
POST /api/connections/mitauthority=infomaniaklegt PENDING-Connection an. - FE: Modal oeffnet sich mit Schritt-fuer-Schritt-Anleitung + Link
https://manager.infomaniak.com/v3/ng/accounts/token/listund Token-Eingabefeld. - FE: User fuegt PAT ein ->
POST /api/infomaniak/connections/{id}/token. - BE: Validiert PAT in zwei Schritten:
resolveOwnerIdentity()ruft PIM Calendar (Scopeworkspace:calendar) auf, faellt bei leerer Owner-Liste auf PIM Contacts zurueck (Scopeworkspace: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/driveaccount_idzwingend braucht und kein Drive-Endpoint die Identity ohneuser_info-Scope rausgibt).GET /2/drive?account_id={resolved}-- erwartet 200, andernfalls 400 (drive-Scope fehlt) bzw. 502 (Infomaniak antwortet unerwartet). Mit der vorab resolvtenaccount_idist der Probe deterministisch und braucht keine 422-Tolerance mehr. Token landet alsTokenmit 10-Jahres-Horizont (tokenStatus-Anzeige, analog ClickUp).externalUsernameundexternalIdwerden fuer die UI-Anzeige gesetzt;externalIdist nicht Vertragspartner des kDrive-Adapters (siehe "Kritische Details").
- 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}. Wichtig: das naheliegende/2/drive?account_id=...-Listing ist filtered auf Drive-Manager-Admins (account_admin: true) und liefert fuer normale kSuite-Member (role: 'user') sauber200 [], obwohl sie das Drive lesen koennen. Der korrekte User-zentrische Endpoint istGET /2/drive/init?with=drives, der ALLE Drives des PAT-Owners zurueckgibt -- inklusiveid,name,account_id,role. Der Adapter cached die Liste auf der Instanz (_ensureDrives); ein Browse zahlt also pro Request maximal eineninit-Call./2/drive/init?with=drivesbraucht NUR dendrive- Scope, keinaccountsoderuser_info. - 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
- Listing:
- 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.browseunterscheidet die Tiefe via Segment-Count.
- Listing braucht zwingend
- kDrive (
- Multi-Host:
_infomaniakGet/_infomaniakDownloadakzeptieren ein optionalesbaseUrl, sodass Calendar gegencalendar.infomaniak.com, Contacts gegencontacts.infomaniak.comund kDrive gegenapi.infomaniak.comlaufen. - 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 > 0undisinstance(account_id, int)).listAccessibleDrives(token)-> Liste aller Drives, die der PAT-Owner sehen kann (egal mit welcher Rolle). Ein einzelnerGET /2/drive/init?with=drives-Call, vomKdriveAdapterbenutzt statt des admin-only/2/drive?account_id=...Listings. Beide raisenInfomaniakIdentityErrormit einer scope-spezifischen Fehlermeldung, sodass der Submit-Endpoint pro fehlendem Scope eine eigene 400-Message zurueckgeben kann.
externalIdist UI-State, kein Adapter-Vertragspartner: Submit speichertexternalId = str(kSuiteAccountId)fuer die ConnectionsPage (Anzeige + Konsistenz mit anderen Providern), aber der KdriveAdapter liest ihn nie -- er fragt zur Laufzeit/2/drive/init?with=drives.- Antwort-Wrapping: Erfolgreiche Responses sind als
{result: 'success', data: ...}gewrappt --_unwrapData()normalisiert. - Token-Validation im Submit-Endpoint: zwei harte 200-Schritte,
jeder probet genau einen Scope:
listAccessibleDrives-> probetdrive-Scope und stellt sicher, dass mindestens ein erreichbarer kDrive existiert.resolveOwnerIdentity-> probetworkspace:calendar/workspace:contact-Scope (mindestens einer noetig). Jeder Schritt liefert eine eigene 400-Message, die exakt den fehlenden Scope nennt.
- Token-Persistenz:
expiresAt = now + 10*365*24*3600,tokenRefresh = None;getTokenStatusForConnectionzeigt damitactive, 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_serviceantwortet bei Infomaniak mit 400 + Hinweis)gateway/.env+env_dev/int/prod[_forgejo].env(Service_INFOMANIAK_* raus)
- Frontend:
src/api/connectionApi.ts(submitInfomaniakTokenneu)src/hooks/useConnections.ts(createInfomaniakConnection+submitInfomaniakTokenersetzencreateInfomaniakConnectionAndAuth, 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-> geloeschtwiki/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-29 | KdriveAdapter discoverte Drives ueber /2/drive/init?with=drives statt /2/drive?account_id=X |
Live-Beweis vom User: Drive 2980592 existiert in account 1696919 mit Files (PDF + 2 Folders), /2/drive?account_id=1696919 antwortet trotzdem 200 [], /2/drive/2980592/files zeigt alle Files. Diagnose: /2/drive ist Drive-Manager-Admin-Sicht (filtered auf account_admin: true), normaler kSuite-Member (role: 'user') sieht dort nichts -- ist nicht Vendor-Bug, sondern Spec. Loesung gefunden via Endpoint-Probing: /2/drive/init?with=drives ist die User-zentrische Drive-Liste, die ALLE zugaenglichen Drives unabhaengig von der Admin-Rolle liefert (verifiziert mit PAT der accounts-Scope explizit nicht hat -> 200, alle Drives drin). Damit wird der zwischenzeitlich eingefuehrte accounts-Scope-Workaround wieder entfernt -- er war eine Sackgasse, weil /1/accounts die Manager-Organizations listet (1 pro User in den meisten Faellen), nicht die Drive-Accounts. Der init-Endpoint braucht nur den drive-Scope, der bereits im PAT-Standard-Setup ist; keine Token-Rotation noetig. |
| 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
- AuthAuthority-Enum erweitert
- InfomaniakConnector + KdriveAdapter (discovert Drives zur
Laufzeit ueber
listAccessibleDrives()->/2/drive/init?with=drives, cached die Liste auf der Adapter-Instanz) + CalendarAdapter + ContactAdapter resolveOwnerIdentity()als reiner UI-Identity-Helper (Display-Name + kSuite-account_id fuer das Connection-Label)listAccessibleDrives()als Drive-Discovery-Helper (/2/drive/init?with=drives,drive-Scope-Probe, raisedInfomaniakIdentityErrormit klarer Scope-Message)- ConnectorResolver-Registry (alle Adapter werden uniform mit
accessTokenkonstruiert; keine Sonderbehandlung ingetServiceAdapter) - Setup-Guide (PAT-basiert, Calendar + Contacts als zusaetzliche aktive Services)
- PAT-Submit-Endpoint
POST /api/infomaniak/connections/{id}/token(Pre-Flight in 2 Schritten:listAccessibleDrives(drive- Scope),resolveOwnerIdentity(workspace:calendar/workspace:contact) -- jeder Schritt mit eigener 400-Message) - OAuth-Routen entfernt
- Infomaniak-Refresh aus tokenManager + tokenRefreshService entfernt
- Infomaniak-Scopes aus
oauthProviderConfigentfernt Service_INFOMANIAK_*aus allen .env-Dateien entfernt- DataConnections-Dispatch (Infomaniak-Branch in
connect_service-> 400) - FE-Hook umgebaut (
createInfomaniakConnection+submitInfomaniakToken) - FE-OAuth-Popup-Branch fuer Infomaniak entfernt
- ConnectionsPage-PAT-Modal
- UDB-Integration (Authority-Icon, Service-Icons, Source-Colors)
- 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)
- 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=kSuiteAccountId, externalUsername=Owner-Anzeigename aus PIM |
must |
| 3 | Given ungueltiger PAT oder fehlender Scope (drive ODER weder workspace:calendar noch workspace:contact), When Submit, Then bleibt Connection PENDING, Modal zeigt 400-Detail das den fehlenden Scope namentlich nennt |
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, When im UDB kdrive expandiert wird, Then erscheinen alle Drives die der User sehen kann -- auch wenn er dort nur role: 'user' hat (Adapter discovert ueber /2/drive/init?with=drives, nicht ueber das admin-only /2/drive?account_id=... Listing) |
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/aktualisiertTOPICS.mdaktualisiert- Dokument nach
z-archive/verschoben