Commit 52534b23 authored by Gabe Sullice's avatar Gabe Sullice Committed by Wim Leers
Browse files

Issue #3042124 by gabesullice, Wim Leers, logickal, benjy, Grimreaper,...

Issue #3042124 by gabesullice, Wim Leers, logickal, benjy, Grimreaper, alexpott: [regression] Empty response body when user is an administrator and an exception is thrown; some traces cannot be encoded because of recursion detection
parent d2bb36d8
Loading
Loading
Loading
Loading
+0 −36
Changes for src/Normalizer/FieldItemNormalizer.php: 0 added lines, 36 removed lines.
Original line number Diff line number Diff line
@@ -132,42 +132,6 @@ class FieldItemNormalizer extends NormalizerBase implements DenormalizerInterfac
    return $data_internal;
  }

  /**
   * Rasterizes a value recursively.
   *
   * This is mainly for configuration entities where a field can be a tree of
   * values to rasterize.
   *
   * @param mixed $value
   *   Either a scalar, an array or a rasterizable object.
   *
   * @return mixed
   *   The rasterized value.
   */
  protected static function rasterizeValueRecursive($value) {
    if (!$value || is_scalar($value)) {
      return $value;
    }
    if (is_array($value)) {
      $output = [];
      foreach ($value as $key => $item) {
        $output[$key] = static::rasterizeValueRecursive($item);
      }

      return $output;
    }
    if ($value instanceof CacheableNormalization) {
      return $value->getNormalization();
    }
    // If the object can be turned into a string it's better than nothing.
    if (method_exists($value, '__toString')) {
      return $value->__toString();
    }

    // We give up, since we do not know how to rasterize this.
    return NULL;
  }

  /**
   * Gets a field item instance for use with SerializedColumnNormalizerTrait.
   *
+1 −1
Changes for src/Normalizer/HttpExceptionNormalizer.php: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ class HttpExceptionNormalizer extends NormalizerBase {
   * {@inheritdoc}
   */
  public function normalize($object, $format = NULL, array $context = []) {
    return new HttpExceptionNormalizerValue(new CacheableMetadata(), $this->buildErrorObjects($object));
    return new HttpExceptionNormalizerValue(new CacheableMetadata(), static::rasterizeValueRecursive($this->buildErrorObjects($object)));
  }

  /**
+37 −0
Changes for src/Normalizer/NormalizerBase.php: 37 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@

namespace Drupal\jsonapi\Normalizer;

use Drupal\jsonapi\Normalizer\Value\CacheableNormalization;
use Drupal\serialization\Normalizer\NormalizerBase as SerializationNormalizerBase;

/**
@@ -20,6 +21,42 @@ abstract class NormalizerBase extends SerializationNormalizerBase {
   */
  protected $format = 'api_json';

  /**
   * Rasterizes a value recursively.
   *
   * This is mainly for configuration entities where a field can be a tree of
   * values to rasterize.
   *
   * @param mixed $value
   *   Either a scalar, an array or a rasterizable object.
   *
   * @return mixed
   *   The rasterized value.
   */
  protected static function rasterizeValueRecursive($value) {
    if (!$value || is_scalar($value)) {
      return $value;
    }
    if (is_array($value)) {
      $output = [];
      foreach ($value as $key => $item) {
        $output[$key] = static::rasterizeValueRecursive($item);
      }

      return $output;
    }
    if ($value instanceof CacheableNormalization) {
      return $value->getNormalization();
    }
    // If the object can be turned into a string it's better than nothing.
    if (method_exists($value, '__toString')) {
      return $value->__toString();
    }

    // We give up, since we do not know how to rasterize this.
    return NULL;
  }

  /**
   * {@inheritdoc}
   */
+48 −0
Changes for tests/src/Functional/JsonApiRegressionTest.php: 48 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -905,4 +905,52 @@ class JsonApiRegressionTest extends JsonApiFunctionalTestBase {
    $this->assertSame(['foo' => 'bar'], $data['data'][0]['attributes']['data']);
  }

  /**
   * Tests that the response still has meaningful error messages.
   */
  public function testRecursionDetectedWhenResponseContainsViolationsFrom3042124() {
    $this->config('jsonapi.settings')->set('read_only', FALSE)->save(TRUE);

    // Set up default request.
    $url = Url::fromUri('internal:/jsonapi/node/article');
    $request_options = [
      RequestOptions::HEADERS => [
        'Content-Type' => 'application/vnd.api+json',
        'Accept' => 'application/vnd.api+json',
      ],
      RequestOptions::JSON => [
        'data' => [
          'type' => 'node--article',
          'attributes' => [],
        ],
      ],
    ];

    // Set up test users.
    $user = $this->drupalCreateUser(['bypass node access'], 'Sam');
    $admin = $this->drupalCreateUser([], 'Gandalf', TRUE);

    // Make request as regular user.
    $request_options[RequestOptions::AUTH] = [$user->getUsername(), $user->pass_raw];
    $this->request('POST', $url, $request_options);
    $response = $this->request('POST', $url, $request_options);

    // Assert that the response has a body.
    $data = Json::decode((string) $response->getBody());
    $this->assertSame(422, $response->getStatusCode());
    $this->assertNotNull($data);
    $this->assertSame(sprintf('title: This value should not be null.'), $data['errors'][0]['detail']);

    // Make request as regular user.
    $request_options[RequestOptions::AUTH] = [$admin->getUsername(), $admin->pass_raw];
    $this->request('POST', $url, $request_options);
    $response = $this->request('POST', $url, $request_options);

    // Assert that the response has a body.
    $data = Json::decode((string) $response->getBody());
    $this->assertSame(422, $response->getStatusCode());
    $this->assertNotNull($data);
    $this->assertSame(sprintf('title: This value should not be null.'), $data['errors'][0]['detail']);
  }

}