Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/Debug/ExceptionManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace BlitzPHP\Debug;

use BlitzPHP\Exceptions\HttpException;
use BlitzPHP\Exceptions\TokenMismatchException;
use BlitzPHP\View\View;
use Symfony\Component\Finder\SplFileInfo;
use Throwable;
Expand All @@ -35,6 +37,8 @@ class ExceptionManager
public static function registerHttpErrors(Run $debugger, array $config): Run
{
return $debugger->pushHandler(static function (Throwable $exception, InspectorInterface $inspector, RunInterface $run) use ($config): int {
$exception = self::prepareException($exception);

$exception_code = $exception->getCode();
if ($exception_code >= 400 && $exception_code < 600) {
$run->sendHttpCode($exception_code);
Expand All @@ -52,7 +56,7 @@ public static function registerHttpErrors(Run $debugger, array $config): Run

if (in_array((string) $exception->getCode(), $files, true)) {
$view = new View();
$view->setAdapter(config('view.active_adapter', 'native'), ['view_path_locator' => $config['error_view_path']])
$view->setAdapter(config('view.active_adapter', 'native'), ['view_path' => $config['error_view_path']])
->display((string) $exception->getCode())
->setData(['message' => $exception->getMessage()])
->render();
Expand Down Expand Up @@ -166,4 +170,16 @@ private static function setBlacklist(PrettyPageHandler $handler, array $blacklis

return $handler;
}

/**
* Prepare exception for rendering.
*/
private static function prepareException(Throwable $e): Throwable
{
if ($e instanceof TokenMismatchException) {
$e = new HttpException($e->getMessage(), 419, $e);
}

return $e;
}
}
16 changes: 16 additions & 0 deletions src/Exceptions/TokenMismatchException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

/**
* This file is part of Blitz PHP framework.
*
* (c) 2022 Dimitri Sitchet Tomkeu <devcode.dst@gmail.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace BlitzPHP\Exceptions;

class TokenMismatchException extends FrameworkException
{
}
173 changes: 173 additions & 0 deletions src/Middlewares/VerifyCsrfToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

/**
* This file is part of Blitz PHP framework.
*
* (c) 2022 Dimitri Sitchet Tomkeu <devcode.dst@gmail.com>
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/

namespace BlitzPHP\Middlewares;

use BlitzPHP\Contracts\Http\ResponsableInterface;
use BlitzPHP\Contracts\Security\EncrypterInterface;
use BlitzPHP\Exceptions\EncryptionException;
use BlitzPHP\Exceptions\TokenMismatchException;
use BlitzPHP\Http\Request;
use BlitzPHP\Http\Response;
use BlitzPHP\Session\Cookie\Cookie;
use BlitzPHP\Session\Cookie\CookieValuePrefix;
use BlitzPHP\Traits\Support\InteractsWithTime;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class VerifyCsrfToken implements MiddlewareInterface
{
use InteractsWithTime;

/**
* Les URI qui doivent être exclus de la vérification CSRF.
*/
protected array $except = [];

/**
* Indique si le cookie XSRF-TOKEN doit être défini dans la réponse.
*/
protected bool $addHttpCookie = true;

/**
* Constructeur
*/
public function __construct(protected EncrypterInterface $encrypter)
{
}

/**
* {@inheritDoc}
*
* @param Request $request
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($this->isReading($request) || $this->runningUnitTests() || $this->inExceptArray($request) || $this->tokensMatch($request)) {
return tap($handler->handle($request), function ($response) use ($request) {
if ($this->shouldAddXsrfTokenCookie()) {
$this->addCookieToResponse($request, $response);
}
});
}

throw new TokenMismatchException('Erreur de jeton CSRF.');
}

/**
* Détermine si la requête HTTP utilise un verbe « read ».
*/
protected function isReading(Request $request): bool
{
return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS'], true);
}

/**
* Détermine si l'application exécute des tests unitaires.
*/
protected function runningUnitTests(): bool
{
return is_cli() && on_test();
}

/**
* Détermine si la requête comporte un URI qui doit faire l'objet d'une vérification CSRF.
*/
protected function inExceptArray(Request $request): bool
{
foreach ($this->except as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}

if ($request->fullUrlIs($except) || $request->pathIs($except)) {
return true;
}
}

return false;
}

/**
* Détermine si les jetons CSRF de session et d'entrée correspondent.
*/
protected function tokensMatch(Request $request): bool
{
$token = $this->getTokenFromRequest($request);

return is_string($request->session()->token())
&& is_string($token)
&& hash_equals($request->session()->token(), $token);
}

/**
* Récupère le jeton CSRF de la requête.
*/
protected function getTokenFromRequest(Request $request): ?string
{
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');

if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
try {
$token = CookieValuePrefix::remove($this->encrypter->decrypt($header));
} catch (EncryptionException) {
$token = '';
}
}

return $token;
}

/**
* Détermine si le cookie doit être ajouté à la réponse.
*/
public function shouldAddXsrfTokenCookie(): bool
{
return $this->addHttpCookie;
}

/**
* Ajoute le jeton CSRF aux cookies de la réponse.
*
* @param Response $response
*/
protected function addCookieToResponse(Request $request, $response): ResponseInterface
{
if ($response instanceof ResponsableInterface) {
$response = $response->toResponse($request);
}

if (! ($response instanceof Response)) {
return $response;
}

$config = config('cookie');

return $response->withCookie(Cookie::create('XSRF-TOKEN', $request->session()->token(), [
'expires' => $this->availableAt(config('session.expiration')),
'path' => $config['path'],
'domain' => $config['domain'],
'secure' => $config['secure'],
'httponly' => false,
'samesite' => $config['samesite'] ?? null,
]));
}

/**
* Détermine si le contenu du cookie doit être sérialisé.
*/
public static function serialized(): bool
{
return EncryptCookies::serialized('XSRF-TOKEN');
}
}
24 changes: 9 additions & 15 deletions src/View/Adapters/AbstractAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ abstract class AbstractAdapter implements RendererInterface
/**
* Instance de Locator lorsque nous devons tenter de trouver une vue qui n'est pas à l'emplacement standard.
*/
protected ?LocatorInterface $locator = null;
protected LocatorInterface $locator;

/**
* Le nom de la mise en page utilisée, le cas échéant.
Expand All @@ -70,25 +70,19 @@ abstract class AbstractAdapter implements RendererInterface
/**
* {@inheritDoc}
*
* @param array $config Configuration actuelle de l'adapter
* @param bool $debug Devrions-nous stocker des informations sur les performances ?
* @param array $config Configuration actuelle de l'adapter
* @param string|null $viewPath Dossier principal dans lequel les vues doivent être cherchées
* @param bool $debug Devrions-nous stocker des informations sur les performances ?
*/
public function __construct(protected array $config, $viewPathLocator = null, protected bool $debug = BLITZ_DEBUG)
public function __construct(protected array $config, $viewPath = null, protected bool $debug = BLITZ_DEBUG)
{
helper('assets');

if (! empty($viewPathLocator)) {
if (is_string($viewPathLocator)) {
$this->viewPath = rtrim($viewPathLocator, '\\/ ') . DS;
} elseif ($viewPathLocator instanceof LocatorInterface) {
$this->locator = $viewPathLocator;
}
if (is_string($viewPath) && is_dir($viewPath = rtrim($viewPath, '\\/ ') . DS)) {
$this->viewPath = $viewPath;
}

if (! $this->locator instanceof LocatorInterface && ! is_dir($this->viewPath)) {
$this->viewPath = '';
$this->locator = service('locator');
}
$this->locator = service('locator');

$this->ext = preg_replace('#^\.#', '', $config['extension'] ?? $this->ext);
}
Expand Down Expand Up @@ -264,7 +258,7 @@ protected function getRenderedFile(?array $options, string $view, ?string $ext =

$file = Helpers::ensureExt($file, $ext);

if (! is_file($file) && $this->locator instanceof LocatorInterface) {
if (! is_file($file)) {
$file = $this->locator->locateFile($view, 'Views', $ext);
}

Expand Down
4 changes: 2 additions & 2 deletions src/View/Adapters/BladeAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ class BladeAdapter extends AbstractAdapter
/**
* {@inheritDoc}
*/
public function __construct(protected array $config, $viewPathLocator = null, protected bool $debug = BLITZ_DEBUG)
public function __construct(protected array $config, $viewPath = null, protected bool $debug = BLITZ_DEBUG)
{
parent::__construct($config, $viewPathLocator, $debug);
parent::__construct($config, $viewPath, $debug);

$this->engine = new Blade(
$this->viewPath ?: VIEW_PATH,
Expand Down
4 changes: 2 additions & 2 deletions src/View/Adapters/LatteAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class LatteAdapter extends AbstractAdapter
/**
* {@inheritDoc}
*/
public function __construct(protected array $config, $viewPathLocator = null, protected bool $debug = BLITZ_DEBUG)
public function __construct(protected array $config, $viewPath = null, protected bool $debug = BLITZ_DEBUG)
{
parent::__construct($config, $viewPathLocator, $debug);
parent::__construct($config, $viewPath, $debug);

$this->latte = new Engine();

Expand Down
6 changes: 3 additions & 3 deletions src/View/Adapters/NativeAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ class NativeAdapter extends AbstractAdapter
/**
* {@inheritDoc}
*/
public function __construct(protected array $config, $viewPathLocator = null, protected bool $debug = BLITZ_DEBUG)
public function __construct(protected array $config, $viewPath = null, protected bool $debug = BLITZ_DEBUG)
{
parent::__construct($config, $viewPathLocator, $debug);
parent::__construct($config, $viewPath, $debug);

$this->saveData = (bool) ($config['save_data'] ?? true);
}
Expand Down Expand Up @@ -591,7 +591,7 @@ public function required(bool|string $condition): string
/**
* Génère un champ input caché à utiliser dans les formulaires générés manuellement.
*/
public function csrf(?string $id): string
public function csrf(?string $id = null): string
{
return csrf_field($id);
}
Expand Down
4 changes: 2 additions & 2 deletions src/View/Adapters/PlatesAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ class PlatesAdapter extends AbstractAdapter
/**
* {@inheritDoc}
*/
public function __construct(protected array $config, $viewPathLocator = null, protected bool $debug = BLITZ_DEBUG)
public function __construct(protected array $config, $viewPath = null, protected bool $debug = BLITZ_DEBUG)
{
parent::__construct($config, $viewPathLocator, $debug);
parent::__construct($config, $viewPath, $debug);

$this->engine = new Engine(rtrim($this->viewPath, '/\\'), $this->ext);

Expand Down
4 changes: 2 additions & 2 deletions src/View/Adapters/SmartyAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ class SmartyAdapter extends AbstractAdapter
/**
* {@inheritDoc}
*/
public function __construct(protected array $config, $viewPathLocator = null, protected bool $debug = BLITZ_DEBUG)
public function __construct(protected array $config, $viewPath = null, protected bool $debug = BLITZ_DEBUG)
{
parent::__construct($config, $viewPathLocator, $debug);
parent::__construct($config, $viewPath, $debug);

$this->engine = new Smarty();

Expand Down
4 changes: 2 additions & 2 deletions src/View/Adapters/TwigAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class TwigAdapter extends AbstractAdapter
/**
* {@inheritDoc}
*/
public function __construct(protected array $config, $viewPathLocator = null, protected bool $debug = BLITZ_DEBUG)
public function __construct(protected array $config, $viewPath = null, protected bool $debug = BLITZ_DEBUG)
{
parent::__construct($config, $viewPathLocator, $debug);
parent::__construct($config, $viewPath, $debug);

$loader = new FilesystemLoader([
$this->viewPath,
Expand Down
2 changes: 1 addition & 1 deletion src/View/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public function render(string $view, ?array $options = null, ?bool $saveData = n

if (! is_file($file)) {
$fileOrig = $file;
$file = ($this->locator ?: service('locator'))->locateFile($view, 'Views');
$file = $this->locator->locateFile($view, 'Views');

// locateFile will return an empty string if the file cannot be found.
if (empty($file)) {
Expand Down
2 changes: 1 addition & 1 deletion src/View/View.php
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ public function setAdapter(string $adapter, array $config = []): static

$this->adapter = new self::$validAdapters[$adapter](
$config,
$config['view_path_locator'] ?? service('locator'),
$config['view_path'] ?? null,
$debug
);

Expand Down