custom/plugins/PickwareErpStarter/vendor/pickware/debug-bundle/src/ResponseExceptionListener/ResponseExceptionListenerDecorator.php line 77

Open in your IDE?
  1. <?php
  2. /*
  3.  * Copyright (c) Pickware GmbH. All rights reserved.
  4.  * This file is part of software that is released under a proprietary license.
  5.  * You must not copy, modify, distribute, make publicly available, or execute
  6.  * its contents or parts thereof without express permission by the copyright
  7.  * holder, unless otherwise permitted by law.
  8.  */
  9. declare(strict_types=1);
  10. namespace Pickware\DebugBundle\ResponseExceptionListener;
  11. use GuzzleHttp\Psr7\Message;
  12. use League\OAuth2\Server\Exception\OAuthServerException;
  13. use Nyholm\Psr7\Factory\Psr17Factory;
  14. use Pickware\HttpUtils\Sanitizer\HttpSanitizing;
  15. use Psr\Log\LoggerInterface;
  16. use Shopware\Core\Framework\Api\EventListener\ResponseExceptionListener;
  17. use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
  18. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  19. use Symfony\Component\HttpFoundation\JsonResponse;
  20. use Symfony\Component\HttpFoundation\Request;
  21. use Symfony\Component\HttpFoundation\Response;
  22. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  23. use Throwable;
  24. class ResponseExceptionListenerDecorator implements EventSubscriberInterface
  25. {
  26.     private const PICKWARE_APP_USER_AGENT_DETECTION_SUBSTRINGS = [
  27.         'com.pickware.wms',
  28.         'com.viison.pickware.POS',
  29.     ];
  30.     private ResponseExceptionListener $decoratedService;
  31.     private LoggerInterface $errorLogger;
  32.     private JwtValidator $jwtValidator;
  33.     private HttpSanitizing $httpSanitizing;
  34.     private PsrHttpFactory $psrHttpFactory;
  35.     /**
  36.      * We can't use a php type hint for the ResponseExceptionListener here since it does not implement an interface that
  37.      * contains the non-static methods, and it could be decorated by a different plugin as well.
  38.      *
  39.      * @param ResponseExceptionListener $decoratedService
  40.      */
  41.     public function __construct(
  42.         $decoratedService,
  43.         LoggerInterface $errorLogger,
  44.         JwtValidator $jwtValidator,
  45.         HttpSanitizing $httpSanitizing
  46.     ) {
  47.         $this->decoratedService $decoratedService;
  48.         $this->errorLogger $errorLogger;
  49.         $this->jwtValidator $jwtValidator;
  50.         $this->httpSanitizing $httpSanitizing;
  51.         $this->psrHttpFactory = new PsrHttpFactory(
  52.             new Psr17Factory(),
  53.             new Psr17Factory(),
  54.             new Psr17Factory(),
  55.             new Psr17Factory(),
  56.         );
  57.     }
  58.     /**
  59.      * Unfortunately, static methods can not be decorated, so we need to call the original method directly and hope that
  60.      * no other plugin wraps this method and changes its return value.
  61.      *
  62.      * @return array
  63.      */
  64.     public static function getSubscribedEvents()
  65.     {
  66.         return ResponseExceptionListener::getSubscribedEvents();
  67.     }
  68.     public function onKernelException(ExceptionEvent $originalEvent)
  69.     {
  70.         $event $this->decoratedService->onKernelException($originalEvent);
  71.         $exception $event->getThrowable();
  72.         if ($exception instanceof OAuthServerException) {
  73.             return $event;
  74.         }
  75.         if ($this->shouldAddTraceToResponse($event->getRequest(), $event->getResponse())) {
  76.             $event->setResponse($this->addTraceToResponse($event->getResponse(), $event->getThrowable()));
  77.         }
  78.         $psrRequest $this->psrHttpFactory->createRequest($event->getRequest());
  79.         if ($this->shouldLogTrace($event->getRequest())) {
  80.             $this->errorLogger->error($exception->getMessage(), [
  81.                 'exception' => $this->getTrace($exception),
  82.                 'request' => Message::toString($this->httpSanitizing->sanitizeRequest($psrRequest)),
  83.             ]);
  84.         }
  85.         return $event;
  86.     }
  87.     private function shouldAddTraceToResponse(Request $request, ?Response $response): bool
  88.     {
  89.         if (!$this->containsValidDebugHeader($request)) {
  90.             return false;
  91.         }
  92.         if (!$response) {
  93.             return false;
  94.         }
  95.         if (!($response instanceof JsonResponse)) {
  96.             return false;
  97.         }
  98.         if ($response->getStatusCode() === 401) {
  99.             return false;
  100.         }
  101.         return true;
  102.     }
  103.     private function containsValidDebugHeader(Request $request): bool
  104.     {
  105.         $debugHeader $request->headers->get('X-Pickware-Show-Trace');
  106.         return $debugHeader && $this->jwtValidator->isJwtTokenValid($debugHeader);
  107.     }
  108.     private function addTraceToResponse(JsonResponse $responseThrowable $throwable): Response
  109.     {
  110.         $content json_decode($response->getContent(), true);
  111.         $content['trace'] = $this->getTrace($throwable);
  112.         $response->setData($content);
  113.         return $response;
  114.     }
  115.     private function shouldLogTrace(Request $request): bool
  116.     {
  117.         if ($this->containsValidDebugHeader($request)) {
  118.             return true;
  119.         }
  120.         $userAgent $request->headers->get('User-Agent');
  121.         if (!$userAgent) {
  122.             return false;
  123.         }
  124.         foreach (self::PICKWARE_APP_USER_AGENT_DETECTION_SUBSTRINGS as $substring) {
  125.             if (str_contains(mb_strtolower($userAgent), mb_strtolower($substring))) {
  126.                 return true;
  127.             }
  128.         }
  129.         return false;
  130.     }
  131.     private function getTrace(Throwable $throwable): array
  132.     {
  133.         // Remove args so that no credentials are logged.
  134.         return array_map(function ($element) {
  135.             unset($element['args']);
  136.             return $element;
  137.         }, $throwable->getTrace());
  138.     }
  139. }