189 lines
9.5 KiB
Markdown
189 lines
9.5 KiB
Markdown
<!-- 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).
|