Znovupoužitelné komponenty a autorizace

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
jannek19
Člen | 47
+
+1
-

Ahoj,

dejme tomu, že mám komponentu pro výpis seznamu uživatelů, u každého uživatele je tlačítko pro jeho smazání. Tlačítko obsahuje odkaz na signál komponenty, který provede smazání uživatele z DB. Jak co nejlépe ověřit, že má právě přihlášený uživatel právo konkrétní záznam smazat? Komponenta by měla být znovupoužitelná a připravená na různé metody ověření přístupu.

Napadají mě následující řešení:

  1. napsal bych si autorizátor implementující dohodnutý interface. Komponenta by vždy vyžadovala injectnutí odpovídající služby (autorizátoru) a ověření by delegovala na tuto službu
  2. komponenta by nevěděla nic o žádném autorizátoru, jen by poskytovala událost onAuthorize a při ověření oprávnění by vždy odpálila tuto událost. Callbacky navěšené na tuto událost by ověřily oprávnění a pro odepření přístupu by vyhodily ForbiddenException.

Která varianta je podle vás vhodnější?

Editoval jannek19 (6. 8. 2014 22:30)

jannek19
Člen | 47
+
0
-

Nikdo nic? :( Ke kterému řešení byste se přikláněli vy? Ke vstřikování autorizátoru, nebo k použití události? Co je podle vás vhodnější? Řešil jste někdo něco podobného? Pokud tady plácám blbosti, tak se omlouvám.

Tomáš Votruba
Moderator | 1114
+
0
-

Možná pomůže stručnější dotaz.

Šaman
Člen | 2640
+
+1
-

3. Komponenta sama o authorizaci nic netuší, případná práva na její zobrazení hlídá továrnička v presenteru. Kontrolu práv před smazáním si kontroluje samotný model. Komponenta tedy z práv zná jen výjimku, kterou má odchytit a vypsat že uživatel nemá práva.

enumag
Člen | 2118
+
0
-

Nesouhlasím. Kontrola oprávnění nemá v modelu co dělat. Model je jen API jak pokládat databázi dotazy. Zda je dotaz oprávněný si má ošetřit volající, tedy komponenta či presenter.

Editoval enumag (7. 8. 2014 0:56)

Šaman
Člen | 2640
+
0
-

S tím zase nesouhlasím já. Model není jen nějaký dotazovač, tím je ORM. Model by měl být jádro aplikace, který si sám hlídá konzistenci a chrání bezpečnost. Modelu je sice jedno, jestli se uživatel identifikuje heslem, tokenem, nebo čím chce. Ale jestli ten uživatel může něco dělat, to by si měla hlídat nějaká vrstva hned nad repository. (Ano, repository se má jen starat o db (resp. úložiště) a nikoliv o oprávnění. Ale model ≠ repository.)

Jinak oprávnění na zobrazení komponenty/pohledu si samozřejmě řeší presenter. Ale i kdyby se zobrazilo třeba mazací tlačítko uživatelovi, který nemá práva mazat, tak to nesmí ohrozit bezpečnost modelu.

Editoval Šaman (7. 8. 2014 7:00)

jannek19
Člen | 47
+
0
-

Good point, příspěvek jsem zkusil zkrátit.

jannek19
Člen | 47
+
0
-

Díky za odpovědi. Svým způsobem mi nová vrstva mezi komponentou a samotným repository, která se by se postarala o autorizaci dává smysl, ale nejsem si jistý, že to řeší můj problém. Kontrolu v továrničce v presenteru samozřejmě provedu, ale tím maximálně zabráním tomu, aby se ta komponenta nevytvořila, neřeší to problém, kdy mám v komponentě signál a právě v něm musím rozhodnout jestli má uživatel právo operaci dokončit, nebo ne. Logika celé operace je v komponentě, data, o kterých rozhoduji, jsou v komponentě, tak musím podle mě v komponentě také rozhodnout, nebo toto rozhodnutí delegovat někam dál. Ale právě si moc nevím rady s konkrétním řešením :-/ Ale ještě nad tím popřemýšlím, třeba je ta vrstva mezi komponentou a repository právě to, co hledám.

Zax
Člen | 370
+
0
-

To bohužel řeší jen „smí/nesmí vytvořit komponentu“. Často ale chceme přímo v komponentě rozhodnout, jestli něco zobrazíme jako text, nebo text s odkazem na editaci. Nebo se chceme rozhodnout, jestli zobrazit editační/mazací tlačítka v seznamu, ale nechceme kvůli tomu dělat samostatnou komponentu (důvody neřešme).

Osobně používám metodu 1. Určitě existují i lepší řešení, ale přijde mi to vhodné („good enough“). Můžu se pak na oprávnění dotazovat i v šabloně (byť to není úplně ideální).

Ještě jsem přemýšlel (v podstatě metoda 1. jen trochu konkrétnější), že bych pro každou komponentu udělal vlastní něco-jako-authorizátor, v podstatě službu, která bude mít konkrétní metody (definované třeba už v interface, aby šlo libovolně měnit implementaci) určené na dotazování z komponenty např.

canEditAllArticles()
canEditArticle($id) // nebo canEditArticle(Article $article)
canDeleteAllArticles()
...

A tento autorizátor by se mohl vnitřně dotazovat jak na Nette authorizátor, tak třeba i na model (uživatel smí upravovat a mazat jen své články, admin smí vše…).

Osobně by mě taky zajímalo nějaké pěkné univerzální řešení.

Editoval Zax (9. 8. 2014 17:42)

Jan Suchánek
Člen | 404
+
0
-

Me se libi resit to eventem signalu komponenty napr. beforeDelete primo v prezenteru.

Prezenter rozhodne a komponenta nemusi resit pri signalu nic.

Šaman
Člen | 2640
+
0
-

Pokud řešíš viditelnost třeba mazacích tlačítek, tak samozřejmě komponenta potřebuje authorizator. Je to naprosto relevantní požadavek. Komponenta, nebo presenter, to je jedno, oba by si měli hlídat práva, jen si myslím, že tam dojde snadněji k chybě (mnoho kontrol na různých místech), než v modelu, kde všechny požadavky na repository nejdřív profiltruje vrstva která má authorizátor. Takže tu vrstvu v modelu beru jako hlavní, která ručí za konzistenci dat, a tu kontrolu v presenteru/komponentě spíš jako frontendovou záležitost.

jannek19
Člen | 47
+
0
-

Event se mi taky na pohled celkem dost líbí, problém je v tom, že se na něj nemůžeš spolehnout. To pak přichází v úvahu ještě nějaká autorizační vrstva mezi komponentou a modelem (jak popisuje např. Šaman), která ti zaručí, že nedojde k operaci, ke které dojít nemá. Problém s eventem je ten, že nesmíš zapomenout na něj navěsit v presenteru (nebo někde jinde) odpovídající kontroly – 100× nezapomeneš, ale po 101 můžeš a hned je bezpečnostní díra na světě. Takže event maximálně jako nějakou doplňkovou záležitost, která třeba zabrání vykreslení nějakého prvku (odkazu, tlačítka), ale nesmíš na ní přenést celou odpovědnost za autorizaci.

Jan Suchánek
Člen | 404
+
0
-

@jannek19 No já jsem ten event řešil přímo u komponenty při tvorbě v presenteru.

protected function createComponentExample(){
	$control = $this->exampleFactory->create();
	$control->onBeforeAction[] = $this->checkSecurity; // a beforeAction provedu vdyž před akci tím pádem komponenta jí nebude muset řešit.
	return $control;
}

Lde bych na to zapoměl? V komponentě na to mohu zapomenout uplně stejne nebo ne?

Editoval jenicek (18. 8. 2014 16:52)

jannek19
Člen | 47
+
0
-

Právě v tom presenteru. Já vím – jaká je pravděpodobnost, že na něco tak očividného zapomeneš, ale stát se to IMHO může. Jinak ten způsob, že bych si to navěsil takhle v presenteru se mi právě hodně líbí.

Editoval jannek19 (19. 8. 2014 15:39)

mrtnzlml
Člen | 140
+
0
-

Přesně takto jsem se to také pokusil vyřešit (zde), ale vůbec se mi to nelíbí, protože právě hrozí to, že se na něco zapomene, což je podle mě až moc velký a zbytečný risk…

Jan Suchánek
Člen | 404
+
0
-

Nechtělo se mi to řešit přez Kdyby\Events, a myslel jsem to nějak takto:

class ExamplePresenter
{
	protected createComponentExample()
	{
		$control = $this->exampleFactory->create($this->user->id);
		$control->onRestriction[] = function($control){
			if($isAllowed = $this->user->isAllowed( ... )){
				$control->setEditable();
			}
		};
		retrun $control;
	}
}

a v komponentě už jen pouštet $this->onRestriction() a v ty restrikci by se dali předávat i ty parametry restrikce, ale asi je to zbytečné delegování zodpovědnosti někam jinam, a zatěžování presenteru.

Možná má tedy ten Šamanuv způsob řešit restrikce přímo v modelu něco do sebe.

Editoval jenicek (20. 8. 2014 14:40)

akadlec
Člen | 1326
+
+1
-

No že vám do toho skáču, ale jak to pak řešíte v templatě? Tam by to taky mělo být „nějak“ k dispozici aby template věděla zda ten editační button zobrazit či nikoliv ne?

Jan Suchánek
Člen | 404
+
0
-

Ok presne to delam tim editable, ktere predavam do sablony komponenty eventem a v komponente tedy nepracuji s autorizaci. Je to blbe?

Mozna staci jen setEditable pro komponentu a defaultne ji mit needitovatelnou.

Editoval jenicek (21. 8. 2014 10:40)