kdrive fix
This commit is contained in:
parent
cc7aa1899d
commit
53f28d47a1
10 changed files with 639 additions and 173 deletions
|
|
@ -67,7 +67,8 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en).
|
|||
| Testing-Strategie | d-guides/testing-strategy.md | Testpyramide, AC-Format, Test-Pfade |
|
||||
| Dev-Setup | d-guides/dev-setup.md | Lokale Umgebung starten |
|
||||
| Secrets-Verschluesselung | d-guides/encrypt-env-secrets.md | Env-Dateien verschluesseln |
|
||||
| Google OAuth | d-guides/google-oauth-setup.md | OAuth Auth/Data Apps einrichten |
|
||||
| Google OAuth | d-guides/google-oauth-setup.md | OAuth Auth/Data Apps einrichten (inkl. Calendar/Contacts-Scopes + Reconnect-Hinweis) |
|
||||
| Infomaniak Token-Setup | d-guides/infomaniak-token-setup.md | Personal Access Token im Infomaniak-Manager fuer kDrive/Calendar/Contacts erzeugen |
|
||||
| Security-Migration | d-guides/security-migration-guide.md | JWT Cookie Migration |
|
||||
| Doc-Sync Cursor-Rule | d-guides/cursor-doc-sync.md | Installation, Regel-Quelle `doc-sync.mdc`, Doku-Workflow |
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- status: canonical -->
|
||||
<!-- lastReviewed: 2026-04-28 -->
|
||||
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification); gateway/modules/features/teamsbot/service.py (Hybrid Agent Escalation 2026-04-24); Typed Action Architecture Phasen 1-5; featureDataAgent domain hints hook 2026-04-27; central parameterValidation + DatabaseQueryError 2026-04-28 -->
|
||||
<!-- lastReviewed: 2026-04-28 (OpenAI temperature handling for GPT-5.x / o-series) -->
|
||||
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification); gateway/modules/features/teamsbot/service.py (Hybrid Agent Escalation 2026-04-24); Typed Action Architecture Phasen 1-5; featureDataAgent domain hints hook 2026-04-27; central parameterValidation + DatabaseQueryError 2026-04-28; OpenAI temperature contract for GPT-5.x / o-series 2026-04-28 -->
|
||||
|
||||
# AI Agent & Knowledge Store
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ Zusätzlich zu den unten genannten **Kern-Tools** existieren **dynamische Tools*
|
|||
| Plugin-Modul | Typische Rolle |
|
||||
|--------------|----------------|
|
||||
| `aicorePluginAnthropic.py` | Claude-Modelle |
|
||||
| `aicorePluginOpenai.py` | GPT, Embeddings, Bild |
|
||||
| `aicorePluginOpenai.py` | GPT, Embeddings, Bild — modellabhaengiges Payload-Tuning: GPT-5.x und o-Serie (o1/o3/o4) sind Reasoning-Modelle und akzeptieren weder `max_tokens` (-> immer `max_completion_tokens`) noch ein custom `temperature` (-> Feld bei diesen Modellen weggelassen, OpenAI erzwingt sonst HTTP 400 `unsupported_value`) |
|
||||
| `aicorePluginMistral.py` | Mistral Chat / Embed |
|
||||
| `aicorePluginPerplexity.py` | Sonar / Recherche |
|
||||
| `aicorePluginTavily.py` | Web-Suche |
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<!-- status: canonical -->
|
||||
<!-- lastReviewed: 2026-04-24 -->
|
||||
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) + Typed Action Architecture Phasen 1-5 -->
|
||||
<!-- lastReviewed: 2026-04-28 -->
|
||||
<!-- verifiedAgainst: gateway (codebase audit 2026-04-07, post Automation Unification) + Typed Action Architecture Phasen 1-5 + Infomaniak-Connector (2026-04-28) + MSFT/Google Calendar+Contacts + Reconnect (2026-04-28) -->
|
||||
|
||||
# Gateway -- Architektur
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ Unter `gateway/modules/` (Kontext-Audit):
|
|||
|-------|-------|
|
||||
| `aicore/` | Model-Registry, Model-Selector, Provider-Plugins (Anthropic, OpenAI, Mistral, Perplexity, Tavily, PrivateLLM) |
|
||||
| `auth/` | Authentifizierung, CSRF, Token-Refresh-Middleware, JWT |
|
||||
| `connectors/` | DB-Connector (PostgreSQL), Provider-Subpakete (Microsoft, Google, ClickUp, FTP), Ticket/Messaging/Geo-Konnektoren |
|
||||
| `connectors/` | DB-Connector (PostgreSQL), Provider-Subpakete (Microsoft, Google, ClickUp, FTP, Infomaniak), Ticket/Messaging/Geo-Konnektoren. Pro Provider registrieren ServiceAdapter (`OutlookAdapter`, `OneDriveAdapter`, `SharepointAdapter`, `TeamsAdapter`, `CalendarAdapter`, `ContactsAdapter`, `GmailAdapter`, `DriveAdapter`, `KdriveAdapter`, …) die UDB-Services. Adapter-Registry pro Connector ist `_SERVICE_MAP` |
|
||||
| `datamodels/` | Pydantic-Datenmodelle (u. a. Ai, Billing, Chat, Content, Files, Knowledge, Rbac, Subscription, UiLanguage, Workflow) |
|
||||
| `features/` | Feature-Module (autonome Domänen): workspace, graphicalEditor, chatbot, commcoach, neutralization, realEstate, trustee, teamsbot |
|
||||
| `interfaces/` | DB-Interfaces (App, Billing, Chat, Knowledge, Management, Subscription), AI-Objects, RBAC, Features, Messaging |
|
||||
|
|
|
|||
|
|
@ -1,118 +1,296 @@
|
|||
<!-- status: build -->
|
||||
<!-- started: 2026-04-26 -->
|
||||
<!-- pivoted: 2026-04-28 (OAuth -> Personal Access Token) -->
|
||||
<!-- pivoted: 2026-04-28 (Mail-Endpoint nicht PAT-faehig -> Calendar als zweiter aktiver Service) -->
|
||||
<!-- pivoted: 2026-04-28 (Contacts-PIM-Endpoint funktioniert -> Contacts als dritter aktiver Service) -->
|
||||
<!-- pivoted: 2026-04-29 (kDrive findet Standalone/Free-tier-Drives nicht ueber kSuite-account_id -> `accounts`-Scope + `/1/accounts`-Resolver) -->
|
||||
<!-- component: gateway, frontend-nyla -->
|
||||
|
||||
# Infomaniak Connector (kDrive + Mail) + UDB-Integration
|
||||
# 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 OAuth gegen `login.infomaniak.com` durchfuehrt
|
||||
und Daten aus zwei Services bereitstellt:
|
||||
neuer `InfomaniakConnector`, der Daten aus den Infomaniak-Services
|
||||
bereitstellt. Heute aktiv:
|
||||
|
||||
- **kDrive** (Pendant zu OneDrive / Google Drive)
|
||||
- **Mail** (Pendant zu Outlook / Gmail)
|
||||
- **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 -- entsprechend gibt
|
||||
es nur `_FLOW_CONNECT`, kein `_FLOW_LOGIN`.
|
||||
Infomaniak ist explizit **kein** Login-Provider fuer PowerOn.
|
||||
|
||||
Zusatzlich wurden zwei kleine Inkonsistenzen in der ClickUp-UDB-Anzeige
|
||||
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
|
||||
|
||||
- **Refresh-Token-Persistenz**: Infomaniak rotiert Refresh-Tokens nicht in jedem
|
||||
Token-Response; falls `refresh_token` im Callback fehlt, wird aus dem letzten
|
||||
gespeicherten Token rekonstruiert (analog Google).
|
||||
- **API-Pfad-Konvention**: Drei-Ebenen-Hierarchie kDrive (`/{driveId}/{fileId}`)
|
||||
und vier-Ebenen Mail (`/{mailboxId}/{folderId}/{uid}`); `ServiceAdapter.browse`
|
||||
unterscheidet die Tiefe via Segment-Count.
|
||||
- **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.
|
||||
- **Authority-Filter** in `tokenRefreshService` muss `INFOMANIAK` enthalten,
|
||||
sonst werden Token nie proaktiv refreshed.
|
||||
- **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" (analog ClickUp).
|
||||
- Ziel: Refresh-Token-Lifecycle vollautomatisch (kein User-Reconnect noetig).
|
||||
- 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 (`User`-Erstellung via Infomaniak-OAuth).
|
||||
- NICHT: Upload-Implementierung in kDrive/Mail (gibt nur Browse + Download).
|
||||
- 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)
|
||||
- `modules/auth/oauthProviderConfig.py` (Scopes)
|
||||
- `modules/auth/tokenManager.py` (Refresh)
|
||||
- `modules/auth/tokenRefreshService.py` (Background-Refresh)
|
||||
- `modules/connectors/providerInfomaniak/connectorInfomaniak.py` (neu)
|
||||
- `modules/connectors/connectorResolver.py` (Registry)
|
||||
- `modules/routes/routeSecurityInfomaniak.py` (neu)
|
||||
- `modules/routes/routeDataConnections.py` (Dispatch)
|
||||
- `app.py` (Router-Mount)
|
||||
- `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` (Authority-Typ)
|
||||
- `src/hooks/useConnections.ts` (Popup-Handler)
|
||||
- `src/pages/basedata/ConnectionsPage.tsx` (Button)
|
||||
- `src/components/UnifiedDataBar/SourcesTab.tsx` (Icons + Colors + Mapping, ClickUp-Fix)
|
||||
- DB-Migration: nein (nur Enum-Wert, alle Tabellen abwaertskompatibel).
|
||||
- Andere: keine.
|
||||
- `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 (das delegiert an `ClickupService`) |
|
||||
| 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 | Kurz, konsistent mit Infomaniak-Branding (kDrive heisst offiziell so) |
|
||||
| 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] Scopes definiert (`user_info kdrive mail`)
|
||||
- [x] InfomaniakConnector + KdriveAdapter + MailAdapter
|
||||
- [x] ConnectorResolver-Registry
|
||||
- [x] OAuth-Route `/api/infomaniak/auth/connect[/callback]`
|
||||
- [x] Token-Refresh (`refreshInfomaniakToken` + Background-Service)
|
||||
- [x] DataConnections-Dispatch (`authority_map`, Labels, `connect_service`)
|
||||
- [x] App-Router-Mount
|
||||
- [x] Frontend-Typen
|
||||
- [x] Hook `createInfomaniakConnectionAndAuth` + Event-Listener
|
||||
- [x] ConnectionsPage-Button
|
||||
- [x] UDB-Integration (Authority-Icon, Service-Icons, Source-Colors, Mapping)
|
||||
- [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
|
||||
- [x] Setup-Guide `wiki/d-guides/infomaniak-oauth-setup.md`
|
||||
- [ ] Manueller End-to-End-Test (ClientID/Secret im Manager registrieren, Verbinden, kDrive browsen, Mail browsen, Datei downloaden)
|
||||
- [ ] 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 Infomaniak-Credentials in `APP_CONFIG`, When Nutzer "Infomaniak" auf ConnectionsPage klickt, Then oeffnet sich der OAuth-Popup auf `login.infomaniak.com/authorize` | must |
|
||||
| 2 | Given erfolgreicher OAuth-Callback, Then ist die UserConnection `ACTIVE`, Token gespeichert (`tokenAccess`+`tokenRefresh`), `externalId/Email` gesetzt | must |
|
||||
| 3 | Given aktive Connection, When im UDB die Authority expandiert wird, Then werden Services `kdrive` und `mail` mit Icons angezeigt | must |
|
||||
| 4 | Given Token vor Ablauf < 5 min, When Background-Refresh laeuft, Then wird `_refresh_infomaniak_token` aufgerufen und Token erneuert | must |
|
||||
| 5 | Given ClickUp-Service-Node im UDB, Then ist das Service-Icon das Klemmbrett (Fix) | should |
|
||||
| 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-2 | manual | nein | UI: ConnectionsPage | pending |
|
||||
| T2 | 3 | manual | nein | UI: UDB SourcesTab | pending |
|
||||
| T3 | 4 | unit | spaeter | gateway/tests/auth/test_tokenManager.py | pending |
|
||||
| T4 | 5 | manual | nein | UI: UDB SourcesTab | pending |
|
||||
| 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-oauth-setup.md`
|
||||
- Setup-Guide: `wiki/d-guides/infomaniak-token-setup.md`
|
||||
|
||||
## Abschluss
|
||||
|
||||
|
|
|
|||
189
c-work/2-build/2026-04-msft-google-calendar-contacts.md
Normal file
189
c-work/2-build/2026-04-msft-google-calendar-contacts.md
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
<!-- status: build -->
|
||||
<!-- started: 2026-04-28 -->
|
||||
<!-- component: gateway, frontend-nyla -->
|
||||
|
||||
# MSFT- und Google-Connector: CalendarAdapter + ContactsAdapter, plus Reconnect-Button
|
||||
|
||||
## Beschreibung und Kontext
|
||||
|
||||
Der Infomaniak-Connector liefert seit dem 2026-04-28 die drei Services
|
||||
**kDrive**, **Calendar** und **Contacts**. Die analogen Services fuer Microsoft
|
||||
und Google fehlten bislang in der UDB. Ziel dieses Builds: vier neue
|
||||
ServiceAdapter (MSFT-Calendar, MSFT-Contacts, Google-Calendar,
|
||||
Google-Contacts), so dass die UDB pro Connection einen einheitlichen
|
||||
Service-Pool zeigt und der Agent fuer Calendar/Contacts gegen alle drei
|
||||
Provider gleichermassen arbeiten kann.
|
||||
|
||||
Zusaetzliche Anforderung: ein **Reconnect-Button** im Frontend. Bestehende
|
||||
MSFT-/Google-Connections wurden mit einem kleineren Scope-Set autorisiert.
|
||||
Beim Hinzufuegen neuer Scopes (`Calendars.Read`, `Contacts.Read`,
|
||||
`calendar.readonly`, `contacts.readonly`) liefern die Provider auf einer
|
||||
Re-Authorisierung sonst still **dieselben** alten Tokens, weil sie
|
||||
`include_granted_scopes` (Google) bzw. `prompt=select_account` (MSFT)
|
||||
default-maessig nutzen -- die neuen Scopes wuerden nie das Tokenset
|
||||
erreichen.
|
||||
|
||||
## Architektur
|
||||
|
||||
- `oauthProviderConfig.googleDataScopes` += `calendar.readonly`,
|
||||
`contacts.readonly`
|
||||
- `oauthProviderConfig.msftDataScopes` += `Calendars.Read`, `Contacts.Read`
|
||||
- `connectorMsft.CalendarAdapter` (neu)
|
||||
- Endpoints: `me/calendars`, `me/calendars/{id}/events`
|
||||
- Pagination via `@odata.nextLink` (Helper `_stripGraphBase` aus dem Modul)
|
||||
- Pfad: `""` -> Calendars; `"/{calendarId}"` -> Events
|
||||
- Download: synthetisches RFC-5545 VCALENDAR/VEVENT (Graph hat keinen
|
||||
`$value`-Endpoint fuer Events)
|
||||
- `connectorMsft.ContactsAdapter` (neu)
|
||||
- Endpoints: `me/contactFolders`, `me/contactFolders/{id}/contacts`,
|
||||
plus virtueller Default-Folder `default` -> `me/contacts`
|
||||
- Download: vCard 3.0 (N/FN/ORG/TITLE/EMAIL/TEL/ADR/NOTE)
|
||||
- Helper: `_eventToIcs`, `_contactToVcard`, `_safeFileName`,
|
||||
`_personLabel`, `_icsEscape`, `_icsDateTime` (alle modullokal)
|
||||
- `connectorMsft.MsftConnector._SERVICE_MAP` += `"calendar"` + `"contact"`
|
||||
- `connectorGoogle.CalendarAdapter` (neu)
|
||||
- Endpoints: `users/me/calendarList`,
|
||||
`calendars/{id}/events?singleEvents=true&orderBy=startTime`
|
||||
- Download: `.ics` aus Event-Detail synthetisiert
|
||||
- Search: `events?q=...` per Calendar-ID (Default `primary`)
|
||||
- `connectorGoogle.ContactsAdapter` (neu)
|
||||
- Endpoints: `contactGroups` + virtueller `all` -> `people/me/connections`
|
||||
- Gruppen-Mitglieder via `contactGroups/{id}` -> `memberResourceNames` ->
|
||||
`people:batchGet?resourceNames=...&personFields=...`
|
||||
- Search: `people:searchContacts`
|
||||
- Download: vCard 3.0 aus `personFields=names,emailAddresses,phoneNumbers,
|
||||
organizations,addresses,biographies,memberships`
|
||||
- `connectorGoogle.GoogleConnector._SERVICE_MAP` += `"calendar"` +
|
||||
`"contact"`
|
||||
- `routeFeatureWorkspace` und `routeFeatureGraphicalEditor`
|
||||
Service-Label-/Icon-Maps += `kdrive`, `calendar`, `contact` (so dass die
|
||||
UDB sinnvolle Bezeichnungen anzeigt -- vorher war Infomaniak-kDrive
|
||||
ungelabelt durchgerutscht)
|
||||
|
||||
## Reconnect-Flow
|
||||
|
||||
- `routeDataConnections.connect_service` (`POST /api/connections/{id}/connect`)
|
||||
akzeptiert optionalen Body `{"reauth": true}` und haengt `&reauth=1` an
|
||||
die zurueckgegebene `auth_url`.
|
||||
- `routeSecurityMsft.auth_connect` setzt bei `reauth=1`
|
||||
`prompt=consent` (statt `select_account` / `login`). Das erzwingt den
|
||||
Microsoft-Consent-Screen und liefert die neuen Scopes nach.
|
||||
- `routeSecurityGoogle.auth_connect` droppt bei `reauth=1`
|
||||
`include_granted_scopes=true` (default-`true` macht Google "vergesslich"
|
||||
fuer neu hinzugekommene Scopes -- ohne diesen Drop blieben die neuen
|
||||
Scopes still aussen vor).
|
||||
- ClickUp ignoriert den Param (FastAPI laesst unbekannte Query-Args
|
||||
durchrutschen) -- das ist OK, weil ClickUp keine Scope-Erweiterung hat.
|
||||
- Frontend:
|
||||
- `connectionApi.connectService(id, reauth?)` haengt den Body an.
|
||||
- `useConnections.connectWithPopup(id, reauth?)` reicht durch (auch
|
||||
der separate `useOAuthConnect`-Hook).
|
||||
- `ConnectionsPage` hat ein neues `customAction` `reconnect` mit
|
||||
`FaSyncAlt`-Icon, eigenem `reconnectingConnections`-Set und
|
||||
`visible`-Filter `status === 'active' && authority in {msft, google,
|
||||
clickup}`. Der Refresh-Button bleibt fuer Token-Refresh ohne
|
||||
Re-Consent erhalten.
|
||||
|
||||
## Fokus und kritische Details
|
||||
|
||||
- **Pagination**: MSFT Calendar/Contacts paginieren bei `$top<=100`; wir
|
||||
ziehen `@odata.nextLink` durch wie OutlookAdapter und stoppen bei
|
||||
`effectiveLimit`.
|
||||
- **iCal-Bauen**: Wir ueberlassen das nicht einer Library, weil RFC-5545
|
||||
fuer einzelne VEVENT-Faelle trivial ist und der Overhead einer
|
||||
Dependency (`icalendar`) nicht gerechtfertigt ist. Escape und
|
||||
DTSTAMP/DTSTART/DTEND in UTC.
|
||||
- **vCard-Bauen**: vCard 3.0 weil universell von Outlook/Google/macOS
|
||||
Contacts akzeptiert. ADR-Felder: leere Felder werden als leere Slots
|
||||
gerendert (nicht weggelassen), damit das Format gueltig bleibt.
|
||||
- **Default-Contacts-Folder**: Outlook hat einen unsichtbaren
|
||||
Default-Ordner -- den simulieren wir mit dem Pseudo-Folder `default`,
|
||||
der intern auf `me/contacts` mappt. Ohne diesen Eintrag waere der
|
||||
Default-Ordner aus der UDB nicht erreichbar.
|
||||
- **People-API Group-Resolution**: `contactGroups/{id}` liefert keine
|
||||
vollstaendigen Person-Records, nur `memberResourceNames`. Wir batchen
|
||||
diese in 200er-Chunks via `people:batchGet`. Limit fuer Resource-Names
|
||||
pro Batch ist 200 (Google API spec).
|
||||
- **Reconnect ohne Token-Vernichtung**: Wir loeschen die alte Token nicht
|
||||
-- der Auth-Callback ueberschreibt sie atomar nach erfolgreichem
|
||||
Code-Exchange. Bei abgebrochenem Reconnect bleibt der bisherige Token
|
||||
gueltig.
|
||||
|
||||
## Entscheidungen
|
||||
|
||||
- **Calendar/Contacts read-only:** Wir nehmen `Calendars.Read` /
|
||||
`calendar.readonly` / `Contacts.Read` / `contacts.readonly` -- nicht
|
||||
ReadWrite. Der UDB-Use-Case ist ausschliesslich Lesen + Suche +
|
||||
Download. Schreibrechte werden bei einem konkreten Bedarf separat
|
||||
diskutiert (mehr Scopes erhoehen Consent-Aufwand und Risk-Profile
|
||||
unnoetig).
|
||||
- **Reconnect statt eigenem Endpoint:** Wir spendieren keinen separaten
|
||||
`/reconnect`-Endpoint, sondern einen Body-Param am bestehenden
|
||||
`/connect`. Vorteil: ein einziger Authorisierungspfad, identische
|
||||
Callback-Behandlung, kein doppeltes State-Management.
|
||||
- **`include_granted_scopes` Drop bei Google:** Default `true` ist
|
||||
freundlich fuer Token-Reuse, aber die offizielle Google-Doku warnt
|
||||
ausdruecklich, dass es bei Scope-Erweiterung ueberraschend Tokens
|
||||
ohne neue Scopes ausstellt. `false` bei Reconnect ist die robustere
|
||||
Wahl.
|
||||
- **`prompt=consent` bei MSFT:** Microsoft erkennt zusaetzliche Scopes
|
||||
zwar normalerweise selbststaendig und zeigt einen Consent-Screen,
|
||||
aber das ist nur best-effort. Mit `prompt=consent` ist es
|
||||
garantiert.
|
||||
- **vCard 3.0 statt 4.0:** 4.0 hat noch immer schlechte Outlook-
|
||||
Kompatibilitaet beim Import; 3.0 ist der Common-Denominator.
|
||||
- **`.ics` per Hand bauen statt `iCalendar`-Lib:** Eine externe
|
||||
Dependency lohnt fuer einen einzigen VEVENT je Download nicht.
|
||||
|
||||
## Umsetzungs-Checkliste
|
||||
|
||||
- [x] `oauthProviderConfig.py`: `googleDataScopes` + `msftDataScopes`
|
||||
ergaenzt
|
||||
- [x] `connectorMsft.CalendarAdapter` (browse/download/search)
|
||||
- [x] `connectorMsft.ContactsAdapter` (browse/download/search)
|
||||
- [x] `connectorMsft._SERVICE_MAP`: `calendar`, `contact` registriert
|
||||
- [x] `connectorMsft._eventToIcs`, `_contactToVcard`, Helpers
|
||||
- [x] `connectorGoogle.CalendarAdapter` (browse/download/search)
|
||||
- [x] `connectorGoogle.ContactsAdapter` (browse/download/search inkl.
|
||||
Group-Resolution)
|
||||
- [x] `connectorGoogle._SERVICE_MAP`: `calendar`, `contact` registriert
|
||||
- [x] `connectorGoogle._googleEventToIcs`, `_googlePersonToVcard`,
|
||||
Helpers
|
||||
- [x] `routeFeatureWorkspace` Service-Label/Icon-Maps erweitert
|
||||
- [x] `routeFeatureGraphicalEditor` Service-Label/Icon-Maps erweitert
|
||||
- [x] `routeDataConnections.connect_service`: optionaler `reauth`-Body
|
||||
- [x] `routeSecurityMsft.auth_connect`: `reauth` -> `prompt=consent`
|
||||
- [x] `routeSecurityGoogle.auth_connect`: `reauth` -> drop
|
||||
`include_granted_scopes`
|
||||
- [x] Frontend `connectionApi.connectService(id, reauth?)`
|
||||
- [x] Frontend `useConnections.connectWithPopup(id, reauth?)` (beide
|
||||
Hooks)
|
||||
- [x] Frontend `ConnectionsPage`: `Reconnect`-Button mit
|
||||
`FaSyncAlt`-Icon und eigenem Loading-Set
|
||||
- [ ] **Manuelle Verifikation:** existierende MSFT-Connection neu
|
||||
durchklicken (Reconnect), Calendar+Contacts in der UDB
|
||||
verifizieren; existierende Google-Connection genauso
|
||||
- [ ] **Manuelle Verifikation:** Calendar-Download liefert eine
|
||||
importierbare `.ics` (Outlook-Test); Contacts-Download liefert eine
|
||||
importierbare `.vcf`
|
||||
- [ ] Doc-Sync: `wiki/b-reference/auth-and-rbac.md`,
|
||||
`wiki/b-reference/connectors.md` aktualisieren (neue Scopes,
|
||||
Reconnect-Pfad), Topics-Liste pruefen
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
1. Bestehende MSFT-/Google-Connection -> Reconnect-Button -> nach
|
||||
Consent erscheinen `Calendar` und `Contacts` als zusaetzliche
|
||||
Service-Knoten in der UDB.
|
||||
2. Calendar-Browse listet die User-Calendars; Klick auf einen
|
||||
Calendar listet die juengsten Events; Download eines Events
|
||||
liefert eine `.ics` mit DTSTART/DTEND/SUMMARY/LOCATION.
|
||||
3. Contacts-Browse listet ContactFolders (MSFT) bzw. ContactGroups
|
||||
(Google); Klick listet die enthaltenen Contacts; Download liefert
|
||||
eine `.vcf` mit FN, ORG, EMAIL, TEL.
|
||||
4. Frischer Connect (neue MSFT-Connection) zieht die neuen Scopes ohne
|
||||
Reconnect (weil sie im initialen `msftDataScopes` /
|
||||
`googleDataScopes` enthalten sind).
|
||||
5. ClickUp- und FTP-Connections sind unbeeintraechtigt; bei
|
||||
Infomaniak ist der Reconnect-Button korrekt **nicht** sichtbar
|
||||
(Authority-Whitelist).
|
||||
|
|
@ -12,8 +12,28 @@ type: `feat` `fix` `refactor` `docs` `test` `chore` `build` · scope: `gateway
|
|||
|
||||
Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler.
|
||||
|
||||
## 2026-04-29
|
||||
|
||||
- 2026-04-29 | fix | gateway, frontend-nyla, wiki | **Infomaniak-Connector: kDrive findet Standalone- und Free-tier-Drives (account_id-Resolution korrigiert).** Live-Beweis vom User mit echten kDrive-Files und Empty-Listing aus PowerOn: das Standalone-/Free-tier-kDrive haengt an einer **anderen** `account_id` als die kSuite, und `/2/drive?account_id=<kSuite-id>` antwortet sauber 200 mit leerer Liste -- die kSuite-`account_id` aus PIM Calendar/Contacts (heutige Identity-Quelle) trifft also den Drive-Account ueberhaupt nicht. Root-cause: es gibt unter den heutigen Scopes (`drive`, `workspace:*`) **keinen** Endpoint, der die volle Account-Liste eines PAT zurueckgibt -- der einzige PAT-faehige ist `GET /1/accounts`, der `403 all_scopes "scopes: ['accounts']"` antwortet wenn der `accounts`-Scope fehlt. Architektur-Change: (a) Neuer Helper `resolveAccessibleAccountIds(token)` im `connectorInfomaniak.py` -- ein einzelner `/1/accounts`-Call gibt **alle** account_ids zurueck, raised `InfomaniakIdentityError` mit klarer Scope-Message wenn der Scope fehlt. (b) `KdriveAdapter` haelt jetzt eine `_accountIds: List[int]` (statt `_accountId: int`); `_listDrives()` ruft `/2/drive?account_id=X` fuer **jede** account_id auf und unioniert die Drive-Ergebnisse mit Dedup ueber `driveId`. Damit deckt der Adapter sowohl den kSuite-eingebetteten als auch den Standalone-/Free-tier-kDrive ab, ohne dass der User irgendwas konfigurieren muss. (c) `resolveOwnerIdentity` ist jetzt rein UI-Identity (Display-Name + kSuite-`account_id` fuer das Connection-Label) -- nicht mehr fuer Drive-Lookup verwendet. (d) `submit_infomaniak_token` validiert jetzt drei Scopes deterministisch: `/1/accounts` (`accounts`-Scope), `resolveOwnerIdentity` (`workspace:calendar`/`workspace:contact`-Scope), `/2/drive?account_id={first}` (`drive`-Scope). Jeder Schritt liefert eine eigene 400-Message, die genau den fehlenden Scope nennt. (e) `grantedScopes` um `"accounts"` ergaenzt. Frontend: PAT-Setup-Modal listet jetzt **fuenf** Pflicht-Scopes (`accounts` neu) mit klarem "sonst findet kDrive deinen Drive nicht"-Hinweis. Doku: `infomaniak-token-setup.md` Status-Tabelle / Scope-Tabelle / Validation-Section / "How the kDrive adapter knows your account"-Section komplett umgeschrieben. **Konsequenz: bestehende Infomaniak-PATs ohne `accounts`-Scope muessen einmal im Infomaniak Manager neu erstellt werden -- der gestrige `resolveOwnerIdentity`-Pre-Flight-Check faellt sonst nie auf, das Listing bleibt leer.** (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`)
|
||||
|
||||
- 2026-04-29 | fix | gateway | **Infomaniak-Connector: Calendar-Events und Contacts-Download auf die echten PAT-faehigen Pfade gezogen.** Live-Tests gegen die Vendor-API zeigten zwei Pfad-Mismatches im gestrigen Build: (1) Calendar-Events: der nested Route `/api/pim/calendar/{id}/event` 302t zur OAuth-Login-Seite (also nicht PAT-faehig); korrekter Endpoint ist `/api/pim/event?calendar_id={id}&from=YYYY-MM-DD HH:MM:SS&to=...` mit Pflicht-Range max 3 Monate (Vendor-Constraint `range_must_be_lower_than_3_months`) -- Adapter waehlt jetzt fix 90-Tage-Window (heute -30 / +60), URL-Encoding via `urllib.parse.quote`. Event-Detail und `.ics`-Export laufen ueber `/api/pim/event/{eventId}` und `.../export` (also ohne calendar-Praefix). (2) Contacts-Download: `/addressbook/{book}/contact/{id}` antwortet mit `500 unexpected_error` und `.../export` 302t zu OAuth -- beide Detail-Endpoints sind also nicht PAT-faehig. Listing dagegen funktioniert, liefert aber per Default nur Stammdaten -- ohne `with=emails,phones,addresses,details` kommen Email/Phone/Adresse leer. Loesung: ContactAdapter holt das Listing mit dem `with`-Param und rendert die `.vcf` selbst via `_renderInfomaniakVcard()` (vCard 3.0, escaped, mit N/FN/ORG/EMAIL/TEL/ADR/URL/NOTE) -- konsistent mit MSFT/Google-Adapter, die ihre `.vcf`s ebenfalls selbst synthetisieren. Plus: Helper `_safeFileName` aus dem Calendar-Adapter zu modullokal hochgezogen, von beiden Adaptern genutzt; ungenutzte `import re`/`import json`-Inline-Imports raus. **kDrive-Adapter ist im Live-Test korrekt:** `/2/drive?account_id=1696919` antwortet 200 mit leerem Array fuer den Test-Account (kein kDrive-Produkt aktiviert). (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`)
|
||||
|
||||
## 2026-04-28
|
||||
|
||||
- 2026-04-28 | refactor | gateway | **Infomaniak-Connector: `account_id` ist Adapter-State, nicht Connection-State.** Symptom im Log: `Infomaniak GET https://api.infomaniak.com/2/drive?account_id=pat-XXXXXXXX -> 422 validation_rule_integer "The account id must be an integer."`. Root cause: der `KdriveAdapter` las `connection.externalId` als `account_id`-Quelle, und bei kaputten Submits konnte dort der Token-Fingerprint ("pat-59ee48d9") statt der `account_id` stehen. Saubere Loesung statt Fingerprint-Migration: (a) Neuer modullokaler Helper `resolveOwnerIdentity(token) -> InfomaniakOwnerIdentity` im `connectorInfomaniak.py` -- versucht PIM Calendar (`workspace:calendar`-Scope), faellt auf PIM Contacts (`workspace:contact`-Scope) zurueck, raised `InfomaniakIdentityError` wenn keine Owner-Records gefunden. (b) `KdriveAdapter` hat keinen `accountId`-Konstruktor-Parameter mehr, sondern resolvt zur Laufzeit ueber `_ensureAccountId()` (gecached auf der Adapter-Instanz). Damit ist der Adapter self-contained und liest **nichts** mehr aus der Connection -- `externalId` ist reiner UI-State. (c) `submit_infomaniak_token` ruft denselben `resolveOwnerIdentity()` als Pre-Flight-Check, dann `/2/drive?account_id={resolved}` als sauberer 200-Probe. Der frueher noetige `_probeScope`-422-Tolerance-Hack ist entfallen. (d) `getServiceAdapter` hat keine kDrive-Sonderbehandlung mehr; alle drei Adapter werden uniform mit `accessToken` konstruiert. (e) `_PIM_PREFIX` ist auf Modul-Ebene definiert; CalendarAdapter und ContactAdapter haben keine Klassen-Konstanten mehr. **Konsequenz: jede existierende Infomaniak-Connection arbeitet ohne User-Aktion sofort wieder, auch wenn `externalId` historisch einen Fingerprint enthaelt -- der Adapter zieht die `account_id` deterministisch aus der API.** Setup-Guide um Architektur-Abschnitt erweitert. (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`)
|
||||
|
||||
- 2026-04-28 | feat | gateway, frontend-nyla | **MSFT- und Google-Connector: CalendarAdapter + ContactsAdapter neu, plus Reconnect-Button.** Nachdem Infomaniak heute Calendar/Contacts kann, ziehen MSFT und Google nach. Backend: (a) `oauthProviderConfig.googleDataScopes` um `calendar.readonly` + `contacts.readonly` erweitert, `msftDataScopes` um `Calendars.Read` + `Contacts.Read`. (b) `connectorMsft.CalendarAdapter` (Graph: `me/calendars`, `me/calendars/{id}/events?$top&$orderby=start/dateTime desc`, Pagination via `@odata.nextLink`, `.ics`-Download als selbstgebautes RFC-5545 VCALENDAR/VEVENT da Graph keinen `$value`-Endpoint fuer Events kennt; `$search` fuer query). (c) `connectorMsft.ContactsAdapter` (Graph: `me/contactFolders` + virtueller `default`-Ordner fuer `me/contacts`, `me/contactFolders/{id}/contacts?$orderby=displayName`, `.vcf` als vCard-3.0 selbstgebaut mit N/FN/ORG/TITLE/EMAIL/TEL/ADR/NOTE). Helper `_eventToIcs`, `_contactToVcard`, `_safeFileName`, `_personLabel` plus `_icsEscape`/`_icsDateTime`. (d) `connectorGoogle.CalendarAdapter` (Calendar v3: `users/me/calendarList`, `calendars/{id}/events?singleEvents=true&orderBy=startTime`, `.ics` aus Event-Detail). (e) `connectorGoogle.ContactsAdapter` (People API: `contactGroups` + virtueller `all`-Folder ueber `people/me/connections`, fuer Gruppen `people:batchGet?resourceNames=...`, Search via `people:searchContacts`, `.vcf` aus `personFields=names,emailAddresses,phoneNumbers,organizations,addresses,biographies`). (f) `_SERVICE_MAP` von beiden Connectoren um `"calendar"`/`"contact"` ergaenzt; `routeFeatureWorkspace` und `routeFeatureGraphicalEditor` Service-Label-/Icon-Maps um `kdrive`, `calendar`, `contact` ergaenzt, damit die UDB sinnvolle Anzeigen macht. (g) **Reconnect-Flow**: `POST /api/connections/{id}/connect` akzeptiert optionalen Body `{"reauth": true}` und haengt `&reauth=1` an die Auth-URL. `routeSecurityMsft.auth_connect` setzt bei `reauth=1` `prompt=consent` (sonst select_account/login), `routeSecurityGoogle.auth_connect` droppt bei `reauth=1` `include_granted_scopes=true` damit Google strikt fuer die aktuelle Scope-Liste neu signiert (sonst werden neue Scopes still uebersprungen). Frontend: `connectionApi.connectService(id, reauth?)` -> POST mit Body, `useConnections.connectWithPopup(id, reauth?)` reicht durch, `ConnectionsPage` zeigt fuer aktive MSFT/Google/ClickUp-Connections einen `FaSyncAlt`-Button "Erneut verbinden (neue Scopes erteilen)" mit eigenem Loading-Set. Bestehende Connections muessen einmal reconnected werden, damit Calendar/Contacts in der UDB auftauchen. (c-work: `c-work/2-build/2026-04-msft-google-calendar-contacts.md`)
|
||||
|
||||
- 2026-04-28 | fix | gateway | **OpenAI-Connector: `temperature` fuer GPT-5.x / o-Serie aus dem Payload nehmen.** Symptom im Log: jede AI-Anfrage failt mit HTTP 400 `Unsupported value: 'temperature' does not support 0.2 with this model. Only the default (1) value is supported.`, der Failover spricht 14 Modelle durch und schreibt `Recorded failure for gpt-5.5, cooldown 60.0s`. Root cause: die GPT-5-Familie (gpt-5, gpt-5.4*, gpt-5.5*) und die o-Serie (o1/o3/o4) sind Reasoning-Modelle; OpenAI akzeptiert dort -- analog zur `max_tokens` -> `max_completion_tokens`-Restriction -- nur den Default (`1`). Wir senden aber unverhandelt `temperature=0.2` aus jedem `AiModel`-Eintrag. Fix: Helper `_supportsCustomTemperature(modelName)` und in `callAiBasic`/`callAiBasicStream`/`callAiImage` den Key nur conditional ins Payload aufnehmen (Modellnamen mit Praefix `gpt-5`, `o1`, `o3`, `o4` lassen ihn weg). Der vom User im UI gesetzte Override ueber `AiCallOptions.temperature` wird auf den nicht unterstuetzten Modellen still verworfen statt einen 400 zu erzwingen. Tests: `tests/unit/aicore/test_aicorePluginOpenai_temperature.py` (18 Tests, parametrisiert ueber Legacy-Modelle vs. Reasoning-Familie). Suite gesamt: 530 passed.
|
||||
|
||||
- 2026-04-28 | docs | wiki | **Infomaniak-Connector: Mail-Adapter formal als "blocked by vendor" markiert.** Erschoepfende Pfad-Tests am 2026-04-28 zeigen: alle 7 plausiblen Mail-Endpoints sind heute nicht PAT-faehig. `api.infomaniak.com/{1,2}/mail` -> 404 nginx (existiert nicht); `mail.infomaniak.com/api/mail[?account_id=...]`, `/api/pim/mail`, `/api/pim/mailbox`, `/api/pim/folder` -> 302 zu `login.infomaniak.com/authorize` (nur OAuth-Web-Session, Bearer-PAT wird abgelehnt); `mail.infomaniak.com/api/mail/?account_id=...` -> 301 zu `http://mail.infomaniak.com:5000` (interner Cyrus-IMAP-Port, von aussen nicht erreichbar). Der `workspace:mail`-Scope bleibt im PAT-Standard-Setup, damit der MailAdapter spaeter ohne Token-Rotation freigeschaltet werden kann; Setup-Guide und c-work-Doku zementieren den Befund. Kein Code-Change. (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`)
|
||||
|
||||
- 2026-04-28 | feat | gateway, frontend-nyla | **Infomaniak-Connector: ContactAdapter neu.** Nachfolge-Tests gegen die Contacts-PIM-API zeigten, dass `https://contacts.infomaniak.com/api/pim/addressbook` (Singular!) mit dem PAT-Scope `workspace:contact` und Bearer-Auth `200` + JSON liefert -- gleiche Antwort-Struktur wie Calendar (`addressbooks[].id/name/account_id/...`). Die Plural- und `/contact*`-Pfade (`/api/pim/contacts`, `/api/pim/contact/addressbook`) sind weiterhin OAuth-only bzw. 404. Neuer `ContactAdapter` 1:1 analog `CalendarAdapter`: `browse("/")` -> Adressbuecher, `browse("/{bookId}")` -> Kontakte (Display-Name, Email, Phone, Organization in Metadata), `download("/{bookId}/{contactId}")` -> `.vcf` via `/contact/{id}/export` mit JSON-Fallback, `search()` als kostenguenstiger Client-Filter. Geteilte Organisations-Adressbuecher (`name=""`, `is_dynamic_organisation_member_directory=true`) bekommen "Organisation" als Anzeigename, sonst waere der Tree-Knoten leer. Neue Konstante `_CONTACTS_BASE`, `ContactAdapter` in `_SERVICE_MAP` registriert. Frontend: `SourcesTab` kennt `contact`-Icon (👤), -Color und Mapping; PAT-Modal nennt Contacts als heute aktiv (Mail bleibt "in Vorbereitung"). Setup-Guide: Status-Tabelle, UDB-Verifikations-Liste und Validation-Beschreibung aktualisiert. (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`)
|
||||
|
||||
- 2026-04-28 | feat | gateway, frontend-nyla | **Infomaniak-Connector: kDrive PAT-Fix + Calendar-Adapter neu.** Nach den ersten echten PAT-Test-Calls aufgeraeumt: (a) `/2/drive` braucht ein `account_id`-Query-Arg, sonst `422 account_id required`. Fix: `account_id` einmalig beim Token-Submit aus dem Calendar-PIM-Endpoint (`https://calendar.infomaniak.com/api/pim/calendar` -- liefert `account_id`, `user_id`, Anzeigename) ziehen, in `UserConnection.externalId` persistieren und ueber `InfomaniakConnector.getServiceAdapter` als Konstruktor-Parameter in den `KdriveAdapter` injizieren. `/1/profile` (haette `user_info`-Scope verlangt) und `/1/mail` (existiert nicht: 404 nginx) raus aus den Probes. (b) `_probeScope` toleriert jetzt 4xx ausser 401/403 (z.B. 422 `validation_failed`) als "Scope ist da, Endpoint braucht nur weitere Args". (c) Neuer `CalendarAdapter` (`browse` -> Calendars/Events ueber `calendar.infomaniak.com/api/pim/calendar`, `download` -> `.ics` via `/event/{id}/export`, JSON-Fallback). `_infomaniakGet` und `_infomaniakDownload` akzeptieren ein optionales `baseUrl`, sodass Calendar gegen `calendar.infomaniak.com` und kDrive gegen `api.infomaniak.com` laufen koennen. (d) `MailAdapter` aus `_SERVICE_MAP` entfernt: `mail.infomaniak.com/api/mail` redirected mit 302 zu `login.infomaniak.com/authorize`, akzeptiert also keine PATs; gleiches gilt fuer `contacts.infomaniak.com/api/pim/contact`. Beide Scopes werden weiterhin in `grantedScopes` gespeichert, damit kuenftige Adapter ohne Token-Rotation aufgeschaltet werden koennen. (e) Frontend: `SourcesTab` kennt `calendar`-Icon, -Color und `_SERVICE_TO_SOURCE_TYPE`-Mapping; PAT-Modal in `ConnectionsPage` zeigt Calendar als zweiten aktiven Service, Mail/Contact als "in Vorbereitung, Scope schon mitnehmen". (f) Setup-Guide aktualisiert (Status-Tabelle + Validation-Beschreibung). (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`)
|
||||
|
||||
- 2026-04-28 | refactor | gateway, frontend-nyla | **Infomaniak-Connector: OAuth -> Personal Access Token.** Infomaniaks `login.infomaniak.com/authorize` akzeptiert nur Identity-Scopes (`openid`, `profile`, `email`, `phone`); Versuche mit `kdrive`/`mail`-Scopes quittieren mit `error=invalid_scope`. Datenzugriff geht ausschliesslich ueber manuell im Manager erstellte PATs. Umbau: (a) Backend `routeSecurityInfomaniak` komplett neu -- ein Endpoint `POST /api/infomaniak/connections/{id}/token`, validiert PAT via `GET https://api.infomaniak.com/1/profile`, persistiert Bearer mit 10-Jahres-Horizont (analog ClickUp), entfernt OAuth-Connect/Callback-Pfade. (b) `tokenManager.refreshInfomaniakToken` + `tokenRefreshService._refresh_infomaniak_token` entfernt, AuthAuthority.INFOMANIAK aus den Background-Refresh-Filtern raus -- PATs sind langlebig. (c) `oauthProviderConfig.infomaniakDataScopes` + `infomaniakDataScopesForRefresh` entfernt. (d) `routeDataConnections.connect_service` antwortet bei Infomaniak jetzt mit 400 + Hinweis auf den PAT-Endpoint. (e) Env-Cleanup: `Service_INFOMANIAK_DATA_CLIENT_ID/SECRET` und `Service_INFOMANIAK_OAUTH_REDIRECT_URI` aus `.env` + `env_dev/int/prod[_forgejo].env` raus. (f) Frontend: `useConnections.createInfomaniakConnectionAndAuth` (OAuth-Popup) ersetzt durch `createInfomaniakConnection` + `submitInfomaniakToken`; `ConnectionsPage` zeigt PAT-Modal mit Schritt-Anleitung + Deeplink zu `manager.infomaniak.com/v3/ng/accounts/token/list`; Cancel rollt PENDING-Connection per DELETE zurueck. (g) Doku: `wiki/d-guides/infomaniak-oauth-setup.md` geloescht, `wiki/d-guides/infomaniak-token-setup.md` neu. (c-work: `c-work/2-build/2026-04-infomaniak-connector.md`)
|
||||
|
||||
- 2026-04-28 | refactor | gateway | **Cleanup der zwei tieferliegenden Defensive-Programming-Schichten, die den Trustee-Bug (vorheriger Eintrag) ueberhaupt erst durchgelassen haben.**
|
||||
- **(1) DB-Connector: fail-loud statt swallow.** `connectorDbPostgre.getRecord/getRecordset/getRecordsetPaginated/getDistinctColumnValues/_loadTable/semanticSearch` haben bisher jede Exception per `except Exception → log → return []` (bzw. `None`/leeres Pagination-Resultat) verschluckt. Folge: jeder echte DB-Fehler (Postgres-Adapt, UndefinedTable, UndefinedColumn, OperationalError, etc.) wurde fuer den Caller ununterscheidbar von "0 Rows" -- darauf basierten misleading downstream Errors wie "No active accounting configuration found". Neu: typisierte Exception `DatabaseQueryError(table, message, original)` plus zentrales `_rollbackQuietly(connection)` (Postgres setzt die Connection in Error-State nach jedem fehlgeschlagenen Statement). Empty Result Sets liefern weiterhin `[]`/`None`/`{items: [], totalItems: 0, totalPages: 0}` (= Normalpfad ueber `cursor.fetchall()/fetchone()`), aber jede Exception innerhalb des Query-Pfads wird hochgereicht. Tests: `tests/unit/connectors/test_connectorDbPostgre_failLoud.py` (9 Tests).
|
||||
- **(2) Action-Parameter: zentrale Validierung statt impliziter Kontrakt.** Workflow-Actions haben `parameters: Dict[str, Any]` ohne Schema-Enforcement bekommen; die Aktionsimplementationen mussten ad-hoc `isinstance`-Branches haben oder mit Postgres-Errors abstuerzen (siehe Trustee-Bug). Neues Modul `gateway/modules/workflows/processing/shared/parameterValidation.py` mit `InvalidActionParameterError(ValueError)` + `validateAndCoerceParameters(actionDef, parameters)`. Zentral aufgerufen in `ActionExecutor.executeAction` -- gilt fuer alle Aufrufpfade (Agent, Workflow-Graph, REST). Logik:
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -27,10 +27,32 @@ This guide explains how to set up Google OAuth 2.0 authentication for the Porta
|
|||
3. If prompted, configure the OAuth consent screen first:
|
||||
- Choose "External" user type
|
||||
- Fill in the required fields (App name, User support email, Developer contact information)
|
||||
- Add scopes: `https://www.googleapis.com/auth/userinfo.profile`, `https://www.googleapis.com/auth/userinfo.email`
|
||||
- Add scopes (see `gateway/modules/auth/oauthProviderConfig.py` -- `googleAuthScopes` and `googleDataScopes` are the source of truth):
|
||||
- **Auth app** (login only):
|
||||
- `openid`
|
||||
- `https://www.googleapis.com/auth/userinfo.profile`
|
||||
- `https://www.googleapis.com/auth/userinfo.email`
|
||||
- **Data app** (UDB connectors -- Drive, Gmail, Calendar, Contacts):
|
||||
- everything from the Auth app, **plus**
|
||||
- `https://www.googleapis.com/auth/gmail.readonly`
|
||||
- `https://www.googleapis.com/auth/drive.readonly`
|
||||
- `https://www.googleapis.com/auth/calendar.readonly`
|
||||
- `https://www.googleapis.com/auth/contacts.readonly`
|
||||
- Enable the matching Google APIs under "APIs & Services > Library":
|
||||
Gmail API, Google Drive API, **Google Calendar API**, **People API**.
|
||||
- Add test users if needed
|
||||
- Click "Save and Continue" through all sections
|
||||
|
||||
> **Adding new scopes to an existing app?** When you broaden the scope list
|
||||
> (e.g. when Calendar/Contacts were rolled out on top of the original Drive +
|
||||
> Gmail set), every existing UserConnection must go through the
|
||||
> **Reconnect** action in the Connections page (`/basedata/connections`). The
|
||||
> Reconnect button posts `{"reauth": true}` to the connect endpoint, which
|
||||
> drops `include_granted_scopes=true` from the authorization URL so Google
|
||||
> issues a token strictly for the **current** scope set. Without reconnecting
|
||||
> the connection keeps working with the **old** scopes only -- the new
|
||||
> services (Calendar, Contacts) silently return 403/empty.
|
||||
|
||||
4. Back to creating OAuth client ID:
|
||||
- Application type: "Web application"
|
||||
- Name: "Porta Web Client"
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
# Infomaniak OAuth 2.0 Setup Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide explains how to register an Infomaniak OAuth application so that PowerOn
|
||||
users can connect their Infomaniak account as a data source. The connection
|
||||
exposes two services in the Unified Data Bar:
|
||||
|
||||
- **kDrive** -- browse and download files from any drive accessible to the user.
|
||||
- **Mail** -- browse mailboxes, folders, and download messages as `.eml`.
|
||||
|
||||
Infomaniak is a **data-only** authority in PowerOn. It is **not** a login
|
||||
provider; users still authenticate against PowerOn via Local / Google / Microsoft.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An Infomaniak account with admin rights to create API credentials.
|
||||
- Access to the Infomaniak Manager (https://manager.infomaniak.com).
|
||||
- The PowerOn gateway publicly reachable (or `http://localhost:8000` for development).
|
||||
|
||||
## Step 1: Create an Infomaniak API Application
|
||||
|
||||
1. Login to https://manager.infomaniak.com.
|
||||
2. Navigate to your account icon (top right) > **API**.
|
||||
3. Click **Create a new application**.
|
||||
4. Fill in:
|
||||
- **Name:** PowerOn (or your project name)
|
||||
- **Description:** Data connector for kDrive + Mail
|
||||
- **Redirect URI:** must exactly match the gateway endpoint:
|
||||
- Development: `http://localhost:8000/api/infomaniak/auth/connect/callback`
|
||||
- Production: `https://<gateway-host>/api/infomaniak/auth/connect/callback`
|
||||
5. Select scopes:
|
||||
- `user_info` -- required to read `/1/profile` for `externalId` / `email`.
|
||||
- `kdrive` -- required for the kDrive ServiceAdapter.
|
||||
- `mail` -- required for the Mail ServiceAdapter.
|
||||
6. Save and copy the generated **Client ID** and **Client Secret**.
|
||||
|
||||
## Step 2: Configure PowerOn Gateway
|
||||
|
||||
Add the following keys to your gateway environment file (e.g. `gateway/env_dev.env`):
|
||||
|
||||
```env
|
||||
# Infomaniak OAuth -- Data App (kDrive + Mail)
|
||||
Service_INFOMANIAK_DATA_CLIENT_ID = your-client-id
|
||||
Service_INFOMANIAK_DATA_CLIENT_SECRET = your-client-secret
|
||||
Service_INFOMANIAK_OAUTH_REDIRECT_URI = http://localhost:8000/api/infomaniak/auth/connect/callback
|
||||
```
|
||||
|
||||
For production, replace the redirect URI with your HTTPS gateway host. The URI
|
||||
must be identical (byte-for-byte) to the one registered in step 1.
|
||||
|
||||
Restart the gateway after editing the env file so `APP_CONFIG` is reloaded.
|
||||
|
||||
## Step 3: Verify the Flow
|
||||
|
||||
1. Login to PowerOn and open **Basisdaten > Verbindungen**.
|
||||
2. Click **Infomaniak** in the header actions.
|
||||
3. A popup opens against `https://login.infomaniak.com/authorize`.
|
||||
4. Sign in with your Infomaniak account, grant the requested scopes.
|
||||
5. The popup closes automatically and the new connection appears with status
|
||||
`connected`.
|
||||
6. Open the **Unified Data Bar > Sources** tab; you should see the Infomaniak
|
||||
authority node with `kdrive` and `mail` services.
|
||||
|
||||
## Token Lifecycle
|
||||
|
||||
- Access tokens are short-lived (refresh handled automatically by
|
||||
`tokenRefreshService._refresh_infomaniak_token`).
|
||||
- Refresh tokens are stored alongside the connection (`Token.tokenRefresh`).
|
||||
- If a refresh returns `invalid_grant`, the user must reconnect (popup again
|
||||
via the Infomaniak button on `ConnectionsPage`).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `invalid_redirect_uri`
|
||||
The URI registered on Infomaniak Manager does not match `Service_INFOMANIAK_OAUTH_REDIRECT_URI`.
|
||||
They must be byte-identical (including protocol, host, port, and path).
|
||||
|
||||
### `invalid_client`
|
||||
Client ID or secret in the env file does not match what Infomaniak issued.
|
||||
Re-copy the values from Manager > API.
|
||||
|
||||
### Profile lookup returns 401
|
||||
The `user_info` scope was not granted. Re-create the application with the
|
||||
correct scope set, or reconnect the user.
|
||||
|
||||
### Empty kDrive listing
|
||||
The user has no kDrives associated. Verify on https://kdrive.infomaniak.com
|
||||
that at least one drive is accessible.
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Never commit the client secret. Use the encrypted env mechanism described in
|
||||
`wiki/d-guides/encrypt-env-secrets.md`.
|
||||
- Rotate the client secret if it leaks; update `Service_INFOMANIAK_DATA_CLIENT_SECRET`
|
||||
and restart the gateway. Existing tokens remain valid only until they expire.
|
||||
- The connector is scoped to `DATA_CONNECTION` only. Even with valid Infomaniak
|
||||
credentials, no PowerOn login session is created.
|
||||
|
||||
## Reference
|
||||
|
||||
- Code: `gateway/modules/connectors/providerInfomaniak/connectorInfomaniak.py`
|
||||
- OAuth route: `gateway/modules/routes/routeSecurityInfomaniak.py`
|
||||
- Token refresh: `gateway/modules/auth/tokenManager.py::refreshInfomaniakToken`
|
||||
- Frontend hook: `frontend_nyla/src/hooks/useConnections.ts::createInfomaniakConnectionAndAuth`
|
||||
161
d-guides/infomaniak-token-setup.md
Normal file
161
d-guides/infomaniak-token-setup.md
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# Infomaniak Personal Access Token Setup
|
||||
|
||||
## Overview
|
||||
|
||||
PowerOn integrates Infomaniak as a **data-only** authority for the kSuite
|
||||
services in the Unified Data Bar:
|
||||
|
||||
| Service | API scope | Status in PowerOn |
|
||||
|---|---|---|
|
||||
| _account discovery_ -- enumerates the user's account_ids | `accounts` | required for kDrive |
|
||||
| **kDrive** -- browse / download files | `drive` | active |
|
||||
| **Calendar** -- agendas + events (.ics download) | `workspace:calendar` | active |
|
||||
| **Contacts** -- address books + contacts (.vcf download) | `workspace:contact` | active |
|
||||
| **Mail** -- mailboxes, folders, `.eml` download | `workspace:mail` | blocked by Infomaniak (no PAT-friendly endpoint) |
|
||||
|
||||
You should tick **all five scopes** when creating the token even if only
|
||||
kDrive, Calendar and Contacts are wired up today -- this avoids a token
|
||||
rotation when Mail goes live.
|
||||
|
||||
The `accounts` scope is non-negotiable: a standalone or free-tier
|
||||
kDrive lives on a *different* `account_id` than its kSuite counterpart,
|
||||
and `/1/accounts` is the only PAT-friendly endpoint that enumerates
|
||||
**all** account_ids in one call. Without it the kDrive listing will
|
||||
silently come back empty even though the PAT carries the `drive`
|
||||
scope, because PowerOn would only know about the kSuite `account_id`
|
||||
(via PIM Calendar / Contacts) and that one returns no drives.
|
||||
|
||||
**Status of the Mail adapter (2026-04-28):** Infomaniak currently does
|
||||
not expose a PAT-authenticated endpoint for mailboxes/folders/messages.
|
||||
Every probed route either returns `404` (`/1/mail`, `/2/mail`) or
|
||||
`302`-redirects to the OAuth authorize page
|
||||
(`/api/mail`, `/api/pim/mail`, `/api/pim/mailbox`, `/api/pim/folder`),
|
||||
which is the OAuth-Web-Session path -- bearer PATs are rejected. The
|
||||
`/api/mail/?account_id=...` route 301-redirects to an internal Cyrus
|
||||
server on `http://mail.infomaniak.com:5000` that is not reachable from
|
||||
the public internet. The `workspace:mail` scope is stored on the PAT so
|
||||
the Mail adapter can be enabled later with no token rotation, as soon
|
||||
as Infomaniak opens a public PAT-friendly endpoint.
|
||||
|
||||
Infomaniak does not expose its data APIs (`/2/drive/...`, `/1/mail/...`) over
|
||||
OAuth 2.0. The OAuth endpoint at `login.infomaniak.com/authorize` only issues
|
||||
identity tokens (scopes `openid`, `profile`, `email`, `phone`). Bearer tokens
|
||||
that work against the data APIs must be issued manually by the user as a
|
||||
**Personal Access Token (PAT)** in the Infomaniak Manager.
|
||||
|
||||
This is the same model used by GitHub Personal Access Tokens, Notion
|
||||
Integration Tokens, and many other vendors. PowerOn never sees the user's
|
||||
Infomaniak password.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An Infomaniak account that has access to at least one kDrive and one mailbox.
|
||||
- The PowerOn frontend reachable in the browser; the backend reachable from
|
||||
the frontend.
|
||||
|
||||
## Step 1: Create the token in the Infomaniak Manager
|
||||
|
||||
1. Open the API-Tokens page directly:
|
||||
<https://manager.infomaniak.com/v3/ng/accounts/token/list>
|
||||
2. Click **Token erstellen** (Create token).
|
||||
3. Fill in the form:
|
||||
- **Token name**: anything memorable, for example `PowerOn` or
|
||||
`PowerOn DEV`.
|
||||
- **Application**: leave it on `Default application`. The "PowerOn"
|
||||
application registration is **not** used for PATs.
|
||||
- **Scopes** (search box): add **all five** of the following, one by one:
|
||||
|
||||
| Search term | Pick this entry |
|
||||
|---|---|
|
||||
| `accounts` | `accounts - Manage your accounts` |
|
||||
| `drive` | `drive - Drive products` |
|
||||
| `workspace:mail` | `workspace:mail - Manage your emails` |
|
||||
| `workspace:calendar` | `workspace:calendar - Manage your calendars` |
|
||||
| `workspace:contact` | `workspace:contact - Manage your contacts` |
|
||||
|
||||
Do **not** tick `All` -- it grants every Infomaniak API (Hosting,
|
||||
Billing, AI, ...). Do **not** add `user_info` -- PowerOn does not call
|
||||
`/1/profile`.
|
||||
- **Validity**: any value works. PowerOn does not auto-refresh PATs.
|
||||
4. Click **Erstellen** (Create) and **copy the token immediately**. Infomaniak
|
||||
shows the token value only once.
|
||||
|
||||
## Step 2: Paste the token into PowerOn
|
||||
|
||||
1. Sign in to PowerOn and open **Basisdaten -> Verbindungen**.
|
||||
2. Click **Infomaniak**. PowerOn creates a pending connection and opens a
|
||||
modal that asks for the Personal Access Token.
|
||||
3. Paste the token from Step 1 and click **Verbinden**.
|
||||
|
||||
PowerOn validates the token in three deterministic steps before
|
||||
persisting anything -- each step probes exactly one scope:
|
||||
|
||||
1. `GET https://api.infomaniak.com/1/accounts` (requires scope
|
||||
`accounts`) -- enumerates **all** Infomaniak account_ids the PAT
|
||||
can reach. Without this scope kDrive cannot find the owning
|
||||
account; the submit fails with HTTP 400 and a message that names
|
||||
the missing scope.
|
||||
2. `GET https://calendar.infomaniak.com/api/pim/calendar` (requires
|
||||
scope `workspace:calendar`; `workspace:contact` works as the
|
||||
equivalent fallback) -- yields the user's display name and kSuite
|
||||
account_id for the connection label in the UI.
|
||||
3. `GET https://api.infomaniak.com/2/drive?account_id=<first>`
|
||||
(requires scope `drive`) -- a clean 200 confirms the `drive`
|
||||
scope is on the PAT.
|
||||
|
||||
On success the connection turns active and the token is stored
|
||||
encrypted in the backend; on failure the modal shows which scope is
|
||||
missing. The mail scope is stored as part of `grantedScopes` without
|
||||
a probe -- it is only consumed once the Mail adapter lands.
|
||||
|
||||
## Step 3: Verify in the Unified Data Bar
|
||||
|
||||
Open the **Sources** tab in the Unified Data Bar. The connection appears with
|
||||
the Infomaniak label and exposes (today) three child nodes:
|
||||
|
||||
- **kDrive** -- expand to see drives, then folders and files.
|
||||
- **Calendar** -- expand to see calendars, then events; downloads as `.ics`.
|
||||
- **Contacts** -- expand to see address books, then contacts; downloads as `.vcf`.
|
||||
|
||||
Mail will appear as an additional child node once a PAT-friendly endpoint
|
||||
is identified; no token rotation needed because the scope is already on
|
||||
the PAT.
|
||||
|
||||
## Rotating or revoking the token
|
||||
|
||||
- To rotate, repeat Step 1 with a new token, then in PowerOn delete the
|
||||
existing Infomaniak connection and create a new one with the fresh token.
|
||||
- To revoke, delete the token in the Infomaniak Manager. The PowerOn
|
||||
connection will start to fail at the next call; delete it from the
|
||||
Verbindungen page to remove the stored bearer.
|
||||
|
||||
## How the kDrive adapter knows your account
|
||||
|
||||
`/2/drive` requires an integer `account_id` query arg. A user can own
|
||||
kDrives in several Infomaniak accounts -- typically a kSuite account
|
||||
plus a standalone (or free-tier) kDrive that lives on its **own**
|
||||
account_id. The kSuite account_id from PIM Calendar / Contacts only
|
||||
covers the kSuite case, which is why naive PAT integrations show an
|
||||
empty kDrive even though there are files.
|
||||
|
||||
The `KdriveAdapter` therefore calls `GET /1/accounts` (the only
|
||||
PAT-friendly endpoint that lists every account_id of a token) and
|
||||
unions the `/2/drive?account_id=<X>` listing across all returned
|
||||
account_ids. The result is cached on the adapter instance for the
|
||||
lifetime of the request, so each browse touches `/1/accounts` at most
|
||||
once.
|
||||
|
||||
The submit endpoint runs the same enumeration as a pre-flight check
|
||||
before persisting the token; if `/1/accounts` rejects the PAT for
|
||||
missing the `accounts` scope, the submit fails with a clear 400
|
||||
instead of producing a half-broken connection.
|
||||
|
||||
## Security notes
|
||||
|
||||
- PATs are stored exactly like OAuth access tokens for Google or Microsoft:
|
||||
encrypted at rest in the gateway database, only ever sent over TLS to
|
||||
`api.infomaniak.com`, and never returned to the frontend after submission.
|
||||
- The PowerOn backend does not need any Infomaniak client ID or client
|
||||
secret -- there is no `Service_INFOMANIAK_*` configuration.
|
||||
- Each user manages their own tokens. There is no global "PowerOn Infomaniak
|
||||
app" the operator has to register or maintain.
|
||||
Loading…
Reference in a new issue