-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
36 changed files
with
3,653 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
src/foundation/src/Http/Middleware/Concerns/ExcludesPaths.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SwooleTW\Hyperf\Foundation\Http\Middleware\Concerns; | ||
|
||
use Hyperf\Stringable\Str; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
trait ExcludesPaths | ||
{ | ||
/** | ||
* Determine if the request has a URI that should be excluded. | ||
*/ | ||
protected function inExceptArray(ServerRequestInterface $request): bool | ||
{ | ||
$fullUrl = $this->getFullUrl($request); | ||
$decodedUrl = $this->decodedPath($request); | ||
foreach ($this->getExcludedPaths() as $except) { | ||
if ($except !== '/') { | ||
$except = trim($except, '/'); | ||
} | ||
|
||
if (Str::is($except, $fullUrl) || Str::is($except, $decodedUrl)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Get the URIs that should be excluded. | ||
*/ | ||
public function getExcludedPaths(): array | ||
{ | ||
return $this->except ?? []; | ||
} | ||
|
||
/** | ||
* Get the full URL for the request. | ||
*/ | ||
protected function getFullUrl(ServerRequestInterface $request): string | ||
{ | ||
$uri = $request->getUri(); | ||
$query = $uri->getQuery(); | ||
|
||
$baseUrl = $uri->getScheme() . '://' . $uri->getAuthority(); | ||
$pathInfo = $uri->getPath(); | ||
|
||
$question = $baseUrl . $pathInfo === '/' ? '/?' : '?'; | ||
$url = $baseUrl . $pathInfo; | ||
|
||
return $query ? $url . $question . $query : $url; | ||
} | ||
|
||
/** | ||
* Parse the pattern and format for usage. | ||
*/ | ||
protected function decodedPath(ServerRequestInterface $request): string | ||
{ | ||
$path = trim($request->getUri()->getPath(), '/'); | ||
|
||
return rawurldecode($path === '' ? '/' : $path); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace SwooleTW\Hyperf\Foundation\Http\Middleware; | ||
|
||
use Hyperf\Collection\Arr; | ||
use Hyperf\Contract\ConfigInterface; | ||
use Hyperf\HttpServer\Request; | ||
use Psr\Container\ContainerInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Server\MiddlewareInterface; | ||
use Psr\Http\Server\RequestHandlerInterface; | ||
use SwooleTW\Hyperf\Cookie\Cookie; | ||
use SwooleTW\Hyperf\Encryption\Contracts\Encrypter; | ||
use SwooleTW\Hyperf\Foundation\Contracts\Application as ApplicationContract; | ||
use SwooleTW\Hyperf\Foundation\Http\Middleware\Concerns\ExcludesPaths; | ||
use SwooleTW\Hyperf\Session\Contracts\Session as SessionContract; | ||
use SwooleTW\Hyperf\Session\TokenMismatchException; | ||
use SwooleTW\Hyperf\Support\Traits\InteractsWithTime; | ||
|
||
class VerifyCsrfToken implements MiddlewareInterface | ||
{ | ||
use InteractsWithTime; | ||
use ExcludesPaths; | ||
|
||
/** | ||
* The URIs that should be excluded. | ||
* | ||
* @var array<int, string> | ||
*/ | ||
protected array $except = []; | ||
|
||
/** | ||
* The globally ignored URIs that should be excluded from CSRF verification. | ||
*/ | ||
protected static array $neverVerify = []; | ||
|
||
/** | ||
* Indicates whether the XSRF-TOKEN cookie should be set on the response. | ||
*/ | ||
protected bool $addHttpCookie = true; | ||
|
||
/** | ||
* Create a new middleware instance. | ||
*/ | ||
public function __construct( | ||
protected ContainerInterface $app, | ||
protected ConfigInterface $config, | ||
protected Encrypter $encrypter, | ||
protected Request $request | ||
) { | ||
} | ||
|
||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface | ||
{ | ||
if ($this->isReading($request) | ||
|| $this->runningUnitTests() | ||
|| $this->inExceptArray($request) | ||
|| $this->tokensMatch() | ||
) { | ||
$response = $handler->handle($request); | ||
if ($this->shouldAddXsrfTokenCookie()) { | ||
$response = $this->addCookieToResponse($response); | ||
} | ||
|
||
return $response; | ||
} | ||
|
||
throw new TokenMismatchException('CSRF token mismatch.'); | ||
} | ||
|
||
/** | ||
* Determine if the HTTP request uses a ‘read’ verb. | ||
*/ | ||
protected function isReading(ServerRequestInterface $request): bool | ||
{ | ||
return in_array($request->getMethod(), ['HEAD', 'GET', 'OPTIONS']); | ||
} | ||
|
||
/** | ||
* Determine if the application is running unit tests. | ||
*/ | ||
protected function runningUnitTests(): bool | ||
{ | ||
if (! $this->app instanceof ApplicationContract) { | ||
return false; | ||
} | ||
|
||
return $this->app->runningUnitTests(); | ||
} | ||
|
||
/** | ||
* Get the URIs that should be excluded. | ||
*/ | ||
public function getExcludedPaths(): array | ||
{ | ||
return array_merge($this->except, static::$neverVerify); | ||
} | ||
|
||
/** | ||
* Determine if the session and input CSRF tokens match. | ||
*/ | ||
protected function tokensMatch(): bool | ||
{ | ||
$token = $this->getTokenFromRequest(); | ||
$sessionToken = $this->app->get(SessionContract::class)->token(); | ||
|
||
return is_string($sessionToken) | ||
&& is_string($token) | ||
&& hash_equals($sessionToken, $token); | ||
} | ||
|
||
/** | ||
* Get the CSRF token from the request. | ||
*/ | ||
protected function getTokenFromRequest(): ?string | ||
{ | ||
return $this->request->input('_token') | ||
?? $this->request->header('X-CSRF-TOKEN') | ||
?? null; | ||
} | ||
|
||
/** | ||
* Determine if the cookie should be added to the response. | ||
*/ | ||
public function shouldAddXsrfTokenCookie(): bool | ||
{ | ||
return $this->addHttpCookie; | ||
} | ||
|
||
/** | ||
* Add the CSRF token to the response cookies. | ||
*/ | ||
protected function addCookieToResponse(ResponseInterface $response): ResponseInterface | ||
{ | ||
/* @phpstan-ignore-next-line */ | ||
return $response->withCookie( | ||
$this->newCookie($this->config->get('session', [])) | ||
); | ||
} | ||
|
||
/** | ||
* Create a new "XSRF-TOKEN" cookie that contains the CSRF token. | ||
*/ | ||
protected function newCookie(array $config): Cookie | ||
{ | ||
return new Cookie( | ||
'XSRF-TOKEN', | ||
$this->app->get(SessionContract::class)->token(), | ||
$this->availableAt(60 * $config['lifetime']), | ||
$config['path'] ?? '/', | ||
$config['domain'] ?? '', | ||
$config['secure'] ?? false, | ||
false, | ||
false, | ||
$config['same_site'] ?? null, | ||
$config['partitioned'] ?? false | ||
); | ||
} | ||
|
||
/** | ||
* Indicate that the given URIs should be excluded from CSRF verification. | ||
*/ | ||
public static function except(array|string $uris): void | ||
{ | ||
static::$neverVerify = array_values(array_unique( | ||
array_merge(static::$neverVerify, Arr::wrap($uris)) | ||
)); | ||
} | ||
|
||
/** | ||
* Flush the state of the middleware. | ||
*/ | ||
public static function flushState(): void | ||
{ | ||
static::$neverVerify = []; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.