Automatická registrace komponenty

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Zadražil
Člen | 62
+
0
-

Dobrý den,

začíná se mi kupit v BasePresenteru spousta kódu kvůli komponentám. Rád bych nějak vyřešil automatické registrování komponent, ale nevím jak. Našel jsem následující kód (který jsem umístil do BasePresenteru, ale ten mi z nějakého důvodu nefunguje (komponentu to nezaregistruje).

protected function createComponent($name)
	{
		$component = parent::createComponent($name);
		if ($component === NULL && class_exists($name)) {
			$component = new $name;
		}

		return $component;
	}

Díky za jakékoli nakopnutí.

Editoval David Zadražil (3. 8. 2013 12:02)

Jan Tvrdík
Nette guru | 2595
+
0
-

@David Zadražil: Blahopřeji, pravě jsi v aplikaci vyrobil bezpečnostní chybu.

Viz také https://forum.nette.org/…s-komponenty

pepakriz
Člen | 246
+
0
-

Používám toto řešení: https://github.com/…resenter.php#L175

mám službu widgetManager, do které přes neon registruji továrničky na komponenty. Když v presenteru neexistuje továrnička na komponentu, vyzkouším ještě, jestli náhodou neexistuje ve widgetManageru.

David Zadražil
Člen | 62
+
0
-

@Jan Tvrdík: Díky, později mi to došlo že to nebude nejvhodnější řešení.

@pepakriz: Vyzkouším, díky.

Šaman
Člen | 2666
+
0
-

Požívám podobné řešení jako popisuje Pepa, jen na to nemám žádný manager a jen tu továrničku hledám v configu. Tohle je v BaseComponent, obdobná metoda je i v BasePresenteru:

<?php
/**
 * Supertovárnička na komponenty
 * Pokud neexistuje volaná komponenta, zkusí se zavolat továrnička z configu
 *
 * @param	string	$name	třida komponenty
 * @return	IComponent
 */
protected function createComponent($name)
{
	$controlName = ucfirst($name);
	if (method_exists($this, "createComponent" . $controlName))
	{
		return parent::createComponent($name);
	}
	else
	{
		return $this->presenter->context->createService($name);
	}
}
?>

V configu pak stačí:

services:
	registrationForm: App\Controls\RegistrationForm
	lostPasswordForm: App\Controls\LostPasswordForm

Editoval Šaman (3. 8. 2013 14:46)

Jan Tvrdík
Nette guru | 2595
+
0
-

@Šaman: Blahopřeji, pravě jsi v aplikaci vyrobil bezpečnostní chybu.

Viz také https://forum.nette.org/…s-komponenty


Koukám, že Vojta ti to vysvětloval už před 11 měsíci a pak znovu před 3 měsíci. Takže teď to zkusím já.

Ta tvoje „supertovárnička“ je špatně ze dvou důvodů

  1. Nerespektuje API definované třídou UI\Presenter, od které dědí. Součástí toho API je totiž i požadavek, že v případě, že metoda createComponent není schopna požadovanou komponentu vytvořit, tak by měla vrátit NULL, nikoliv vyhodit výjimku. Dále pak existuje požadavek, že když už vracíš hodnotu, který není NULL, tak se nutně musí jednat o instanci IComponent. To taky porušuješ.
  2. Z bezpečnostního hlediska – za žádných okolností bys uživateli neměl dát možnost vytvářet instance libovolné třídy nebo služby. Uvědom si, že tím, že se vytvoří služba se kaskádově vytvoří (nejsou-li již vytvořeny) i všechny její závislosti. Tedy dáváš uživateli možnost zavolat obrovské množství konstruktorů, injekt metod, setterů a vůbec všeho, co v configu máš. Viz také dva roky staré vysvětlení od Honzy Marka.

Editoval Jan Tvrdík (3. 8. 2013 15:25)

Šaman
Člen | 2666
+
0
-

Kdybych toho tam měl skutečně velké množství, vytvořil bych extenzi, kde by byly jen volatelné komponenty. Porušení API mi nedošlo, opravím to.
Ještě jsi zapomněl zmínit, že volám context a tím skrývám závislost komponenty (presenteru) na jiné komponentě.

Na druhou stranu množství (i když ne obrovské) továrniček v nějakém BasePresenteru (pro společné komponenty typicky vykreslované v layoutu), nebo opakujících se továrniček po různých presenterech mi vadilo. Tohle tento problém zcela eliminovalo za cenu skrytí závislosti a možnosti volat kteroukoliv komponenty odevšad. (To API půjde spravit.)
Takže pokud vím, že nějaká komponenta nemá být volatelná odkudkoliv, nedávám jí do configu (mám je i oddělené do zvláštního souboru, aby toto řešení bylo co nejvíce přehledné).

Ideálním řešením by asi bylo mít nějakého toho manažera komponent a toho předat jako závislost komponentám/presenterům. A ten by měl nějak definováno co kdo a za jakých podmínek může komponentu vytvořit. Ale pro klasické, často jen informační komponenty, které používám mi toto stále nebezpečné nepřijde. Vojta už mi to dvakrát vysvětloval na příkladu, který je mimo můj způsob práce. Například já kontroluji práva na akci/signál ve všech presenterech přes ACL. Pokud uživatel nemá právo na metodu delete! komponenty userDataGrid, tak mu vypíšu nedostatečná oprávnění. Zůstává mu jen možnost zobrazit komponentu kdekoliv, nicméně komponenta by si sama měla kontrolovat, jestli nevypisuje něco, co nesmí. To už je ale na jinou diskuzi.

Takže bezpečnostní chyba tím vzniknout může za určitých podmínek, ale také nemusí, pokud si rizika rohoto řešení eliminuji. Mimochodem pokud dám továrničku createComponentUserDataGrid() do Basepresenteru, tak mám stejnou bezpečnostní díru bez velkého humbuku.

Editoval Šaman (3. 8. 2013 19:34)

Filip Procházka
Moderator | 4668
+
0
-

Ideální řešení je takové, kde ty továrničky napíšeš ručně. A ideálně pouze v presenterech, kde je reálně používáš.

Jan Tvrdík
Nette guru | 2595
+
0
-

Ještě jsi zapomněl zmínit, že volám context a tím skrývám závislost komponenty (presenteru) na jiné komponentě.

Protože to není obecně problém. DI není svatý grál a tedy nelze obecně tvrdit, že nepoužívání DI je špatně.

Na druhou stranu množství (…) továrniček v nějakém BasePresenteru (…), nebo opakujících se továrniček po různých presenterech mi vadilo.

Problém jsi viděl správně, řešení jsi nicméně vymyslel špatně.

Takže pokud vím, že nějaká komponenta nemá být volatelná odkudkoliv, nedávám jí do configu

Jenomže ty to vůbec nemáš omezené na komponenty, ale umožňuješ přístup úplně ke všem službám. Kdyby jsi ty komponenty, které chceš mít globálně dostupné, měl označené nějakým tagem a umožnil takto získat jen komponenty s daným tagem, tak by situace byla výrazně lepší. Stejně tak by bylo v pořádku, kdybys nad každým presenterem měl anotaci @allowedComponents, kde bys měl vyjmenované komponenty, které se mohou nad daným presenterem vytvořit.

Takže bezpečnostní chyba tím vzniknout může za určitých podmínek, ale také nemusí, pokud si rizika rohoto řešení eliminuji.

Mýlíš se. Bezpečnostní chyba, kterou nejde (momentálně) zneužít, je pořád bezpečnostní chyba. Pokud máš v aplikaci SQL injection zranitelnost, kterou nejde nijak zneužít, tak to nic nemění na faktu, že ta SQL injection je bezpečnostní chyba. Nehledě na to, že je velmi náročné hlavou propočítat / promyslet skutečně do hloubky, že danou bezpečnostní chybu nejde nijak zneužít. Spoléhat se na tuto nejistotu mi přijde velmi naivní. Je nesmírně náročně garantovat do budoucna, že se při nějakém refactoringu (který navíc nemusíš dělat ty) nestane ta bezpečnostní chyba zneužitelná.

Šaman
Člen | 2666
+
0
-

Dík za analýzu, asi to přes ten tag, nebo rozšíření udělám. I ta anotace – nebo možná virtuální metoda createComponentFoo() – je dobrý nápad. Navíc tím budou závislosti někde vypsané, takže nebudou skryté. (DI se snažím dodržovat, mám ho jen ve výše popsané metodě. Ono se to fakt pak líp ladí, když vím, de se mi co vzalo.)

Někde jsem četl, že se plánuje přidat do konfigurace i nastavování presenterů. Pak by se daly ty závislosti na komponentách zapsat přímo tam.