Chystá se nette/forms 3.3, prosím o otestování

David Grudl
founder | 8315
+
+5
-

Poprosil bych o testování nette/forms 3.3.0-RC, která přináší modernizaci CSRF ochrany, zpřehlednění Latte integrace a několik změn zpětné kompatibility.

Jako vždy, cílem rozhodně není komukoliv rozbít kód. Některé změny ale nutně zasahují do chování, a proto sháním zpětnou vazbu. Pokud vám něco přestane fungovat nebo narazíte na nečekané deprecated hlášky, dejte prosím vědět.

Vyžaduje PHP 8.3 nebo vyšší.

Bezpečnost: nová CSRF ochrana

Formuláře se nově brání proti CSRF kontrolou hlavičky Sec-Fetch-Site (striktní same-origin) namísto dosavadní SameSite cookie. Tyto hlavičky přidává prohlížeč sám a nelze je zfalšovat ani při XSS zranitelnosti, takže ochrana nepotřebuje token ani stav na serveru. Podrobně to popisuje článek CSRF konečně řeší prohlížeč.

Co to znamená v praxi:

  • Token-based ochrana ($form->addProtection()) je označena jako deprecated. Stále funguje, takže není nutné nic urychleně přepisovat, ale výchozí ochrana ji plně nahrazuje.
  • Kontrola je nově striktně same-origin. Dosavadní mechanismus (SameSite cookie) považoval za bezpečné i odeslání mezi subdoménami téhož webu; nová kontrola vyžaduje přesnou shodu originu (scheme + host + port).

Pokud formulář legitimně přijímáte z jiného originu (cizí web nebo jiná subdoména), lze vestavěnou kontrolu vypnout pomocí $form->allowCrossOrigin(). Ale pozor, tím se CSRF ochrana vypne úplně a formulář přijme požadavek odkudkoliv, takže to dělejte s rozmyslem. Chcete-li povolit jen konkrétní originy, kontrolu vypněte a původ si ověřte ručně proti vlastnímu seznamu pomocí hlavičky Origin:

$form->allowCrossOrigin(); // vypne vestavěnou kontrolu

$allowed = ['https://app.example.com', 'https://admin.example.com'];
$form->onValidate[] = function (Form $form) use ($httpRequest, $allowed) {
	if (
		!$httpRequest->isFrom(Nette\Http\FetchSite::SameOrigin)
		&& !in_array((string) $httpRequest->getOrigin(), $allowed, strict: true)
	) {
		$form->addError('Neplatný původ požadavku.');
	}
};

$httpRequest získáte v presenteru přes $this->getHttpRequest(). getOrigin() vrátí null, pokud hlavička chybí, takže neznámý původ se odmítne.

Latte: sjednocení {formContext} a {formContainer}

Doteď existovaly dvě samostatné značky pro práci s formulářem bez vykreslení <form>: {formContext} zakládal kontext celého formuláře (vzatého z presenteru, když je samotný <form> vykreslen jinde), kdežto {formContainer} vstupoval do vnořeného containeru už otevřeného formuláře. Rozdíl byl jemný a pro řadu lidí matoucí – nebylo zřejmé, kdy sáhnout po které.

Nově je obojí zastřešuje jediná konstrukce {form scope name}:

  • klíčové slovo scope řekne {form}, aby nevypisoval HTML značku <form>, jen vložil formulář/container na zásobník (takže se k němu vážou {input}, {label}, {inputError}),
  • pokud už je nějaký formulář aktivní, název se vyhodnotí relativně vůči němu (chování {formContainer}), jinak z kořenové úrovně (chování {formContext}).

Jeden zápis tak pokryje oba dosavadní případy. Původní {formContext} i {formContainer} přitom zůstávají funkční, ale časem budou deprecated ve prospěch {form scope}.

Latte: {form detached name}

Umožnuje vytvářet vnořené formuláře! HTML zakazuje vnořené značky <form> a prohlížeč vnitřní <form> zahodí a jeho inputy „přilepí“ k vnějšímu formuláři. Klíčové slovo detached to řeší: vypíše prázdný <form></form> na začátku a každý prvek na něj naváže přes HTML atribut form="...". Prvky se tak odešlou do správného formuláře bez ohledu na to, kde v DOM leží.

Pokud máte dva vnořené formuláře, vypište jako {form detached} ten vnější (který ten druhý obklopuje). Jeho prázdná značka <form> se uzavře hned na začátku, takže vnitřní formulář už nebude vnořený a oba budou fungovat správně. A nebo jako detached vypište oba.

Latte: požadavky a interní změny

  • Chybějící čárka před argumenty {form} je nově deprecated – místo {form name attr=val} pište {form name, attr=val}. Důvodem je právě možnost psát za názvem klíčová slova scope/detached.
  • Odstraněna podpora Latte 2 i Latte 3.0 – nově je potřeba Latte 3.1.4+.
  • Vnitřní Runtime byl přepsán ze statické na běžnou (nestatickou) třídu. Je to interní změna, která se vás dotkne jen v případě, že jste statické metody Runtime volali napřímo z vlastního kódu.

Novinka: callback u addSubmit()

Tlačítku lze nově předat handler rovnou při vytvoření, bez samostatného ->onClick[] =:

$form->addSubmit('send', 'Odeslat', function (SubmitButton $button, $values) {
	// ...
});

Změny zpětné kompatibility

setValues() / setDefaults() – příprava na zúžení přijímaných typů. Podporovaným vstupem je nově iterable|stdClass (pole, Traversable, stdClass). Funkčně se zatím nic nemění, ale předáte-li jiný typ objektu – typicky entitu nebo DTO, které se dosud převáděly na pole přes (array) (což u privátních a chráněných vlastností dává „pokroucené" klíče) – dostanete deprecated hlášku. V takovém případě si data převeďte na pole sami.

Další změny chování:

  • RadioList / CheckboxList: getControlPart() a getLabelPart() vyhodí výjimku při neexistujícím klíči (dříve místo toho vznikl nesmyslný výstup).
  • Negativní validační pravidla (~Rule) nově vyhodí výjimku (dříve jen deprecated hláška).

Odstraněno (dříve deprecated): DataClassGenerator, LatteRenderer, getValues(true) (použijte getValues('array')), parametr $default v getOption() (použijte operátor ??).

Nově deprecated (stále funguje, jen hlásí upozornění) – některé magické property. Místo $control->htmlName, caption, omitted, filled, options, selectedItem(s), submittedBy a $form->action, method, renderer používejte odpovídající metody (getHtmlName(), getCaption(), isOmitted(), isFilled(), getOptions(), getSelectedItem(), isSubmittedBy(), getAction(), getMethod(), getRenderer()).

Děkuji za pomoc s testováním!