# Microsoft Entra ID / Microsoft 365 autentikacija

## Rezultat

Aplikacija podržava dva paralelna načina prijave:

- postojeći email + password;
- Microsoft Entra ID preko OIDC Authorization Code Flow-a sa PKCE S256.

Oba toka završavaju u istoj serverskoj PHP sesiji (`user_id`). Role, organizaciona jedinica, `is_system_admin`, `is_active` i sva prava se i dalje čitaju isključivo iz ticketing baze. Microsoft Graph, Entra grupe i Entra App Roles se ne koriste.

## Preduslovi servera

- PHP 8.0 ili noviji;
- PHP ekstenzije cURL i JSON;
- HTTPS;
- Apache `mod_rewrite` (kao i postojeće prilagođene rute);
- Composer zavisnosti instalirane komandom `composer install --no-dev --optimize-autoloader` pre objave.

Lokalni login ne zavisi od Composer biblioteke i ostaje dostupan čak i ako Entra konfiguracija ili servis nisu dostupni.

## Redosled objave

1. Napraviti backup fajlova i baze.
2. Za promenu domena ne pokretati SQL migraciju. Dostavljeni `trenutnabaza.sql` već sadrži Entra kolone i indekse. Istorijski `upgrade_entra_auth.sql` nije deo ove kopije projekta; za instalaciju na stariju šemu prvo pribaviti odgovarajuću migraciju i proveriti šemu.
3. Instalirati produkcione Composer zavisnosti.
4. Objaviti aplikativne fajlove i `vendor` direktorijum ako se Composer ne pokreće direktno na serveru.
5. Postaviti četiri environment promenljive opisane ispod.
6. U Entra App Registration proveriti platformu i povratnu adresu.
7. Testirati prvo lokalnu, zatim Microsoft prijavu jednim namenskim test nalogom.

## Environment promenljive

```text
ENTRA_CLIENT_ID=<Application (client) ID>
ENTRA_TENANT_ID=<Directory (tenant) ID>
ENTRA_CLIENT_SECRET=<secret VALUE, ne Secret ID>
ENTRA_REDIRECT_URI=https://infrastruktura.sos-decijasela.rs/auth/entra/callback
```

Vrednosti se prvenstveno čitaju preko `getenv()`. Ne treba ih upisivati u Git, `.htaccess`, SQL ili javne PHP aplikativne fajlove.

Za standardni cPanel shared hosting bez aplikacionih environment promenljivih podržan je bezbedniji fallback fajl izvan web root-a:

```text
/home/CPANEL_USER/sos-infrastruktura-secrets.php
```

Sadržaj tog fajla:

```php
<?php
define('ENTRA_CLIENT_ID', 'APPLICATION-CLIENT-ID');
define('ENTRA_TENANT_ID', 'DIRECTORY-TENANT-ID');
define('ENTRA_CLIENT_SECRET', 'SECRET-VALUE');
define('ENTRA_REDIRECT_URI', 'https://infrastruktura.sos-decijasela.rs/auth/entra/callback');
```

Postaviti dozvole fajla na `0600`. `config.php` ga automatski učitava preko cPanel `HOME` putanje. Ako hosting nudi Application Manager environment variables, one imaju prioritet i fallback fajl nije potreban.

Client secret koji se kopira je polje **Value** prikazano samo prilikom kreiranja tajne, ne njen **Secret ID**. Pre isteka tajne napraviti novu, promeniti environment vrednost i tek zatim ukloniti staru.

## Entra App Registration

- Supported account types: **Accounts in this organizational directory only (single tenant)**.
- Authentication → Web redirect URI: `https://infrastruktura.sos-decijasela.rs/auth/entra/callback` (bez završne kose crte). Dodati pored postojeće staging adrese dok staging treba da radi; ne koristiti SPA platformu.
- Implicit grant and hybrid flow opcije: isključene.
- Delegated/API permissions: dovoljni su OIDC scope-ovi `openid`, `profile`, `email`; ne dodavati `User.Read`, directory ili group privilegije za ovaj tok.
- Front-channel logout URL nije potreban za lokalnu aplikacionu odjavu.

Detaljni koraci za produkciju su u [PRODUCTION_MIGRATION.md](PRODUCTION_MIGRATION.md).
Staging mora eksplicitno postaviti svoj `ENTRA_REDIRECT_URI` u environment-u ili
svom secrets fajlu, jer je podrazumevana vrednost sada produkcijska. Ako oba domena
dele isti cPanel HOME/secrets fajl, različite callback vrednosti postaviti po domenu
kroz server environment. `entra_setting()` daje environment-u prioritet nad konstantama.
`logout.php` završava samo lokalnu sesiju i vraća na `login.php`; Microsoft sesija
ostaje aktivna. Aplikacija ne šalje `post_logout_redirect_uri` i nema front-channel
logout handler. Ne registrovati CSRF-zaštićeni `logout.php` kao takav handler.

## Povezivanje naloga

1. Potpisan i validiran ID token mora sadržati očekivane `oid`, `tid` i `nonce` vrednosti.
2. Ako postoji `users.entra_object_id`, koristi se taj korisnik bez obzira na kasniju promenu email/UPN vrednosti.
3. Samo prvi put, ako Object ID još nije povezan, ticketing korisnik se traži po email/UPN kandidatima iz validiranog tokena.
4. Mora postojati tačno jedan lokalni korisnik, bez drugog Entra Object ID-a, i mora biti aktivan.
5. Povezivanje se radi transakciono i jedinstveni indeks sprečava da isti Entra identitet pripadne dvama korisnicima.
6. Ako korisnik ne postoji, nalog se ne kreira automatski.

## Test plan

Pre produkcije izvršiti sledeće sa test tenant/user podacima i proveriti `[security]` audit događaje bez tokena i tajni:

1. Postojeći lokalni korisnik + validna lozinka: prijava uspešna, `AUTH_LOCAL_SUCCESS`.
2. Postojeći lokalni korisnik + pogrešna lozinka: odbijeno, `AUTH_LOCAL_FAILED`.
3. Postojeći korisnik + odgovarajući M365 nalog: prijava uspešna.
4. Prvi M365 login: popunjena oba Entra polja i `AUTH_ENTRA_ACCOUNT_LINKED`.
5. Sledeći M365 login: uspeh preko `entra_object_id`.
6. M365 korisnik bez ticketing naloga: jasna poruka, bez novog reda u `users`.
7. Neaktivan ticketing korisnik + validan M365 login: odbijeno.
8. Promeniti rolu kroz postojeću administraciju, ponoviti M365 login: nova lokalna rola važi.
9. Uporediti `user_roles` pre/posle Microsoft prijave: nema promene.
10. Ponoviti lokalni login nakon Entra povezivanja: i dalje radi.
11. Odjava posle obe vrste prijave: serverska sesija i cookie su uklonjeni.
12. Callback sa pogrešnim/isteklim `state`: odbijen pre razmene koda.
13. Token/nalog iz drugog tenant-a: odbijen.
14. Posle povezivanja promeniti lokalni email ili Entra UPN: sledeća prijava i dalje radi preko `oid`.

Dodatno proveriti otkazivanje Microsoft dijaloga, pogrešan client secret, privremeno nedostupan Microsoft endpoint i potpuno identičnu redirect URI vrednost na obe strane.

## Rollback

1. Onemogućiti Microsoft dugme uklanjanjem `ENTRA_CLIENT_ID`, `ENTRA_TENANT_ID` i `ENTRA_CLIENT_SECRET` iz server environment-a.
2. Vratiti prethodne aplikativne fajlove. Lokalni email/password podaci nisu menjani.
3. Za rollback promene domena ne menjati bazu. Istorijski `rollback_entra_auth.sql` nije deo ove kopije projekta i nije korak ove migracije.

Rollback kolona nije obavezan za hitan povratak: nullable Entra kolone su inertne za staru verziju aplikacije.

## Bezbednosne napomene

- Biblioteka validira discovery/JWKS potpis, state, nonce, issuer, audience i vreme važenja; aplikacija dodatno zahteva RS256, očekivani tenant, `oid`, `nonce`, `exp` i `iat`.
- OIDC transient vrednosti žive samo u HttpOnly, Secure, SameSite=Lax serverskoj sesiji i brišu se nakon callback-a.
- Nakon uspeha regenerišu se session ID i CSRF token.
- Access, ID i refresh tokeni se ne čuvaju u aplikacionoj sesiji/bazi i ne upisuju se u log.
- Client secret i authorization code se nikada ne loguju.
- Microsoft identitet ne utiče na role ili permissions.
