SafeHiddenField

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

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)

Patrik Votoček
Člen | 2221
+
0
-

Proč to sem dáváš jako vlákno a nedáš to do extras ?

h4kuna
Backer | 740
+
0
-

vrtak-cz napsal(a):

Proč to sem dáváš jako vlákno a nedáš to do extras ?

Ja to delam taky.

Myslim si ze je lepsi to nejdriv prohnat pres forum a podle toho jaka se zvrhne k tomu diskuse se to odladi.

Alespon hned vis ze je nova komponenta :)

jasir
Člen | 746
+
0
-

Myslím, že je lepší dát to do extras a hned tomu přes menu založit diskusi.

nAS
Člen | 277
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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 HiddenFieldu.

Honza Marek
Člen | 1664
+
0
-

Ok, koukám, že ve Form::addProtection taky není žádné mazání, jestli to dobře chápu.

Honza Kuchař
Člen | 1662
+
0
-

Nechcete to přidat do extras?

setka
Člen | 10
+
0
-

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:

  1. HiddenField, kterým transparentně půjde přenést libovolná data – třeba i pole, nejen řetězec
  2. „bezpečnost“ – aby klient nemohl pozměnit data
  3. 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í:

  1. 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.
  2. 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'])
  3. 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í.