# Microsoft 365 Service FastAPI služba pro server-to-server komunikaci s Microsoft 365 přes Microsoft Graph. ## Konfigurace V Microsoft Entra ID vytvořte app registration, přidělte požadovaná aplikační oprávnění pro Microsoft Graph a nastavte tyto environment proměnné: ```env MS365_TENANT_ID=00000000-0000-0000-0000-000000000000 MS365_CLIENT_ID=00000000-0000-0000-0000-000000000000 MS365_CLIENT_SECRET=secret-value MS365_GRAPH_BASE_URL=https://graph.microsoft.com/v1.0 MS365_GRAPH_SCOPE=https://graph.microsoft.com/.default MS365_REQUEST_TIMEOUT_SECONDS=30 ``` Služba používá OAuth2 client credentials flow, takže oprávnění pro Microsoft Graph musí být aplikační oprávnění schválená administrátorem. ## Credentials v request hlavičkách Credentials se posílají v každém requestu v hlavičkách jako obyčejné hodnoty: ```http X-MS365-Tenant-Id: X-MS365-Client-Id: X-MS365-Client-Secret: ``` Všechny tři hlavičky musí být pohromadě, jinak služba vrátí 400. Když se nepošle žádná, použijí se `MS365_TENANT_ID`, `MS365_CLIENT_ID` a `MS365_CLIENT_SECRET` z prostředí služby. Příklad v PowerShellu: ```powershell $Headers = @{ "X-MS365-Tenant-Id" = $TenantId "X-MS365-Client-Id" = $ClientId "X-MS365-Client-Secret" = $ClientSecret } Invoke-RestMethod -Method Get -Headers $Headers ` -Uri "https://services.csbot.cz/apps/microsoft-365-service/users/$UserMailbox/calendar" ``` ## Multitenant napojení klientů (CSBOT MS365 Connector) Služba je stavěná pro jednu multitenant Entra aplikaci CSBOT, přes kterou se připojují Microsoft 365 tenanty jednotlivých klientů. Podrobné zadání je v Notion stránce "MS365 práva konektoru". - `MS365_CLIENT_ID` a `MS365_CLIENT_SECRET` patří aplikaci CSBOT a jsou pro všechny klienty stejné. - `MS365_TENANT_ID` je tenant konkrétního klienta. Token se získává proti `https://login.microsoftonline.com/{tenant klienta}/oauth2/v2.0/token`. - Při volání za různé klienty se tenant id posílá v hlavičce `X-MS365-Tenant-Id` (viz "Credentials v request hlavičkách"), client id a secret zůstávají stejné. - Klient nevytváří vlastní App Registration ani nepředává secret. Jeho administrátor jen schválí admin consent. URL pro consent vrací `GET /admin-consent/url?redirect_uri=...&state=...` (bere tenant id a client id z hlaviček nebo z env). Callback po consentu (parametry `tenant` a `admin_consent=True`) zpracovává backend CSBOT, ne tato služba. - Služba je bezstavová a neověřuje, zda mailbox, plan id nebo bucket id patří danému zákazníkovi. Tenant isolation (customer id -> tenant id, mailbox, plan id, bucket id) musí držet volající backend, hodnoty nesmí přicházet přímo od LLM. Postup testu po consentu klienta: `GET /status`, `GET /users/{mailbox}/calendar` (vrátí id a name kalendáře, nejlevnější ověření přístupu), `GET /users/{mailbox}/calendar/view`, `POST /users/{mailbox}/calendar/events` + `DELETE`, `GET /planner/plans/{plan_id}/tasks`, `POST /planner/tasks` + `DELETE`. ## Implementované služby - Users: výpis a načtení uživatelů. - Mail: výpis zpráv a odesílání e-mailů včetně příloh. - Calendar: načtení kalendáře schránky (id, name), výpis událostí, calendarView v zadaném rozsahu, volné termíny (getSchedule), načtení, vytvoření, úprava a smazání události včetně Teams online meetingů. - Planner: výpis plánů skupiny, bucketů a tasků plánu, načtení, vytvoření, úprava a smazání tasku. Úprava a smazání používají ETag (`If-Match`). Když hlavička chybí, služba si aktuální ETag načte sama. - OneDrive: výpis souborů v rootu a upload malých souborů. - Groups a Teams: výpis skupin a výpis kanálů týmu. - Admin consent: sestavení consent URL pro administrátora klientského tenantu. ## API ```http GET /health GET /version GET /status GET /users?top=25&search=jane GET /users/{user_id} GET /users/{user_id}/mail/messages?folder=Inbox&top=25&select=... POST /users/{user_id}/mail/send GET /admin-consent/url?redirect_uri=...&state=... GET /users/{user_id}/calendar GET /users/{user_id}/calendar/events?top=25&select=... POST /users/{user_id}/calendar/events GET /users/{user_id}/calendar/view?start=...&end=...&top=25&time_zone=Europe/Prague&include_body=true&body_type=text&select=... POST /users/{user_id}/calendar/schedule GET /users/{user_id}/calendar/events/{event_id}?select=... PATCH /users/{user_id}/calendar/events/{event_id} DELETE /users/{user_id}/calendar/events/{event_id} GET /users/{user_id}/drive/root/children?top=25 PUT /users/{user_id}/drive/root/{path} GET /groups?top=25&select=... GET /groups/{group_id}/planner/plans GET /teams/{team_id}/channels GET /planner/plans/{plan_id}/buckets GET /planner/plans/{plan_id}/tasks POST /planner/tasks GET /planner/tasks/{task_id} PATCH /planner/tasks/{task_id} (volitelně hlavička If-Match) DELETE /planner/tasks/{task_id} (volitelně hlavička If-Match) ``` Parametry `start` a `end` u calendarView jsou ISO 8601 s offsetem, například `2026-09-08T08:00:00+02:00`. Tělo událostí se ve výpisu nevrací, dokud se nepošle `include_body=true`; `body_type=text` vrátí tělo jako čistý text místo HTML. Výpisy mailů, událostí a skupin a načtení jedné události vracejí jen výchozí sadu polí. Parametr `select` (pole oddělená čárkou) ji nahradí; výchozí sada i seznam dostupných polí jsou u každého endpointu ve Swaggeru a v `app/graph_fields.py`. Neznámé pole Graph odmítne a služba vrátí 502 s jeho chybou. Datumy u Planneru (`due_date_time`, `start_date_time`) jsou ISO 8601 s offsetem, například `2026-09-10T16:00:00Z`. Řešitelé se předávají jako Graph user id v `assign_user_ids`, odebírají v `unassign_user_ids`. `user_id` může být Microsoft Graph user id nebo user principal name, například `jane@example.com`. ## Požadovaná Graph oprávnění Přidělte pouze oprávnění, která dané nasazení skutečně používá: - Users: `User.Read.All` - Čtení mailů: `Mail.Read` - Odesílání mailů: `Mail.Send` - Čtení a zápis do kalendáře, calendarView, getSchedule: `Calendars.ReadWrite` - Planner: `Tasks.ReadWrite.All` (zahrnuje i čtení, `Tasks.Read.All` netřeba) - Čtení a zápis do OneDrive: `Files.ReadWrite.All` - Výpis skupin a Teams kanálů: `Group.Read.All`, `Team.ReadBasic.All`, `Channel.ReadBasic.All` Pro konektor CSBOT (Calendar + Planner) je minimum `Calendars.ReadWrite` a `Tasks.ReadWrite.All`, obě jako Application permissions. `Calendars.ReadWrite` platí na všechny mailboxy tenantu, omezení na konkrétní mailbox se řeší na straně klienta v Exchange Online (application access policy), ne v této službě. ## Ukázky pro klienty - `examples/csharp/ListUsersTop10`: konzolová aplikace .NET 8, volá `GET /users?top=10` s hlavičkami `X-MS365-*`. Popis v `examples/csharp/README.md`. ## Lokální spuštění ```bash pip install -r requirements.txt uvicorn app.main:app --reload ``` Vygenerované OpenAPI UI otevřete na `http://localhost:8000/docs`. # job queue test Thu May 28 02:12:07 PM CEST 2026 # job queue test Thu May 28 02:12:28 PM CEST 2026 # websocket logs test Fri May 29 11:03:58 AM CEST 2026 ## Záznam změn - 2026-09-22: parametr `select` u výpisu mailů, událostí, calendarView, jedné události a skupin. Výchozí sady a seznamy polí v `app/graph_fields.py`, Swagger je zobrazuje. - 2026-09-22: `calendar/view` má `include_body` a `body_type` (html, text), tělo události se dá dotáhnout rovnou ve výpisu bez dalšího volání na jednotlivé události. - 2026-09-22: šifrování hlaviček `X-MS365-*` odstraněno, hodnoty se posílají tak, jak jsou. Zrušena proměnná `MS365_CREDENTIAL_ENCODING_SECRET` a závislost `cryptography`. - 2026-09-22: `GET /users/{user_id}/calendar` (objekt kalendáře schránky, id a name) pro read-only ověření přístupu ke kalendáři klienta přes e-mail schránky. - 2026-09-09: verze 1.1.0. Podle Notion zadání "MS365 práva konektoru" doplněn Calendar (calendarView, getSchedule, get/patch/delete události), Planner (plány, buckety, tasky, create/patch/delete s ETag) a `GET /admin-consent/url`. Kód ověřen jen importem aplikace a serializací schémat, proti reálnému tenantu netestováno (chybí DEV App Registration). - 2026-09-09: přidána C# ukázka `examples/csharp/ListUsersTop10` (top 10 z `/users`).