SafeHiddenField
- Foowie
- Člen | 269
Ochrana hodnoty přenášené v hidden fieldu formuláře
Komponenta ke stažení pro Nette 0.9.0 stable (rev 7,
3.9.2009)
Komponenta ke stažení pro Nette 0.9-dev (rev 8,
17.9.2009)
Komponenta využívá exteni Mcrypt, pokud není nalezena použije se na hodnotu jednodušší šifrování. (To není moc silné, ale lepší než drátem do oka ;))
Registrace komponenty do formuláře:
Form::extensionMethod('Form::addSafeHidden', 'SafeHiddenField::Form_addSafeHidden'); // PHP 5.2
//Form::extensionMethod('addSafeHidden', 'SafeHiddenField::Form_addSafeHidden'); // PHP 5.3
Použití:
$form = new AppForm(..., ...);
$form->addSafeHidden("jmeno_komponenty", "sifrovaci_klic");
...
Místo sifrovaci_klic
zadejte libovolný shluk znaků (např.
sdf456awe58f7asdf
;)
Pokud při dešifrování hodnota nesedí, je vyvolána výjimka
InvalidStateException
.
Třída obsahuje public static $lazyErrors
, která určuje, kde se
výjimky budou vyhazovat.
Pokud je hodnota true
(defaultně), bude případná výjimka
vyvolána při zavolání metody getValue()
, jinak je vyvolána
v metodě loadHttpData($data)
.
Pokud byste chtěli zvolit jiný způsob šifrování, stačí zavolat metodu
public static function setCryptFunctions($encrypt, $decrypt)
s callbacky.
Editoval Foowie (17. 9. 2009 14:20)
- nAS
- Člen | 277
Nebylo by lepší hodnotu uchovávat v session a formulářem přenášet pouze token?
Třeba nějak takhle:
<?php
require_once dirname(__FILE__) . '/../../Forms/Controls/HiddenField.php';
/**
* Hidden form control that forces hidden value so it cannot be changed.
*
* @author Martin Major
* @copyright Copyright (c) 2009 Martin Major
* @package Nette\Forms
*/
class ForcedHiddenField extends HiddenField
{
/**
* Generates control's HTML element.
* @return Html
*/
public function getControl()
{
$value = $this->forcedValue === NULL ? $this->value : $this->forcedValue;
$session = Environment::getSession()->getNamespace('Nette.Forms.Form/ForcedHidden');
do {
$token = md5(uniqid(mt_rand()) . $_SERVER['REMOTE_ADDR']);
} while (isset($session->$token));
$session->$token = $value;
return parent::getControl()->value($token);
}
/**
* Loads HTTP data.
* @param array
* @return void
*/
public function loadHttpData($data)
{
$name = $this->getName();
$session = Environment::getSession()->getNamespace('Nette.Forms.Form/ForcedHidden');
if (!isset($session->$data[$name])) {
throw InvalidStateException("Hidden input $name was disallowed changed.");
}
$this->setValue($session->$data[$name]);
}
}
Jenom by bylo potřeba upravit v HiddenField $forcedValue na protected.
- Honza Marek
- Člen | 1664
Pokud při dešifrování hodnota nesedí, je vyvolána výjimka
CryptException
.
Nestačila by InvalidStateException
? Já bych zbytečně nové
výjimky nevymýšlel.
- Foowie
- Člen | 269
Honza M. napsal(a):
Pokud při dešifrování hodnota nesedí, je vyvolána výjimka
CryptException
.Nestačila by
InvalidStateException
? Já bych zbytečně nové výjimky nevymýšlel.
Taky jsem to tam původně měl, ale jelikož to v projektu zachytávám až
na úrovni errorPresenteru tak potřebuju vědět přesnou specifikaci
výjimky…
Edit: uvidím, možná to změním z5 a vytvořím si novou výjimku až
v projektu …
Editoval Foowie (2. 9. 2009 18:55)
- Patrik Votoček
- Člen | 2221
Foowie napsal(a):
Taky jsem to tam původně měl, ale jelikož to v projektu zachytávám až na úrovni errorPresenteru tak potřebuju vědět přesnou specifikaci výjimky…
A na co je asi možnost „error“ codu u vyjímek?
throw new Exception($message [, $code]);
- Honza Marek
- Člen | 1664
Já znova otevřu tohle téma, protože si myslim, že podobná komponenta může být hodně užitečná. Kromě utajení id si třeba můžu nastavit původní hodnotu nějakého pole a při zpracování formuláře bezpečně zjistit, jestli se hodnota změnila nebo ne.
Udělal jsem verzi, která vznikla přepracováním ForcedHiddenFieldu nASe.
<?php
/**
* Hidden form control that forces hidden value so it cannot be changed.
*
* @author Jan Marek, Martin Major, David Grudl
*/
class SecretHiddenField extends HiddenField {
private $sessionKey;
public function __construct() {
parent::__construct();
$this->control->type = 'hidden';
}
public function getSession() {
return Environment::getSession(__CLASS__);
}
/**
* Sets control's value.
* @param string
* @return HiddenField provides a fluent interface
*/
public function setValue($value) {
$session = $this->getSession();
if (isset($this->sessionKey)) {
$token = $this->sessionKey;
} else {
do {
$token = md5(uniqid(mt_rand()));
} while (isset($session->$token));
$this->sessionKey = $token;
}
$session->$token = $value;
$this->value = $value;
return $this;
}
public function loadHttpData()
{
$path = strtr(str_replace(']', '', $this->getHtmlName()), '.', '_');
$token = ArrayTools::get($this->getForm()->getHttpData(), explode('[', $path));
$this->sessionKey = $token;
$session = $this->getSession();
if (!isset($session->$token)) {
$this->addError("Item was changed or was not set!");
return null;
}
$this->value = $session->$token;
}
/**
* Generates control's HTML element.
* @return Html
*/
public function getControl() {
return parent::getControl()->value($this->sessionKey);
}
}
Funguje to poměrně dobře. Není potřeba měnit HiddenField v Nette. Akorát hodnoty v sešně zůstávají a tak se tam hromadí spousta „utajených“ hodnot. Nenapadá mě, jak to elegantně řešit, aby to mazání fungovalo automaticky.
- nAS
- Člen | 277
Je to fajn, určitě se to hodí. Co se týká těch session, to bych asi
neřešil, protože ty se sami smažou po nějaké době neaktivity, takže by
to mohl být problém akorát pokud by někdo generoval ohromné množství
SecretHiddenField
ů. A v tom případě to jde upravit
například přidáním timestampu a automaticky promazávat starčí než
nějaká konstanta. Ale pro normální použití je to úplně zbytečné.
A ještě mi přijde, že ten konstruktor tam je zbytečně, protože typ
controlu se nastavuje už v HiddenField
u.
- Honza Marek
- Člen | 1664
Ok, koukám, že ve Form::addProtection taky není žádné mazání, jestli to dobře chápu.
- setka
- Člen | 10
Zdravím, vytvořil jsem si podobnou komponentu, ale šel jsem na způsobem, který tu ještě nebyl popsán. Třeba to někomu pomůže.
Co jsem potřeboval a jaké jsem měl požadavky:
- HiddenField, kterým transparentně půjde přenést libovolná data – třeba i pole, nejen řetězec
- „bezpečnost“ – aby klient nemohl pozměnit data
- nepotřeboval jsem skrýt data před klientem, i když moje řešení lze o šifrování snadno rozšířit
Hned se nabízí možnost uložit data do session a ve formuláři přenášet nějaký náhodný klíč. To jsem nechtěl, protože pak se data uloží do session při každém zobrazení formuláře. Pokud formulář klient zobrazí a neodešle (v mém použití to tak je často a dat je celkem hodně), data se v session hromadí. Možné řešení je kratší expirace session, což není ideální – nechám si někde v tabu prohlížeče otevřený formulář, za hodinu se k němu vrátím, nepůjde odeslat korektně.
A řešení:
- při prvním použití komponenty se vygeneruje náhodný SALT, který se uloží do session s „nekonečnou“ expirací a používá se stejný po celou dobu její platnosti – session nenabývá na objemu. Na druhou stranu nejde o dlouhodobě konstatní klíč, výhoda taky je že se klíč nemusí nikde v konfiguraci ukládat.
- Při renderování formuláře se data serializují a přidá se k nim HASH
vytvořený php
funkcí
hash_hmac('sha256', serialize($value), $session['salt'])
- v metodě
loadHttpData()
se přijatá data „odserializují“, spočte se HASH z přijatých dat a SALTu ze session a zkontroluje se jeho shoda s přijatým SALTem. V případě chyby, pokud je dostupný Presenter, tak se rovnou přidá Flash Message a provede se$presenter->redirect('this')
, jinak se vyhodí výjimka.
Možná někoho napadne, proč neplatné odeslání neošetřím přes
addError
. Je to proto, že v takovém případě je potřeba ta
neplatná „chybějící“ data získat znovu (třeba z databáze). Pro
plnění formuláře používám metodu setDefaults
, která ovšem
v případě nevalidně odeslaného formuláře hodnoty nenastaví.