vendor/easycorp/easyadmin-bundle/src/Router/UrlSigner.php line 12

Open in your IDE?
  1. <?php
  2. namespace EasyCorp\Bundle\EasyAdminBundle\Router;
  3. use EasyCorp\Bundle\EasyAdminBundle\Config\Option\EA;
  4. trigger_deprecation(
  5.     'easycorp/easyadmin-bundle',
  6.     '4.1.0',
  7.     'EasyAdmin URLs no longer include signatures because they don\'t provide any additional security. The "%s" class will be removed in future EasyAdmin versions, so you should stop using it.',
  8.     __CLASS__
  9. );
  10. /**
  11.  * This class is entirely based on Symfony\Component\HttpKernel\UriSigner.
  12.  * (c) Fabien Potencier <fabien@symfony.com> - MIT License.
  13.  *
  14.  * @author Javier Eguiluz <javier.eguiluz@gmail.com>
  15.  */
  16. final class UrlSigner
  17. {
  18.     private string $kernelSecret;
  19.     public function __construct(string $kernelSecret)
  20.     {
  21.         $this->kernelSecret $kernelSecret;
  22.     }
  23.     /**
  24.      * Signs a URL adding a query parameter with a hash generated
  25.      * with the values of some of the URL query parameters.
  26.      */
  27.     public function sign(string $url): string
  28.     {
  29.         $urlParts parse_url($url);
  30.         if (isset($urlParts['query'])) {
  31.             parse_str($urlParts['query'], $queryParams);
  32.         } else {
  33.             $queryParams = [];
  34.         }
  35.         $queryParams[EA::URL_SIGNATURE] = $this->computeHash($this->getQueryParamsToSign($queryParams));
  36.         return $this->buildUrl($urlParts$queryParams);
  37.     }
  38.     /**
  39.      * Checks that a URL contains a valid signature.
  40.      */
  41.     public function check(string $url): bool
  42.     {
  43.         $urlParts parse_url($url);
  44.         if (isset($urlParts['query'])) {
  45.             parse_str($urlParts['query'], $queryParams);
  46.         } else {
  47.             $queryParams = [];
  48.         }
  49.         // this differs from Symfony's UriSigner behavior: if the URL doesn't contain any
  50.         // query parameters, then consider that the signature is OK (even if there's no signature)
  51.         if ([] === $queryParams) {
  52.             return true;
  53.         }
  54.         if (!isset($queryParams[EA::URL_SIGNATURE]) || empty($queryParams[EA::URL_SIGNATURE])) {
  55.             return false;
  56.         }
  57.         $expectedHash $queryParams[EA::URL_SIGNATURE];
  58.         $calculatedHash $this->computeHash($this->getQueryParamsToSign($queryParams));
  59.         return hash_equals($calculatedHash$expectedHash);
  60.     }
  61.     private function computeHash(array $queryParameters): string
  62.     {
  63.         // Base64 hashes include some characters which are not compatible with
  64.         // query strings, so we replace them to avoid encoding them in the query string
  65.         return str_replace(
  66.             ['+''/''='],
  67.             ['-''_'''],
  68.             base64_encode(hash_hmac('sha256'http_build_query($queryParameters), $this->kernelSecrettrue))
  69.         );
  70.     }
  71.     /**
  72.      * Instead of signing the entire URL, including all its query parameters,
  73.      * sign only a few parameters that can be used to attack a backend by:.
  74.      *
  75.      *   * Enumerating all entities of certain type (EA::ENTITY_ID)
  76.      *   * Accessing all application entities (EA::CRUD_CONTROLLER_FQCN)
  77.      *   * Accessing any CRUD controller method (EA::CRUD_ACTION)
  78.      *   * Accessing any application route (EA::ROUTE_NAME)
  79.      *   * Meddling with the parameters of any application route (EA::ROUTE_PARAMS)
  80.      *
  81.      * The rest of query parameters are not relevant for the signature (EA::PAGE, EA::SORT, etc.)
  82.      * or are dynamically set by the user (EA::QUERY, EA::FILTERS, etc.) so they can't be
  83.      * included in a signature calculated before providing that data.
  84.      */
  85.     private function getQueryParamsToSign(array $queryParams): array
  86.     {
  87.         $signableQueryParams array_intersect_key($queryParams, [
  88.             EA::CRUD_ACTION => 0,
  89.             EA::CRUD_CONTROLLER_FQCN => 1,
  90.             EA::ENTITY_ID => 2,
  91.             EA::ROUTE_NAME => 3,
  92.             EA::ROUTE_PARAMS => 4,
  93.         ]);
  94.         ksort($signableQueryParams\SORT_STRING);
  95.         return $signableQueryParams;
  96.     }
  97.     private function buildUrl(array $urlParts, array $queryParams = []): string
  98.     {
  99.         ksort($queryParams\SORT_STRING);
  100.         $urlParts['query'] = http_build_query($queryParams'''&');
  101.         $scheme = isset($urlParts['scheme']) ? $urlParts['scheme'].'://' '';
  102.         $host $urlParts['host'] ?? '';
  103.         $port = isset($urlParts['port']) ? ':'.$urlParts['port'] : '';
  104.         $user $urlParts['user'] ?? '';
  105.         $pass = isset($urlParts['pass']) ? ':'.$urlParts['pass'] : '';
  106.         $pass = ($user || $pass) ? "$pass@" '';
  107.         $path $urlParts['path'] ?? '';
  108.         $query = isset($urlParts['query']) && $urlParts['query'] ? '?'.$urlParts['query'] : '';
  109.         $fragment = isset($urlParts['fragment']) ? '#'.$urlParts['fragment'] : '';
  110.         return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
  111.     }
  112. }