Atributy pro zápis sloupců#
Kdo smí zvenčí přepsat konkrétní sloupec. Generické updateColumns je záměrně jen pro
administrátora — front přes něj ale legitimně mění pár počítadel a příznaků. Výjimku
proto nese sloupec sám, ve formě PHP atributu na property entity.
Atributy jsou v app/Core/Attributes/Hydration/, vyhodnocuje je
app/Core/Traits/Api/Presenters/SaveTrait.php.
Dvě generace atributů vedle sebe#
| Atribut | Výskytů | Role |
|---|---|---|
#[ExternalReadonly] |
474 | sloupec nesmí přijít zvenčí (hodnotu určuje server) |
#[ExternalAdminOnly] |
16 | smí ho zapsat jen privilegovaný volající |
#[PubliclyUpdatable] |
4 | přes updateColumns ho smí zapsat kdokoli |
#[ConditionallyUpdatable] |
4 | rozhodne pojmenovaná metoda Api presenteru |
#[FieldAccess] |
12 | nová, sjednocující — tři osy read / insert / update |
Stará čtveřice se neruší, jen se do ní nepřidává
#[FieldAccess] je cílový tvar a čte se oboje; plošná migrace 474 výskytů
přijde až s testem parity. Do nového kódu piš FieldAccess, hotová místa nepřepisuj
kvůli kosmetice. Podrobně o publiku polí v RBAC v API.
Stará čtveřice platí jen na EXTERNÍ cestě, FieldAccess všude
Staré stripy běží výhradně při $meta['__external'], což nastavuje jen
actionSave(). FieldAccess je naopak podle role, takže platí i na interní
cestě. Není to nedopatření, je to důvod, proč nový atribut vznikl.
Pořadí rozhodování u updateColumns#
- Oprávnění
updateColumns— projde-li, atributy se vůbec nečtou. - Teprve při zamítnutí se prochází sloupec po sloupci a hledá se atribut.
- Stačí jeden sloupec bez atributu a padá celé volání. Žádný částečný zápis.
Povolení přes atribut se loguje
Do auth kanálu přibude řádek API_COLATTR. Bez něj by v logu bylo zamítnutí
následované úspěšným zápisem — a log by lhal.
#[PubliclyUpdatable]#
„Tenhle sloupec smí zapsat kdokoli.“
#[ORM\Column(name: 'reported', type: 'boolean')]
#[ExternalReadonly]
#[PubliclyUpdatable]
protected bool $reported = false;
Typický případ je příznak „nahlášeno“ u inzerátu nebo příspěvku.
Povoluje LIBOVOLNOU hodnotu, ne jen rozumnou
Proto se nehodí na počítadla — votesUp by šlo nastavit na milion jedním
požadavkem. Počítadla se zvyšují akcí registerEvent, která dělá serverový
+ 1.
Nekontroluje vlastnictví záznamu
Nezkoumá, komu záznam patří. Když na tom záleží, je to případ pro podmíněnou variantu.
#[ConditionallyUpdatable]#
„Pravidlo pro tenhle sloupec je tamhle.“
A na Api presenteru:
Dnes to používá příznak „přečteno“ u odpovědi na inzerát a u odpovědi v katalogu firem — tedy případ, kdy vlastník není na tomhle záznamu, ale na jeho rodiči.
Rozhodujte proti databázi, ne proti požadavku
$id říká jen, o kterém záznamu se bavíme. Cokoli dalšího z požadavku je tvrzení
klienta — kontrola nad hodnotou z téhož požadavku neověřuje nic.
Jméno metody se PÍŠE, neodvozuje
Konvence typu read → checkReadPermission() je past: překlep by se neprojevil
chybou, ale tichým zamítnutím s hláškou „nemáš práva“. Existenci metod hlídá
tests/Unit/Security/ConditionallyUpdatableContractTest.php.
Fail-closed: cokoli nejasného je zamítnutí
Jedno false u kteréhokoli id shodí celé volání. Chybějící záznam i výjimka
v kontrole se počítají jako zamítnutí.
Postup: potřebuju pustit zápis jednoho sloupce zvenčí#
- Zeptej se nejdřív, jestli je to opravdu sloupec. Zvýšení počítadla je
registerEvent; zápis s obchodní logikou je vlastní Api akce s vlastním klíčem oprávnění. - Rozhodni podle tabulky níž, který atribut to je.
- Napiš ho na property ve sdílené base entitě — aby platil pro všechny sekce.
- U podmíněné varianty napiš metodu na Api presenteru a rozhodni v ní dotazem do databáze.
- Ověř obojí — že povolený zápis projde a že zamítnutý skutečně padá.
- Zkontroluj log: povolení přes atribut musí být vidět jako
API_COLATTR.
| Situace | Řešení |
|---|---|
| zápis smí kdokoli a hodnota nemá stupně | #[PubliclyUpdatable] |
| zápis smí jen někdo konkrétní | #[ConditionallyUpdatable] |
| počítadlo, které se má zvýšit o 1 | ani jeden — akce registerEvent |
| zápis s vlastní obchodní logikou | ani jeden — vlastní Api akce |
| hodnotu určuje výhradně server | #[ExternalReadonly] |
Atribut na property znamená: kdokoli s přihlášením, kolikrát chce
Sedí na entitě a čte ho hranice, kterou procházejí obě cesty. Označit sloupec není „povolit jednomu formuláři“ — je to plošné povolení.
Property nesmí nést oba atributy naráz
Nebylo by jasné, které pravidlo platí. Hlídá to týž test.
Pokrytí hlídají strukturální testy#
| Test | Co kontroluje |
|---|---|
ExternalReadonlyCoverageTest |
že server-počítaná pole mají #[ExternalReadonly] |
ExternalAdminOnlyCoverageTest |
totéž pro admin-only sloupce |
ConditionallyUpdatableContractTest |
že pojmenovaná kontrolní metoda existuje |
FieldAccessCoverageTest |
že pole, jehož jméno vypadá jako tajemství, je anotované nebo má výjimku s důvodem |
Pokrytí podle jména pole je síto, ne ochrana
Test pozná password nebo secret. Pole s nevinným jménem, které přesto nese
tajemství (meta, config, note), musí anotovat člověk.
Kam sáhnout#
| Chci | Kde |
|---|---|
| definice atributů | app/Core/Attributes/Hydration/ |
| vyhodnocení při zápisu | app/Core/Traits/Api/Presenters/SaveTrait.php |
| ořez polí podle oprávnění | tamtéž → stripExternalReadonlyFields(), stripExternalAdminOnlyFields() |
| testy pokrytí | tests/Unit/Security/ |
Navazující kapitoly: RBAC v API · Hydrátory · Životní cyklus manageru