# SEC-ATTACHMENT-ACCESS-CONTROL

Direct HTTP access to ticket attachments is prohibited. All attachment access must pass through the authenticated and authorized download endpoint.

## Analizirani tok

1. `ticket_create.php` i komentar u `ticket_view.php` primaju multipart upload tek posle autentifikacije, CSRF provere i prava pristupa tiketu.
2. `validate_ticket_attachments()` koristi serverski `finfo`, proverava ekstenziju, MIME, XLS/XLSX strukturu, broj fajlova i ukupnu veličinu.
3. `store_ticket_attachments()` fizički čuva fajl u `uploads/tickets` kao `ticket-{ticket_id}-{32 random hex}.{ext}` sa dozvolom `0640`.
4. Baza odvojeno čuva attachment ID, ticket ID, originalno ime, nasumično fizičko ime, MIME i veličinu.
5. `ticket_view.php` prikazuje isključivo link `attachment_download.php?id=ATTACHMENT_ID`.
6. Endpoint učitava attachment i pripadajući tiket iz baze, zahteva sesiju, poziva centralni `can_view_ticket()`, zasebno štiti interne priloge i tek zatim čita fajl.
7. Odgovor ostaje prinudni download (`Content-Disposition: attachment`) kao i pre patch-a.

## Zatvoren problem

- Root `.htaccess` eksplicitno odbija svaki URL koji počinje sa `uploads/tickets`.
- `uploads/tickets/.htaccess` koristi `Options -Indexes` i Apache 2.4 `Require all denied`, uz postojeći Apache 2.2 fallback `Deny from all`.
- Ove zabrane utiču samo na HTTP serviranje; PHP zadržava filesystem pristup.
- Endpoint koristi pozitivan attachment ID, a naziv i ticket ID dobija isključivo iz baze.
- Attachment i konkretan tiket se učitavaju jednim prepared upitom. Promena attachment ID-a zato uvek pokreće novu RBAC proveru za tiket kome taj zapis stvarno pripada.
- `assigned_to` se sada eksplicitno učitava, pa centralna ticket dozvola dobija kompletan tiket.
- Fizičko ime mora odgovarati postojećem random formatu i sadržati isti ticket ID kao bazni zapis. `realpath()` i provera dozvoljenog prefiksa sprečavaju izlazak iz `uploads/tickets`, uključujući symlink/path-traversal slučajeve.
- Originalno ime se koristi samo za `Content-Disposition`, nakon uklanjanja kontrolnih znakova, navodnika i backslash-a.
- Neuspeli autorizovani pokušaji i nevalidna fizička imena beleže user/attachment/ticket ID bez session podataka i sadržaja fajla.

## Deployment

1. Napraviti backup aplikacije, baze i persistent `uploads` foldera.
2. Deploy-ovati root `.htaccess`, `attachment_download.php` i ovaj source patch.
3. Pošto `.productionignore` namerno čuva postojeći `uploads` folder, posebno potvrditi da produkcioni `uploads/tickets/.htaccess` sadrži ovaj projekatni fajl. Root rewrite ostaje dodatna zaštita.
4. Potvrditi da Apache dozvoljava potrebne `.htaccess` direktive (`AllowOverride` za rewrite/authz/options). Ako hosting ignoriše `.htaccess`, ista pravila postaviti u VirtualHost konfiguraciju.
5. Očistiti eventualni CDN/proxy cache za `/uploads/tickets/*`.

## Obavezni staging testovi

- Direktan zahtev `/uploads/tickets/postojeci-fajl.pdf` vraća `403` i bez sesije i sa sesijom.
- Autorizovani vlasnik, učesnik, zaduženi, odgovorna uloga i administrator preuzimaju dozvoljeni javni prilog preko endpoint-a.
- Korisnik bez pristupa dobija postojeći enumeration-safe `404`.
- Promena `id=X` na ID priloga drugog tiketa ne daje pristup.
- Neprijavljen korisnik prati postojeću `require_auth()` konvenciju i odlazi na login.
- Nevalidan ID, string i traversal vrednosti vraćaju `404` bez otkrivanja server putanje.
- Interni prilog ostaje dostupan samo korisniku koji može da upravlja tiketom.
- Postojeći JPG/PDF i novi PDF/JPG/PNG/XLSX prolaze kroz endpoint, dok njihov direktan URL ostaje zabranjen.

Lokalno okruženje nema Apache, PHP CLI ni živu MariaDB instancu, pa stvarni HTTP status, session i DB download moraju biti potvrđeni na stagingu. `qa_attachment_access_control_v3_25_1.js` proverava konfiguraciju i ceo statički bezbednosni tok.

## Naredna faza

Najbezbednija dugoročna arhitektura je storage potpuno izvan web root-a. Postojeći fajlovi nisu automatski premeštani jer bi to zahtevalo koordinisanu produkcionu migraciju putanja, dozvola, backupa i rollback-a.
