# 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).