Chris@18: serializer = $serializer; Chris@18: $this->logger = $logger; Chris@18: $this->moduleHandler = $module_handler; Chris@18: $this->appRoot = $app_root; Chris@18: } Chris@18: Chris@18: /** Chris@18: * {@inheritdoc} Chris@18: */ Chris@18: public static function getSubscribedEvents() { Chris@18: $events[KernelEvents::RESPONSE][] = ['onResponse']; Chris@18: return $events; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Sets the validator service if available. Chris@18: */ Chris@18: public function setValidator(Validator $validator = NULL) { Chris@18: if ($validator) { Chris@18: $this->validator = $validator; Chris@18: } Chris@18: elseif (class_exists(Validator::class)) { Chris@18: $this->validator = new Validator(); Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * Validates JSON:API responses. Chris@18: * Chris@18: * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event Chris@18: * The event to process. Chris@18: */ Chris@18: public function onResponse(FilterResponseEvent $event) { Chris@18: $response = $event->getResponse(); Chris@18: if (strpos($response->headers->get('Content-Type'), 'application/vnd.api+json') === FALSE) { Chris@18: return; Chris@18: } Chris@18: Chris@18: $this->doValidateResponse($response, $event->getRequest()); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Wraps validation in an assert to prevent execution in production. Chris@18: * Chris@18: * @see self::validateResponse Chris@18: */ Chris@18: public function doValidateResponse(Response $response, Request $request) { Chris@18: if (PHP_MAJOR_VERSION >= 7 || assert_options(ASSERT_ACTIVE)) { Chris@18: assert($this->validateResponse($response, $request), 'A JSON:API response failed validation (see the logs for details). Please report this in the issue queue on drupal.org'); Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * Validates a response against the JSON:API specification. Chris@18: * Chris@18: * @param \Symfony\Component\HttpFoundation\Response $response Chris@18: * The response to validate. Chris@18: * @param \Symfony\Component\HttpFoundation\Request $request Chris@18: * The request containing info about what to validate. Chris@18: * Chris@18: * @return bool Chris@18: * FALSE if the response failed validation, otherwise TRUE. Chris@18: */ Chris@18: protected function validateResponse(Response $response, Request $request) { Chris@18: // If the validator isn't set, then the validation library is not installed. Chris@18: if (!$this->validator) { Chris@18: return TRUE; Chris@18: } Chris@18: Chris@18: // Do not use Json::decode here since it coerces the response into an Chris@18: // associative array, which creates validation errors. Chris@18: $response_data = json_decode($response->getContent()); Chris@18: if (empty($response_data)) { Chris@18: return TRUE; Chris@18: } Chris@18: Chris@18: $schema_ref = sprintf( Chris@18: 'file://%s/schema.json', Chris@18: implode('/', [ Chris@18: $this->appRoot, Chris@18: $this->moduleHandler->getModule('jsonapi')->getPath(), Chris@18: ]) Chris@18: ); Chris@18: $generic_jsonapi_schema = (object) ['$ref' => $schema_ref]; Chris@18: Chris@18: return $this->validateSchema($generic_jsonapi_schema, $response_data); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Validates a string against a JSON Schema. It logs any possible errors. Chris@18: * Chris@18: * @param object $schema Chris@18: * The JSON Schema object. Chris@18: * @param string $response_data Chris@18: * The JSON string to validate. Chris@18: * Chris@18: * @return bool Chris@18: * TRUE if the string is a valid instance of the schema. FALSE otherwise. Chris@18: */ Chris@18: protected function validateSchema($schema, $response_data) { Chris@18: $this->validator->check($response_data, $schema); Chris@18: $is_valid = $this->validator->isValid(); Chris@18: if (!$is_valid) { Chris@18: $this->logger->debug("Response failed validation.\nResponse:\n@data\n\nErrors:\n@errors", [ Chris@18: '@data' => Json::encode($response_data), Chris@18: '@errors' => Json::encode($this->validator->getErrors()), Chris@18: ]); Chris@18: } Chris@18: return $is_valid; Chris@18: } Chris@18: Chris@18: }