widget, předání parametru komponentě, potvrzení smazání
- jarks
- Člen | 94
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}
- jarks
- Člen | 94
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
(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}
- redhead
- Člen | 1313
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)
- jasir
- Člen | 746
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
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.
- Honza Kuchař
- Člen | 1662
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
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
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
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
č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
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)
- _Martin_
- Generous Backer | 679
No, a nebo nepoužít signál a potvrzení JavaScriptem, ale:
- 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).
- 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
_Martin_ napsal(a):
- 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
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
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
_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)
- jasir
- Člen | 746
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
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)
- wdolek
- Člen | 331
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
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
- Bazylek
- Člen | 22
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.
- bojovyletoun
- Člen | 667
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.