Skip to content

Commit c21939b

Browse files
committed
nette/http 3.4.0
1 parent d4fd62d commit c21939b

10 files changed

Lines changed: 112 additions & 32 deletions

File tree

forms/cs/in-presenter.texy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ Zmíněný CSRF útok spočívá v tom, že útočník naláká oběť na strán
359359
$form->allowCrossOrigin(); // POZOR! Vypne ochranu!
360360
```
361361

362-
Tato ochrana využívá SameSite cookie pojmenovanou `_nss`. Ochrana pomocí SameSite cookie nemusí být 100% spolehlivá, proto je vhodné zapnout ještě ochranu pomocí tokenu:
362+
Tato ochrana se opírá o hlavičky `Sec-Fetch-*` (Fetch Metadata), které prohlížeč posílá automaticky. Starší prohlížeče, které je neposílají, nejsou pokryté, proto je vhodné zapnout ještě ochranu pomocí tokenu:
363363

364364
```php
365365
$form->addProtection();

forms/cs/standalone.texy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,9 @@ Zmíněný CSRF útok spočívá v tom, že útočník naláká oběť na strán
304304
$form->allowCrossOrigin(); // POZOR! Vypne ochranu!
305305
```
306306

307-
Tato ochrana využívá SameSite cookie pojmenovanou `_nss`. Vytvářejte proto objekt formuláře ještě před odesláním prvního výstupu, aby bylo možné cookie odeslat.
307+
Tato ochrana se opírá o hlavičky `Sec-Fetch-*` (Fetch Metadata), které prohlížeč posílá automaticky s požadavkem.
308308

309-
Ochrana pomocí SameSite cookie nemusí být 100% spolehlivá, proto je vhodné zapnout ještě ochranu pomocí tokenu:
309+
Starší prohlížeče, které tyto hlavičky neposílají, nejsou pokryté, proto je vhodné zapnout ještě ochranu pomocí tokenu:
310310

311311
```php
312312
$form->addProtection();

forms/en/in-presenter.texy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ The mentioned CSRF attack involves an attacker luring a victim to a page that si
359359
$form->allowCrossOrigin(); // WARNING! Disables protection!
360360
```
361361

362-
This protection uses a SameSite cookie named `_nss`. SameSite cookie protection might not be 100% reliable, so it's advisable to also enable token protection:
362+
This protection relies on the browser's `Sec-Fetch-*` headers (Fetch Metadata), which it sends automatically. Older browsers that don't send them are not covered, so it's advisable to also enable token protection:
363363

364364
```php
365365
$form->addProtection();

forms/en/standalone.texy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,9 @@ The mentioned CSRF attack involves an attacker luring a victim to a page that si
304304
$form->allowCrossOrigin(); // WARNING! Disables protection!
305305
```
306306

307-
This protection uses a SameSite cookie named `_nss`. Therefore, create the form object before sending the first output so that the cookie can be sent.
307+
This protection relies on the browser's `Sec-Fetch-*` headers (Fetch Metadata), which it sends automatically with the request.
308308

309-
SameSite cookie protection may not be 100% reliable, so it's advisable to also enable token protection:
309+
Older browsers that don't send these headers are not covered, so it's advisable to also enable token protection:
310310

311311
```php
312312
$form->addProtection();

http/cs/request.texy

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,41 @@ isSecured(): bool .[method]
146146
Je spojení šifrované (HTTPS)? Pro správnou funkčnost může být potřeba [nastavit proxy |configuration#HTTP proxy].
147147

148148

149-
isSameSite(): bool .[method]
150-
----------------------------
151-
Přichází požadavek ze stejné (sub)domény a je iniciován kliknutím na odkaz? Nette k detekci používá cookie `_nss` (dříve `nette-samesite`).
149+
isSameSite(): bool .[method deprecated]
150+
---------------------------------------
151+
Přišel požadavek ze stejné stránky (same-site)? Od verze 3.4 ji nahrazuje schopnější metoda [isFrom() |#isFrom].
152+
153+
154+
isFrom(FetchSite|array $site, FetchDest|array|null $dest=null, ?bool $user=null): bool .[method]{data-version:3.4}
155+
------------------------------------------------------------------------------------------------------------------
156+
Řekne vám, odkud požadavek přišel a jak ho prohlížeč vytvořil, na základě hlaviček `Sec-Fetch-*` (tzv. [Fetch Metadata |https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header]), které prohlížeč nastavuje sám a stránka běžící v prohlížeči oběti je nedokáže zfalšovat ani odstranit. Nette ji interně používá k automatické ochraně formulářů a signálů před útoky [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). Hodí se, když chcete ochránit vlastní citlivé akce, jako jsou API endpointy nebo destruktivní odkazy.
157+
158+
Metoda vrátí `true` pouze tehdy, když požadavek splňuje **všechny** zadané podmínky. První parametr `$site` popisuje vztah mezi stránkou, která požadavek vyvolala, a vaším webem (hlavička `Sec-Fetch-Site`). Přijímá jednu hodnotu nebo pole těchto hodnot výčtu `FetchSite`:
159+
160+
- `FetchSite::SameOrigin` – ze zcela stejného původu (schéma, host i port)
161+
- `FetchSite::SameSite` – ze stejného webu, případně z jiné subdomény
162+
- `FetchSite::CrossSite` – z cizího webu
163+
- `FetchSite::None` – uživatel jej vyvolal přímo, např. zadáním URL nebo otevřením záložky
164+
165+
```php
166+
// přišel požadavek z našich vlastních stránek?
167+
if (!$httpRequest->isFrom([FetchSite::SameOrigin, FetchSite::SameSite])) {
168+
// akci zablokujeme
169+
}
170+
```
171+
172+
Volitelný parametr `$dest` (hlavička `Sec-Fetch-Dest`) udává, jaký druh zdroje prohlížeč načítá, např. `FetchDest::Document` pro navigaci na stránku nebo `FetchDest::Empty` pro požadavek z JavaScriptu. Volitelný parametr `$user` (hlavička `Sec-Fetch-User`) říká, zda navigaci vyvolala skutečná akce uživatele, jako kliknutí na odkaz či odeslání formuláře; hodnotou `true` ji vyžadujete.
173+
174+
Kontrola, že je akce dostupná pouze z vlastních stránek a jen skutečnou akcí uživatele, pak vypadá takto:
175+
176+
```php
177+
if (!$httpRequest->isFrom(FetchSite::SameOrigin, FetchDest::Document, user: true)) {
178+
$this->error();
179+
}
180+
```
181+
182+
.[note]
183+
Starší prohlížeče (Safari před 16.4) hlavičky `Sec-Fetch-*` neposílají. Pro ně Nette použije záložně cookie `SameSite=Strict`, která dokazuje pouze to, že požadavek není cross-site. Kontrolu, která navíc vyžaduje `$dest` nebo `$user`, tak nelze tímto způsobem ověřit a v těchto prohlížečích vrátí `false` – pokud je to příliš striktní, testujte jen `$site`.
152184

153185

154186
isAjax(): bool .[method]

http/cs/response.texy

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,16 @@ $httpResponse->sendAsFile('faktura.pdf');
115115
```
116116

117117

118-
setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite='Lax') .[method]
119-
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
118+
setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, SameSite|string $sameSite='Lax', bool $partitioned=false) .[method]
119+
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
120120
Odešle cookie. Výchozí hodnoty parametrů:
121121

122-
| `$path` | `'/'` | cookie má dosah na všechny cesty v (sub)doméně *(konfigurovatelné)*
123-
| `$domain` | `null` | což znamená s dosahem na aktuální (sub)doménu, ale nikoliv její subdomény *(konfigurovatelné)*
124-
| `$secure` | `true` | pokud web běží na HTTPS, jinak `false` *(konfigurovatelné)*
125-
| `$httpOnly` | `true` | cookie je pro JavaScript nepřístupná
126-
| `$sameSite` | `'Lax'` | cookie nemusí být odeslána při [přístupu z jiné domény |nette:glossary#SameSite cookie]
122+
| `$path` | `'/'` | cookie má dosah na všechny cesty v (sub)doméně *(konfigurovatelné)*
123+
| `$domain` | `null` | což znamená s dosahem na aktuální (sub)doménu, ale nikoliv její subdomény *(konfigurovatelné)*
124+
| `$secure` | `true` | pokud web běží na HTTPS, jinak `false` *(konfigurovatelné)*
125+
| `$httpOnly` | `true` | cookie je pro JavaScript nepřístupná
126+
| `$sameSite` | `'Lax'` | cookie nemusí být odeslána při [přístupu z jiné domény |nette:glossary#SameSite cookie]
127+
| `$partitioned` | `false` | zda je cookie partitioned, viz níže *(od verze 3.4)*
127128

128129
Výchozí hodnoty parametrů `$path`, `$domain` a `$secure` můžete změnit v [konfiguraci |configuration#HTTP cookie].
129130

@@ -135,7 +136,14 @@ $httpResponse->setCookie('lang', 'cs', '100 days');
135136

136137
Parametr `$domain` určuje, které domény mohou cookie přijímat. Není-li uveden, cookie přijímá stejná (sub)doména, jako ji nastavila, ale nikoliv její subdomény. Pokud je `$domain` zadaný, jsou zahrnuty i subdomény. Proto je uvedení `$domain` méně omezující než vynechání. Například při `$domain = 'nette.org'` jsou cookies dostupné i na všech subdoménách jako `doc.nette.org`.
137138

138-
Pro hodnotu `$sameSite` můžete použít konstanty `Response::SameSiteLax`, `SameSiteStrict` a `SameSiteNone`.
139+
Hodnotu `$sameSite` můžete předat jako enum `Nette\Http\SameSite` – `SameSite::Lax`, `SameSite::Strict` nebo `SameSite::None` (fungují i řetězce `'Lax'`, `'Strict'`, `'None'`). Pokud ji nastavíte na `SameSite::None`, automaticky se zapne atribut `$secure`, protože prohlížeče cookie se `SameSite=None` bez něj odmítají.
140+
141+
.{data-version:3.4}
142+
Partitioned cookies (CHIPS) dávají cookie samostatné úložiště pro každý web nejvyšší úrovně. Když tedy služba třetí strany (například vložený widget) nastaví partitioned cookie, prohlížeč pro každý web, na kterém se widget objeví, uchovává oddělenou kopii a tyto kopie nelze vzájemně propojit pro sledování napříč weby. Zapnete je nastavením `$partitioned` na `true`; to zároveň vyžaduje atribut `$secure`, takže se zapne automaticky.
143+
144+
```php
145+
$httpResponse->setCookie('theme', 'dark', '1 year', sameSite: SameSite::None, partitioned: true);
146+
```
139147

140148

141149
deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method]

http/cs/sessions.texy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ setExpiration(?string $time): static .[method]
183183
Nastaví dobu neaktivity po které session vyexpiruje.
184184

185185

186-
setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method]
187-
---------------------------------------------------------------------------------------------------------------------
186+
setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, SameSite|string|null $samesite=null): static .[method]
187+
----------------------------------------------------------------------------------------------------------------------------------
188188
Nastavení parametrů pro cookie. Výchozí hodnoty parametrů můžete změnit v [konfiguraci |configuration#Session cookie].
189189

190190

http/en/request.texy

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,41 @@ isSecured(): bool .[method]
146146
Is the connection encrypted (HTTPS)? Proper functionality might require [setting up a proxy |configuration#HTTP Proxy].
147147

148148

149-
isSameSite(): bool .[method]
150-
----------------------------
151-
Is the request coming from the same (sub)domain and initiated by clicking a link? Nette uses the `_nss` cookie (formerly `nette-samesite`) for detection.
149+
isSameSite(): bool .[method deprecated]
150+
---------------------------------------
151+
Did the request come from the same site? Since version 3.4 it is replaced by the more capable [isFrom() |#isFrom].
152+
153+
154+
isFrom(FetchSite|array $site, FetchDest|array|null $dest=null, ?bool $user=null): bool .[method]{data-version:3.4}
155+
------------------------------------------------------------------------------------------------------------------
156+
Tells you where the request came from and how the browser made it, based on the `Sec-Fetch-*` headers (so-called [Fetch Metadata |https://developer.mozilla.org/en-US/docs/Glossary/Fetch_metadata_request_header]) that the browser sets itself and a page running in the victim's browser can neither forge nor remove. Nette uses it internally to automatically protect forms and signals against [Cross-Site Request Forgery |nette:glossary#Cross-Site Request Forgery CSRF] (CSRF). It is useful when you want to guard your own sensitive actions, such as API endpoints or destructive links.
157+
158+
The method returns `true` only when the request matches **all** the conditions you provide. The first parameter `$site` describes the relationship between the page that initiated the request and your site (the `Sec-Fetch-Site` header). It accepts a single value or a list of these `FetchSite` cases:
159+
160+
- `FetchSite::SameOrigin` – from the exact same origin (scheme, host, and port)
161+
- `FetchSite::SameSite` – from the same site, possibly a different subdomain
162+
- `FetchSite::CrossSite` – from a foreign site
163+
- `FetchSite::None` – the user initiated it directly, e.g. by typing the URL or opening a bookmark
164+
165+
```php
166+
// did the request originate from our own pages?
167+
if (!$httpRequest->isFrom([FetchSite::SameOrigin, FetchSite::SameSite])) {
168+
// block the action
169+
}
170+
```
171+
172+
The optional `$dest` parameter (the `Sec-Fetch-Dest` header) says what kind of resource the browser is fetching, e.g. `FetchDest::Document` for a top-level navigation or `FetchDest::Empty` for a request made from JavaScript. The optional `$user` parameter (the `Sec-Fetch-User` header) indicates whether the navigation was triggered by a genuine user action such as clicking a link or submitting a form; pass `true` to require it.
173+
174+
A check that an action is reachable only from your own pages and only through a real user action then looks like this:
175+
176+
```php
177+
if (!$httpRequest->isFrom(FetchSite::SameOrigin, FetchDest::Document, user: true)) {
178+
$this->error();
179+
}
180+
```
181+
182+
.[note]
183+
Older browsers (Safari before 16.4) do not send the `Sec-Fetch-*` headers. For them Nette falls back to a `SameSite=Strict` cookie that only proves the request is not cross-site. A check that additionally requires `$dest` or `$user` cannot be verified this way and returns `false` in those browsers – if that is too strict, test only `$site`.
152184

153185

154186
isAjax(): bool .[method]

http/en/response.texy

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,16 @@ $httpResponse->sendAsFile('invoice.pdf');
115115
```
116116

117117

118-
setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, ?string $sameSite='Lax') .[method]
119-
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
118+
setCookie(string $name, string $value, $time, ?string $path=null, ?string $domain=null, ?bool $secure=null, ?bool $httpOnly=null, SameSite|string $sameSite='Lax', bool $partitioned=false) .[method]
119+
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
120120
Sends a cookie. Default parameter values:
121121

122-
| `$path` | `'/'` | cookie is available for all paths within the (sub)domain *(configurable)*
123-
| `$domain` | `null` | meaning available for the current (sub)domain, but not its subdomains *(configurable)*
124-
| `$secure` | `true` | if the site is running on HTTPS, otherwise `false` *(configurable)*
125-
| `$httpOnly` | `true` | cookie is inaccessible to JavaScript
126-
| `$sameSite` | `'Lax'` | cookie might not be sent during [cross-origin access |nette:glossary#SameSite cookie]
122+
| `$path` | `'/'` | cookie is available for all paths within the (sub)domain *(configurable)*
123+
| `$domain` | `null` | meaning available for the current (sub)domain, but not its subdomains *(configurable)*
124+
| `$secure` | `true` | if the site is running on HTTPS, otherwise `false` *(configurable)*
125+
| `$httpOnly` | `true` | cookie is inaccessible to JavaScript
126+
| `$sameSite` | `'Lax'` | cookie might not be sent during [cross-origin access |nette:glossary#SameSite cookie]
127+
| `$partitioned` | `false` | whether the cookie is partitioned, see below *(since v3.4)*
127128

128129
You can change the default values of the `$path`, `$domain`, and `$secure` parameters in the [configuration |configuration#HTTP Cookie].
129130

@@ -135,7 +136,14 @@ $httpResponse->setCookie('lang', 'en', '100 days');
135136

136137
The `$domain` parameter determines which domains can accept the cookie. If not specified, the cookie is accepted by the same (sub)domain that set it, but not its subdomains. If `$domain` is specified, subdomains are also included. Therefore, specifying `$domain` is less restrictive than omitting it. For example, with `$domain = 'nette.org'`, cookies are also available on all subdomains like `doc.nette.org`.
137138

138-
You can use the constants `Response::SameSiteLax`, `Response::SameSiteStrict`, and `Response::SameSiteNone` for the `$sameSite` value.
139+
You can pass the `$sameSite` value as a `Nette\Http\SameSite` enum – `SameSite::Lax`, `SameSite::Strict`, or `SameSite::None` (the string values `'Lax'`, `'Strict'`, `'None'` work too). If you set it to `SameSite::None`, the `$secure` attribute is enabled automatically, because browsers reject a `SameSite=None` cookie that is not secure.
140+
141+
.{data-version:3.4}
142+
Partitioned cookies (CHIPS) give a cookie its own separate storage for each top-level site. So when a third-party service (such as an embedded widget) sets a partitioned cookie, the browser keeps a distinct copy for every site the widget appears on, and these copies cannot be linked together for cross-site tracking. Turn it on by setting `$partitioned` to `true`; this also requires the `$secure` attribute, so it is enabled automatically.
143+
144+
```php
145+
$httpResponse->setCookie('theme', 'dark', '1 year', sameSite: SameSite::None, partitioned: true);
146+
```
139147

140148

141149
deleteCookie(string $name, ?string $path=null, ?string $domain=null, ?bool $secure=null): void .[method]

http/en/sessions.texy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ setExpiration(?string $time): static .[method]
183183
Sets the inactivity time after which the session expires.
184184

185185

186-
setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, ?string $samesite=null): static .[method]
187-
---------------------------------------------------------------------------------------------------------------------
186+
setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, SameSite|string|null $samesite=null): static .[method]
187+
----------------------------------------------------------------------------------------------------------------------------------
188188
Sets parameters for cookies. You can change the default parameter values in the [configuration |configuration#Session Cookie].
189189

190190

0 commit comments

Comments
 (0)