widget, předání parametru komponentě, potvrzení smazání

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

Dobrý den, prosím o radu. Mám na stránce mnoho položek a vedle každé tlačítko „smazat“. Klikne se na tlačítko, zeptáme se, jestli je to myšleno vážně, smaže se, přesměruje. Nevím ale, jak formuláři předat parametr ID ke smazání.

V presenteru jsem k tomu účelu napsal továrnu na formulář:

protected function createComponentDelForm() {
    $form = new AppForm();
    $form->addHidden('id'); //<< jak sem dostat toto?
    $form->addSubmit('del', 'smazat')
         ->getControlPrototype()->onclick('return confirm(\'Skutečně smazat?\');');
    $form->onSubmit[] = array($this, 'delForm_Submit');
    return $form;}

public function delForm_Submit(Form $form) {
    if ($form['del']->isSubmittedBy()) {
    $values = $form->getValues();
    //zavolat model->smazat podle $values['id']
    }
    $this->redirect('this');}

Šablona:

{foreach $polozka as $p}
{$p->nazev} {control oznaceniDelForm} {* << nějak předat $p->id *}
{/foreach}
Jod
Člen | 701
+
0
-
{?$this['oznaceniDelForm']['id']->value = $p->id}
jarks
Člen | 94
+
0
-

Jod napsal(a):

{?$this['delForm']['id']->value = $p->id}

Díky, ale to není ono. Tohle dá jen „Creating default object from empty value.“ A nic se nepřenese. Componentu vidím na výpise {dump} ze šablony jako „components“, „delForm“ ⇒ object(AppForm), ale nevím, jak se k ní dostat.

Edit: Už to mám: místo $this musí být $component, takto: {?$component['delForm']['id']->value = $p->id}

Editoval jarks (19. 8. 2009 12:33)

jarks
Člen | 94
+
0
-

(Třeba se bude někomu hodit)

ŘEŠENÍ: smazání položky s vyskakovacím dotazem:

Presenter:

//Komponenta formuláře:
protected function createComponentDelForm() {
    $form = new AppForm();
    $form->addHidden('id');
    $form->addSubmit('del', 'smazat')
         ->getControlPrototype()->onclick('return confirm(\'Skutečně smazat?\');');
    $form->onSubmit[] = array($this, 'delForm_Submit');
    return $form;}

//Obsluha formuláře:
public function delForm_Submit(Form $form) {
    if ($form['del']->isSubmittedBy()) {
    $values = $form->getValues();
    if (isset($values['id'])) $this->getModel()->deletePolozka($values['id']); //getModel() vytváří instanci modelu
    }
    $this->redirect('this');}

Model:

public function deletePolozka($id)
{return $this->connection->delete('tabulkapolozek')->where('id=%i', $id)->execute();}

Šablona:

{foreach $polozka as $p}
	{$p->nazev}
	{?$component['delForm']['id']->value = $p->id} {*//<< formuláři předáme ID*}
	{control delForm}<br/>
{/foreach}
Jod
Člen | 701
+
0
-

Sry myslel som $presenter miesto $this :)

Btw ja to confirm používam na jednom odkaze a zabere to jeden riadok, miesto 50 ;)

Editoval Jod (19. 8. 2009 13:14)

redhead
Člen | 1313
+
0
-

Taky nějak nechápu využití AppFormu. Dalo by se využít i signálu. A bylo by to rozhodně kratší a intuitivnější:

public function handleDelete($id)
{
	//smazani prvku podle id...
}

a v šabloně jenom

{foreach $polozky as $p}
	<a href="{link delete! $p->id}" onclick="return confirm('Smazat???')">smazat</a>
{/foreach}

(doufam ze tam neni chyba, pisu to z pameti)

Editoval redhead (19. 8. 2009 13:30)

jarks
Člen | 94
+
0
-

redhead napsal(a):

Taky nějak nechápu využití AppFormu…

Hm. To je mnohem lepší, díky. Aspoň jsem se naučil, jak se dá přistupovat z formuláře ke komponentě.

Cifro
Člen | 245
+
0
-

redhead napsal(a):

	<a href="{link delete! $p->id}" onclick="return confirm('Smazat???')">smazat</a>

Nevznika tak bezpečnostné riziko, že cez url sa bude dať mazať? Treba to ošetriť ešte.

jarks
Člen | 94
+
0
-

Cifro napsal(a):
Nevznika tak bezpečnostné riziko, že cez url sa bude dať mazať? Treba to ošetriť ešte.

Odkaz na smazání se objevuje jen přihlášenému uživateli. Nebo myslíte nějaké další ošetření?

kravčo
Člen | 721
+
0
-

jarks napsal(a):

Odkaz na smazání se objevuje jen přihlášenému uživateli. Nebo myslíte nějaké další ošetření?

V prípade, že máš aplikáciu s viacerými používateľmi, jeden z nich si môže tvar odkazu zapamätať a jednoducho premáznuť ostatných…

nAS
Člen | 277
+
0
-

Cifro napsal(a):

redhead napsal(a):

	<a href="{link delete! $p->id}" onclick="return confirm('Smazat???')">smazat</a>

Nevznika tak bezpečnostné riziko, že cez url sa bude dať mazať? Treba to ošetriť ešte.

Přesně tak, doporučuji nastudovat CSRF útok

jasir
Člen | 746
+
0
-

Jestli by nestálo za to vymyslet do nette nějaké „protected“ linky, něco jako umí form $form->addProtection(), tak nějak podobně. A pak mít v CurlyBrackets makra {safelink}, {safeplink}. Prostě zabránit uživateli snadné změně parametrů v url.

Editoval jasir (19. 8. 2009 20:28)

Honza Kuchař
Člen | 1662
+
0
-

Edit: Už to mám: místo $this musí být $component, takto: {?$component[‚delForm‘][‚id‘]->value = $p->id}

Používej $control. $component je deprecated.

redhead
Člen | 1313
+
0
-

imho pokud má user/admin nastavená nějaká práva, tak mu změna parametrů v url bude stejně k ničemu…

u uživatelů obecně to samozřejmě díra je.

(ale můžu se plést, aspoň se přiučim.. ;) )

Editoval redhead (19. 8. 2009 21:35)

Honza Kuchař
Člen | 1662
+
0
-

jasir napsal(a):

Jestli by nestálo za to vymyslet do nette nějaké „protected“ linky, něco jako umí form $form->addProtection(), tak nějak podobně. A pak mít v CurlyBrackets makra {safelink}, {safeplink}. Prostě zabránit uživateli snadné změně parametrů v url.

No špatné by to nebylo. Kdyby se třeba paramtry uložily do cache a potom by se v url předalo něco jako _sid. Ale je to naprosto neSEO.

redhead
Člen | 1313
+
0
-

Docela mě tahle problematika okolo CSRF zajímala, a tak jsem přes večer připsal kód PresenterComponent::safelink a makra pro CB {safelink} a {safeplink} jak jste navrhovali, a funguje mi to celkem hezky!

Je to založeno na posílání tokenů v url, které se poté kontrolují.
Máme sice nehezký hash v url, ale relativně bezpečnou aplikaci :)

Jediné co bych chtěl nějak dořešit, jak by se měla aplikace chovat, pokud token například vypršel nebo došlo ke změně v url (předán špatný token).
Řekl bych že by se měla vrátit na původní stránku. Ale jak to řešit elegantně? (Re)StoreRequest?? A co potom pokud minulá stránka například přidala něco do košíku, nebo provedla jinou akci pomocí signálu. Tato akce by se pak nechtěně zopakovala, nebo ne?

A druhá věc ještě je, jak ‚označit‘ action/signál, že by měl být chráněn. Zatím to řeším pomocí metody BasePresenter::isSafe(), kterou volám na začátku každé chráněné action/handle metody, pokud safe není (token nesouhlasí) volám $this->terminate() – což je to nedořešené chování.

Ještě mě napadlo je značit anotacema, a tím by mohlo vlastně odpadnout i volání safelink, bylo by ale nutné upravit metodu Presenter::createRequest(), aby našla tu anotaci a pokud by ji našla přidal by se token do url.

Co si o tom myslíte?? :)

JakubKohout
Člen | 92
+
0
-

No píšete že to neni SEO řešení … ale SEO je pro vyhledávače (aspoň sem si to do teď myslel) a google bot by se určitě neměl dostat k nějaký stránce která provádí akci (například smaže položku), takže myslim že ochránění pomocí tokenu je dobrej nápad.

Mimochodem, je vám jasné co by se stalo kdybych vzal ten link, přepsal na ID který chci smazat a umístil ho na svoje stránky do neviditelnýho img. A potom jenom čekal až se potencionální oběť objeví na mejch webovkách…

A určitě sem pro aby se přidal nějaký sLink (safeLink) kterej by na konec url vkládal proměnou s nějakym hashem nebo by klidně stačilo SESSIONID …

paranoiq
Člen | 392
+
0
-

ad safelink: myslím, že už se to tu někde probíralo. odkaz přes GET není moc snadné zabezpečit proti CSFR. token může uniknout z webu třeba přes nějaký odkaz HTTP hlavičkou Referer (a nevím jistě jestli i iframe z cizí domény nemůže přečíst URL nadřazené stránky. obsah číst nesmí). každopádně by po takové GET akci mělo následovat přesměrování na adresu bez tokenu

ad seo: presměrování!

@dRaGen: „kdybych vzal ten link“ – a kde ho vezmete? (Referer!)

nesmí jít o hash. token musí být náhodný řetězec. session u CSFR nic neřeší

Editoval paranoiq (28. 8. 2009 9:41)

redhead
Člen | 1313
+
0
-

četli jste vůbec ten můj příspěvek? ;) Nebo radši chcete vidět kód. Asi by řekl víc, ale nechtěl jsem ho zbytečně zveřejňovat, pokud bych ještě něco upravoval, třeba to chování.

ad hash: také může být složen z náhodných znaků ne? :D

ad session: přes session určitě ne.

ad seo: myslím, že zabývat se seo u administračních úkonů není potřeba. Pokud bereme například google, a vkládáme na web nějaký sitemap.xml, tak tam asi dávat linky na smazání nebudeme. :)

Jinak vycházím z kódu co napsal Jakub Vrána, s použitím tokenů ukládaných do db.

Editoval redhead (28. 8. 2009 12:36)

jasir
Člen | 746
+
0
-

Také se domnívám, že o SEO v tomto případě vůbec nejde :-). Co by to chtělo je funkčnost, která zajistí nemožnost manuální změny parametrů, tzn. ten přidávaný hash generovat (také) na základě ostatních paramtrů v linku.

Editoval jasir (28. 8. 2009 12:53)

jasir
Člen | 746
+
0
-

Pokusil jsem se o implementaci ochrany změny parametrů linků (tj. např. uživatel nemůže změnit například parametr id linku směřující na handleDelete($id)).

Nakonec jsem zavrhl možné makro safelink (protože do šablony zřejmě nepatří ochrana aplikace). Způsob, který jsem tedy zvolil, je označení handlerových metod (handle<Something>), u kterých má kontrola probíhat pomocí anotace @secured.

Realizace jde cestou přepsání metod link a signalReceived ve vašem BasePresenteru nebo BaseControlu (funguje to pro oboje – Controly i Presentery).

Ochrana se automaticky vygeneruje jen pro signály Presenteru/Controlu a to takové, které jsou označeny anotací secured. Stačí tedy…

<?php
/**
 * @secured
 */
public function handleEdit($id) {
}
?>

…anotovat handlery které mají být chráněny proti manuální změně parametrů.

Použití

  • Nakopírujte si tyto metody do vašich BasePresenterů / BaseControlů
  • anotujte handlery, jejichž parametry chcete chránit pomocí @secured
  • a používejte {link}, {plink}, $presenter->link() jak je libo
  • Při změně parametrů vyhodí BadSignalException

Implementace je zatím experimentální, zajímě mě váš názor. Díky.

<?php
//---------------------------------------------------------------------------
//-- Secure Behaviour -------------------------------------------------------
//---------------------------------------------------------------------------

/**
 * For @secure annotated signal handler methods checks if URL parameters has not been changed
 * @param string $signal
 * @throws BadSignalException
 */
public function signalReceived($signal) {

	$methodName = $this->formatSignalMethod($signal);
	if (method_exists($this, $methodName)) {
		$method = $this->getReflection()->getMethod($methodName);
		if (Annotations::has($method, 'secured')) {
			$protectedParams = array();
			foreach ($method->getParameters() as $param) {
				$protectedParams[$param->name] = $this->getParam($param->name);
			}
			if ($this->getParam('__secu') !== $this->createSecureHash($protectedParams)) {
				throw new BadSignalException('Secured parameters are not valid.');
			}
		}
	}

	parent::signalReceived($signal);
}

/**
 * Generates link. If links points to @secure annotated signal handler method, additonal
 * parameter preventing changing parameters will be added.
 *
 * @param string  $destination
 * @param array|mixed $args
 * @return string
 */
public function link($destination, $args = array()) {

	if (!is_array($args)) {
		$args = func_get_args();
		array_shift($args);
	}

	$link = parent::link($destination, $args);
	$lastrequest = $this->presenter->lastCreatedRequest;

	/* --- Bad link --- */
	if ($lastrequest === NULL) {
		return $link;
	}

	/* --- Not a signal --- */
	if (substr($destination, - 1) !== '!') {
		return $link;
	}

	/* --- Only on same presenter --- */
	if ($this->getPresenter()->getName() !== $lastrequest->getPresenterName()) {
		return $link;
	}

	$signal = trim($destination, '!');
	$rc = $this->getReflection()->getMethod($this->formatSignalMethod($signal));

	if (Annotations::has($rc, 'secured') === FALSE) {
		return $link;
	}

	$origParams = $lastrequest->getParams();
	$protectedParams = array();

	foreach ( $rc->getParameters() as $param) {
		$protectedParams[$param->name] = ArrayTools::get($origParams, $this->getParamId($param->name));
	}
	$args['__secu'] = $this->createSecureHash($protectedParams);
	return parent::link($destination, $args);
}


/**
 * Creates secure hash from array of arguments.
 * @param array $param
 * @return string
 */
protected function createSecureHash($params) {
	$ns = Environment::getSession('securedlinks');
	if ($ns->key === NULL) {
		$ns->key = uniqid();
	}
	$s = implode('|', array_keys($params)) . '|' . implode('|', array_values($params)) . $ns->key;
	return substr(md5($s), 4, 8);
}
?>

Určitě to nebude ideální, ale šlape to docela pěkně.

Editoval jasir (6. 9. 2009 15:00)

redhead
Člen | 1313
+
0
-

lol, ta podoba. Také jsem u své CSRF protekce linků zavrhl safelink a rovněž jsem přepsal metodu link. Takže se vytvářeli odkazy na actiony/rendery, kde byla uvedena rovněž anotace secured, už s hashem :)

_Martin_
Generous Backer | 679
+
0
-

No, a nebo nepoužít signál a potvrzení JavaScriptem, ale:

  1. místo signálu použít action s formulářem, který se zeptá na smazání (čímž, pokud se nepletu, je ochrana vůči CSRF zaručena).
  2. před samotným mazáním ověřit, zda má přihlášený uživatel ke smazání konkrétní položky z DB práva.
Ondrej
Člen | 110
+
0
-

_Martin_ napsal(a):

  1. před samotným mazáním ověřit, zda má přihlášený uživatel ke smazání konkrétní položky z DB práva.

bod 2) u CSRF nic neresi…

U CSRF ty prava na mazani uzivatel ma, takze muze dojit k tomu, ze si tak vymaze nechtene celou databazi, protoze mu utocnik tyto mazaci url vnuti. (napr. skrytym obrazkem)

_Martin_
Generous Backer | 679
+
0
-

Ondrej napsal(a):

bod 2) u CSRF nic neresi…

U CSRF ty prava na mazani uzivatel ma, takze muze dojit k tomu, ze si tak vymaze nechtene celou databazi, protoze mu utocnik tyto mazaci url vnuti. (napr. skrytym obrazkem)

Jj, máš pravdu. Asi jsem reagoval na jeden z příspěvků výše, kde někdo zmínil možnost, že jako přihlášený uživatel zkusím měnit tohle číslo u sebe v okně prohlížeče a budu tak mazat záznamy i ostatním.

A k bodu 1) bych měl doplnit, že samozřejmě ten formulář musí být zabezpečen (čili v Nette addProtection()).

_Martin_
Generous Backer | 679
+
0
-

jasir napsal(a):

… velice pěkný kód =) …

A je nutné v tomto uvedeném řešení zakázat uživateli měnit parametry? Nestačilo by kontrolovat pouze ten unikátní řetězec? Když by mi odkaz někdo podstrčil, tak ten token znát nemůže. Nebo může? (Napadá mě jen jedna situace a to, že by v administraci byl iframe, kam by se načetl útočníkův skript – ale nevím, zda má takový skript možnost přistupovat k dokumentu nad tím iframem).

Rozhodně to není žádná kritika, ten skript je dokonalý (část hashe je v session, čili ven se nikdy nedostane, takže ani znalost hashovací funkce nepomůže útočníkovi vygenerovat vlastní token). Princip toho útoku mi docvakl teprve dnes ráno – jen je možné, že to ještě nedocvaklo úplně=)

jasir
Člen | 746
+
0
-

_Martin_ napsal(a):

A je nutné v tomto uvedeném řešení zakázat uživateli měnit parametry? Nestačilo by kontrolovat pouze ten unikátní řetězec?

No toto řešení je právě o zakázání možnosti měnit parametry handle metody. Ale možná bych měl
upozornit na to, že chráněné jsou jen parametry handle metod. Naopak ostatní parametry (i persistentní parametry) jsou nechráněné a uživatel je může měnit.
Mám-li v presenteru třeba toto:

<?php
class SecuredPresenter extends BasePresenter {

	/** @persistent string */
	public $lang;

	/** @secured */
	public handleDelete($id) {
		$this->model->deleteUser($id);
	}
}
?>

Pak v odkazech generovaných pomocí např. {link delete!, $id} bude chráněn parametr id a změnit nepůjde, naopak parametr lang měnit půjde jak bude třeba.

Editoval jasir (25. 10. 2009 20:39)

_Martin_
Generous Backer | 679
+
0
-

Jj, toto mi je jasné. Měl jsem se lépe vyjádřit. Ten dotaz se týkal toho, pokud bych chtěl volání metody chránit proti CSRF a zároveň uživateli dovolit měnit parametry.

jasir
Člen | 746
+
0
-

Já myslím že pak je to jen o přidání parametru tokenu (hashe) do query. Cesta by zřejmě vedla opět přes přepisování metod link() a signalReceived(), jen by se nevytvářel hash za použití ostatních parametrů, ale nějak jednodušeji. Při odeslání linku by se pak kontrolovala jen existence tokenu. Podobně jako v addProtection v třidě Form.

saimons
Člen | 293
+
0
-

Co jsem teda cetl tak mi neni jasne jak by mohla pouha url byt nebezpecna? Prece kdyz mam dejme tomu nakou abstraktni tridu, kde mam autentifikaci. Pri linku na url se vola metoda tridy ktera zdedi zabezpeceni od autentifikace → tim padem pozadavek neprojde a web je presnerovan na login. Nebo je moje uvaha spatne?

Editoval saimons (24. 1. 2010 14:59)

redhead
Člen | 1313
+
0
-

CSRF útok ale spoléhá na akci přihlášeného uživatele. Útočník nějakým způsobem donutí uživatelé nějakou url spustit, a to je pak nebezpečné, protože to vlastně nevlastní vinou udělá sám uživatel.

wdolek
Člen | 331
+
0
-

procpak neni neco takoveho standardne dostupne? ( https://forum.nette.org/…zeni-smazani#… )… vzdyt by to byla docela mila vlastnost Nette, ne?

(v 0.9.4 sem tuto vlastnost pro handlery nevsiml, tak me opravte, pokud tam neco takoveho je)

Matúš Matula
Člen | 257
+
0
-

K jasirovmu kodu som si za podmienku ci je to signal (!) pridal este 1 podmienku do metody link, aby som vylucil generovania neexistujuceho signalu nad formularom

<?php
	...
        /* --- Not a signal --- */
        if (substr($destination, - 1) !== '!') {
            return $link;
        }
	...

	//ADDED
        /* --- Exclude Form submits --- */
        if (substr($destination, - 8) === '-submit!') {
            return $link;
        }
?>

Mozno sa niekomu zide

one-two
Člen | 80
+
0
-

Hezká věc. Pokud to bude někdo chtít použít v 2.0 verzi tak stačí místo

Annotations::has($method, 'secured')

použít:

$method->hasAnnotation('secured')

chvilku mi trvalo než sem to našel, třeba se někomu bude hodit.

Bazylek
Člen | 22
+
0
-

Taky jsem řešil problém, kdy jsem potřeboval předat id z vypsaných dat do componenty pře šablonu, ale tento zpsob mi nefungoval "

{foreach $polozka as $p}
        {?$component['delForm']['id']->value = $p->id} {*//<< formuláři předáme ID*}
        {control delForm}<br/>
{/foreach}

Vyřešil jsem to až s pomocí Tomáše M., kterému tímto děkuji.

{?$control['registraceKurzuForm']['id']->setValue($kurz->id)}
{control registraceKurzuForm}

Snad se to bude někou hodit.

hAssassin
Člen | 293
+
0
-

hezka vecicka, jen to tu trochu umrelo. nebylo by od veci neco takovyho mit primo v Nette? Nebo jak to vypada v aktualni verzi s necim takovym (myslim tu ochranu u odkazu)?

bojovyletoun
Člen | 667
+
0
-

mám jiný nápad- neměnit zápis odkazů, ale anotovat samotné metody, nette by se postaralo, volání metody je dovolené → třeba pomocí session.