Oprávnění na provedení akce s využitím více služeb
- Tirus91
- Člen | 199
Ahoj,
obracím se na vás s takovým pro mne větším oříškem.
Začal jsem přemýšlet o tom, jak celou svojí aplikaci zabezpečit. Pokud bych měl jen jednu aplikaci a ta by neměla možnost nějakých modulů, tak je to celkem jednoduché. Stačilo by využít IAuthorizator::isAllowed() a bylo by po srandě. Ovšem já jsem šel cestou, že mám pár klientů a každý z nich má aplikaci s určitými moduly. Tudíž nikde není vše a každý klient má trochu něco jiného.
Napadlo mne to, že na takové to jádro aplikace bych využil právě IAuthorizator::isAllowed() a tam zapracoval celou logiku.
Ale jak na ty práva v modulech?
Třeba nyní si tu lámu hlavu nad tím, že chci řešit práva na soubory. Kdo
může stáhnout daný soubor, či ho smazat, atp. To mohu vyřešit jednoduše
právě v IAuthorizator::isAllowed(), ale co když mám modul, který mi tu
logiku oprávnění ještě blíže specifikuje? Jako nejednodušší způsob by
bylo využití „kontejneru“ a metody findByType(), ale to není správný
přístup jaký bych měl asi i z pohledu FW využívat.
Věřím, že to tu někdo řešil a budu moc rád za každou radu.
- Tirus91
- Člen | 199
@CZechBoY
abych pravdu řekl, tak asi moc nechápu.
Můj případ je to, že mám někde uložené soubory. V manageru, který obsluhuje právě práci s uložištěm bych si implementoval základní kontrolu oprávnění (např. anonym vs. přihlášený uživatel). Ovšem např. v DMS bych chtěl toto oprávnění rozšířit.
Již mám existující presenter :Front:File:get a ten mi obsluhuje právě získávání souborů. Tento presenter by daný soubor vrátil vždy, jelikož splňuje tu danou logiku.
Dle toho co mi radíš, bych si musel vytvořit ještě nový presenter (např. :Dms:File:get) a do něj si zapracovat novou logiku. Ale ten původní ve FrontModulu by stále soubor vrátil. Tím si přeci otevřu díru k uložišti.
Přijde mi lepší řešení, abych nějakým způsobem zapracoval dynamickou logiku přímo do daného FileManageru, skrze který se přílohy získají odkudkoliv.
Je možné, abych si v Nette service získal všechny služby, které implementují určité rozhraní? To by mi celý problém vyřešilo a bylo by to až dost dynamické. Co se týče ale návrhu, tak mi to právě nepřijde tolik čisté.
Co se týče daného rozhraní, tak jednoduchý příklad
<?php
interface FilePrivileges
{
public function isAllowed($action, UserEntity $user, FileEntity $file);
}
Následně bych mohl vytvořit X Nette Service (každý mohl by mohl rozšiřovat kontrolu uprávnění), které by právě kontrolovali práva. (tohle právě nevím jak udělat v Nette service)
Ano, zde bych si mohl docela sám naběhnout a vytvořit si začarovaný kruh a daný soubor třeba nikdy už nestáhnu.
Doufám, že jsem to dostatečně vysvětlil a podaří se nám najít řešení. Nevěřím tomu, že podobnou situaci nikdo ještě neřešil :)
Ještě mne napadlo to řešit druhou možností. :Front:File:get presenter by vracel obsah jen u těch souborů, které by měli daný příznak (např. module=0). A každý modul, by měl presenter svůj, kde bych využil právě tebou zmiňovanou metodu Presenter::checkRequirements() nebo klidně i IAuthorizator::isAllowed().
Toto řešení se mi ale moc z pohledu DRY nelíbí. Ale asi je to nejjednodušší a nejrychlejší řešení.
Editoval Tirus91 (20. 3. 2017 20:53)
- Jan Tvrdík
- Nette guru | 2595
@Tirus91 Tak za prvé je potřeba si uvědomit, že rozhraní
IAuthorizator
nedává pro většinu aplikací smysl a přestal ho
používat, viz třeba https://forum.nette.org/…r-a-identita
- CZechBoY
- Člen | 3608
Jestli máš ty úložiště po složkách a stačí ti kontrolovat jen
jestli má uživatel právo k danému pložišti tak ti stačí obyč
IAuthorizator s tím, že bys vyhazoval tu výjimku, prostě jak jsem psal
minule.
Jestli potřebuješ každý soubor kontrolovat tak se nejsem jistej jestli brzo
nenarazíš na nějaký výkonový problémy.
Jestli ti nevadí vytvářet nový presentery tak klidně vytvoř ten dms:file:get a překryj tam metodu checkRequirements (+zavolej parent) a udělej ověření podle toho modulu.
- Tirus91
- Člen | 199
@JanTvrdík Honzo díky moc za hint. Určitě si to pročtu a popř. opravím
@CZechBoY Ne, nemám to po složkách. Složky mám virtuální (vazby v DB), ale souborově je to na stejném principu jako má GIT.
Asi abych zajistil oprávnění pro každý model zvlášť, tak budu muset jít cestou skrze nové presentery. Už jen díky tomu, že s kontextem či kontejnerem ve službách či modelech nejde pracovat.
- Oli
- Člen | 1215
Nevím jestli jsem to pochopil dobře, asi ne.
Chceš nějak napsat rozhraní pro kontrolu oprávnění a kdyždý, kdo implementuje to rozhraní by prošel nějakou validací? Aby jsi to měl na jednom místě? Co když potom jednu třídu vytáhneš, implemntuješ rozhraní, ale už ne tu magii, která kontrolovala všechny rozhraní?
Je dost dobře možné, že odpovídám na něco jiného, ale zkusím to. :-)
Každý objekt by měl zodpovídat za svůj stav. Řekněme, že je nějaká
FileService
, která vrátí soubor. Její starostí by mělo být
jaký soubor vrátí a jestli je v pořádku. Pak tu máme nějaký
FileManager
, který spravuje služby a ví, kdo má přístup
k čemu. Jeho starostí by mělo být načíst soubor, zkontrolovat, jestli ho
načíst můžu, a předat presenteru. Presenter by měl dostat soubor nebo
výjimku (FileNotExists, NotAllowedAccess, …) a podle toho se zachovat
(redirect na 403, 404, hláška, že soubor neexistuje, …)
Takhle bych to udělal. Jestli jsem odpovídal na něco uplně jiného, tak se omlouvám :)
- Tirus91
- Člen | 199
@Oli chápeš i nechápeš dobře :)
Ohledně Service a Manager to chápeme oba velmi dobře.
Chci si napsat rozhraní, které někdo implementuje. Daný manažer by si z DI vytáhl všechny služby, které právě toto rozhraní implementují a následně by je všechny volal. Jakmile by nějaký řekl, že daný uživatel na soubor má právo, tak bych ho vrátil. V případě, že bych projel všechny služby a žádná by neřekla, že na soubor má právo, tak bych následně vyhodil příslušnou výjimku.
Ber to hodně zrychleně a hodně špatně, ale věřím, že to pomůže
Metoda FileManager::retrieve() by mohla být nějak takto (psáno jako demonstrace)
public function retrieve(FileEntity $fileEntity, $response = false)
{
//tu bude nejaka ta kontrola napr.
$permisionCheckers = $this->context->findByType('Tirus\\FilePrivileges');
if (count($permisionCheckers) > 0) {
foreach ($permisionCheckers as $permisionChecker) {
if ($permisionChecker->isAllowed('get', $this->userEntity, $fileEntity)) {
if (!$response) {
return $this->getFilePath($fileEntity);
}
return new FileResponse(
$this->getFilePath($fileEntity), $fileEntity->getName(), $fileEntity->getMimetype()
);
}
}
}
throw new \Exception();
}
Editoval Tirus91 (20. 3. 2017 21:42)
- Oli
- Člen | 1215
No a to je podle mě zbytečné. Nevím jaký máš use case, ale evidentně tvoje metoda vrací jeden soubor. A ty víš, jestli uživatel má přístup k souboru nebo ke službě. Tak bych zavolal jednu službu. Případně, pokud neprojde, tak další (s benevolentnějšími pravidly)
Pokud má manažer pracovat s více službami, tak si je injectnout a zkusit jednu po druhé. Rozhodně bych je ale nevytahoval podle interface, protože to je skrytá závislost.