1. This implements \Drupal\Core\Routing\Access\AccessInterface. All implementations of that interface have a name ending in AccessCheck, so this should too.
  2. A link the relevant portion in the spec is missing: http://jsonapi.org/format/#query-parameters.
  3. The spec calls it "Query Parameters". And it's about valid "implementation-specific" query parameters. The "custom" is okay, but I think "parameters" is too vague". Let's call it "query parameters", just like the spec.
  4. The route requirement _custom_parameter_names is too generic. It should be prefixed with the modulename/spec. And it should include "query", per the previous point.
  5. Its validation does not actually validate everything that the spec outlines. Added @todos for this.
  6. And perhaps this validation actually belongs in a helper class, because it applies also to member names in the generated JSON documents. Only a tiny portion is specific to query parameters. Added @todos for this.
CommentFileSizeAuthor
#51 2829328--custom_parameter_names__cleanup--39.patch8.13 KBe0ipso
#42 custom_parameter_names__cleanup-2829328-39.patch17.77 KBWim Leers
#42 interdiff.txt2.22 KBWim Leers
#38 custom_parameter_names__cleanup-2829328-37.patch17.72 KBWim Leers
#38 interdiff.txt7.5 KBWim Leers
#36 custom_parameter_names__cleanup-2829328-36.patch17.18 KBWim Leers
#36 interdiff.txt2.73 KBWim Leers
#34 custom_parameter_names__cleanup-2829328-34.patch17.92 KBWim Leers
#34 interdiff.txt4.81 KBWim Leers
#31 custom_parameter_names__cleanup-2829328-31.patch17.38 KBWim Leers
#31 interdiff.txt1.57 KBWim Leers
#27 custom_parameter_names__cleanup-2829328-26.patch16.99 KBWim Leers
#27 interdiff.txt4.14 KBWim Leers
#25 custom_parameter_names__cleanup-2829328-25.patch13.25 KBWim Leers
#25 interdiff.txt4.94 KBWim Leers
#19 custom_parameter_names__cleanup-2829328-19.patch11.56 KBWim Leers
#19 interdiff.txt2.12 KBWim Leers
#18 custom_parameter_names__cleanup-2829328-18.patch11.74 KBWim Leers
#18 interdiff.txt3.8 KBWim Leers
#16 custom_parameter_names__cleanup-2829328-16.patch11.47 KBWim Leers
#16 interdiff.txt1.08 KBWim Leers
#15 custom_parameter_names__cleanup-2829328-15.patch10.83 KBWim Leers
#15 interdiff.txt2.38 KBWim Leers
#13 interdiff.txt1 KBdawehner
#13 2829328-13.patch8.48 KBdawehner
#12 interdiff.txt2.57 KBWim Leers
#12 custom_parameter_names__cleanup-2829328-12.patch8.91 KBWim Leers
#10 custom_parameter_names__cleanup-2829328-10.patch8.19 KBWim Leers
#6 2829328-6.patch8.99 KBe0ipso
#2 2829328-2.patch8.36 KBWim Leers
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers created an issue. See original summary.

Wim Leers’s picture

Status: Active » Needs review
FileSize
8.36 KB
e0ipso’s picture

Great stuff!

I only want to add that #2779963: [BUGFIX] Protect fields: "type" and "id" should address the last concern when it gets resolved.

Wim Leers’s picture

Cool :)

Wim Leers’s picture

e0ipso’s picture

Re-rolled #2.

I'm setting this to Needs work because we need to implement the todos in there. But I'm letting the tests run first.

e0ipso’s picture

Status: Needs review » Needs work
Wim Leers’s picture

Assigned: Unassigned » Wim Leers

I'll be pushing this forward later today.

e0ipso’s picture

w00t!

Wim Leers’s picture

Wim Leers’s picture

Issue summary: View changes

Updating the IS to reflect what is already done.

Wim Leers’s picture

#2844717: Validation of parameter names doesn't correctly check for backslash and control characters did a great job of also testing against the ASCII control characters, which is mentioned at http://jsonapi.org/format/ as:

U+0000 to U+001F (C0 Controls)

But really, DEL (0x7F or U+007F in Unicode notation) should also be tested explicitly, since it's usually considered part of the C0 controls: https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C0_.28ASCII_and_de...

While not technically part of the C0 control character range, the following two characters are defined in ISO/IEC 2022 as always being available regardless of which sets of control characters and graphics characters have been registered. They can be thought of as having some characteristics of control characters.

  • space (U+0020)
  • DEL (U+007F)

The JSON API spec specifically calls out that space is allowed but not recommended (because it's not URL-safe).

This resolves one of the @todos I had added in #2.

dawehner’s picture

  1. +++ b/src/Routing/Routes.php
    @@ -100,7 +100,7 @@ class Routes implements ContainerInjectionInterface {
             ->setRequirement('_format', 'api_json')
    -        ->setRequirement('_custom_parameter_names', 'TRUE')
    +        ->setRequirement('_json_api_custom_parameter_names', 'TRUE')
    

    Totally +1 to rename that

  2. +++ b/tests/src/Unit/Access/CustomParameterNamesAccessCheckTest.php
    @@ -84,8 +86,14 @@ class CustomParameterNamesTest extends \PHPUnit_Framework_TestCase {
    +      $data['unsafe-ascii-control-' . $ascii] = ['kitt' . $ascii_control_char . 'ens', FALSE];
    

    Note: This generates just one test case in total

Wim Leers’s picture

Thanks for the review & reroll, @dawehner!

A big update will follow shortly.

Wim Leers’s picture

The code is now called CustomParameterNamesAccessCheck, but it's wrong on at least two levels:

  1. much of the logic is the same for member names, not just parameters and custom parameters
  2. actually, this is validating parameters (as in official ones), not custom parameters — this is what the spec writes about custom parameters:

    Implementation specific query parameters MUST adhere to the same constraints as member names with the additional requirement that they MUST contain at least one non a-z character (U+0061 to U+007A). It is RECOMMENDED that a U+002D HYPHEN-MINUS, “-“, U+005F LOW LINE, “_”, or capital letter is used (e.g. camelCasing).

    If a server encounters a query parameter that does not follow the naming conventions above, and the server does not know how to process it as a query parameter from this specification, it MUST return 400 Bad Request.

    Note: This is to preserve the ability of JSON API to make additive additions to standard query parameters without conflicting with existing implementations.

Conclusions:

  1. we should move the logic for validating member names out of this class, and just reuse it
  2. we should be distinguishing (official) parameter names vs custom/implementation-specific parameter names

The fact that CustomParameterNamesAccessCheck is muddying all these concepts together makes it A) hard to understand, B) not future-proof.


So here's a first step: moving the "restricted characters" to a "JSON API spec" class.

Wim Leers’s picture

I noticed that the existing test coverage in \Drupal\Tests\jsonapi\Unit\Access\CustomParameterNamesAccessCheckTest is incomplete in at least two important ways:

  1. Member names MUST contain at least one character. is not tested: we should test that a single-character member name/parameter name is considered valid
  2. U+0080 and above (non-ASCII Unicode characters; not recommended, not URL safe) is not tested: we should test that the "highest" allowed character is indeed allowed: U+10FFFF

In adding those two test cases, I also noticed that the "unsafe chars" test cases were using instead of " and instead of '. So, fixed that too. This should now have several failing tests.

Status: Needs review » Needs work

The last submitted patch, 16: custom_parameter_names__cleanup-2829328-16.patch, failed testing.

Wim Leers’s picture

As predicted, #16 failed. It has these 3 failures:

1) Drupal\Tests\jsonapi\Unit\Access\CustomParameterNamesAccessCheckTest::testJsonApiParamsValidation with data set "unicode-above-u+0080-highest-allowed" ('12\u{10FFFF}', true)
Failed asserting that false is true.

/var/www/html/modules/contrib/jsonapi/tests/src/Unit/Access/CustomParameterNamesAccessCheckTest.php:29

2) Drupal\Tests\jsonapi\Unit\Access\CustomParameterNamesAccessCheckTest::testJsonApiParamsValidation with data set "unsafe-"" ('kitt"ens', false)
Failed asserting that true is false.

/var/www/html/modules/contrib/jsonapi/tests/src/Unit/Access/CustomParameterNamesAccessCheckTest.php:32

3) Drupal\Tests\jsonapi\Unit\Access\CustomParameterNamesAccessCheckTest::testJsonApiParamsValidation with data set "unsafe-'" ('kitt'ens', false)
Failed asserting that true is false.

So the first new test case in #16 actually passed. The second did not, so the current validation is incorrect.

Finally, the incorrect "unsafe chars" I noticed that I fixed also resulted in failures. This means that that validation is incorrect as well.

This fixes all that.

Wim Leers’s picture

At this point, because of the improved (now actually correct) validation of member names, it becomes pointless/unnecessary to validate against the list of restricted characters too. The restricted characters are specifically called out for less strict implementations. But given that we can now strictly validate against the list of allowed characters (as defined by the JSON API spec), we can simplify things a lot.

The last submitted patch, 18: custom_parameter_names__cleanup-2829328-18.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 19: custom_parameter_names__cleanup-2829328-19.patch, failed testing.

dawehner’s picture

  1. +++ b/jsonapi.services.yml
    @@ -79,10 +79,10 @@ services:
    +    class: Drupal\jsonapi\Access\CustomParameterNamesAccessCheck
         tags:
    -      - { name: access_check, applies_to: _custom_parameter_names }
    +      - { name: access_check, applies_to: _json_api_custom_parameter_names }
    
    +++ b/src/Access/CustomParameterNamesAccessCheck.php
    @@ -4,25 +4,33 @@ namespace Drupal\jsonapi\Access;
    +  public function access(Route $route, Request $request) {
    +    assert('$route->getRequirement("_json_api_custom_parameter_names") === "TRUE"');
    +
    

    Technically this adds some coupling which didn't existed before. Not sure this is really needed.

  2. +++ b/src/JsonApiSpec.php
    @@ -0,0 +1,74 @@
    +final class JsonApiSpec {
    

    People will argue about this finalness when this comes into the core review process. Just take it off, but I totally get why you did it.

  3. +++ b/tests/src/Unit/Access/CustomParameterNamesAccessCheckTest.php
    @@ -17,11 +18,12 @@ class CustomParameterNamesTest extends \PHPUnit_Framework_TestCase {
    @@ -48,6 +50,10 @@ class CustomParameterNamesTest extends \PHPUnit_Framework_TestCase {
    

    I'm wondering whether we should move most of this unit test over to a dedicated test for the JsonApiSpec class.

e0ipso’s picture

I am good with where this is right now. I am not particularly concerned about @dawehner's comments above, but if it's important from his judgement that's good enough for me.

dawehner’s picture

Some things are just so simple to fix that its not worth to worry about.

Wim Leers’s picture

This moves the bulk of the tests over from CustomParameterNamesAccessCheckTest to JsonApiSpecTest. This allows for proper unit testing.

This addresses #22.3. It was already in progress before #22.3 was posted :)

Status: Needs review » Needs work

The last submitted patch, 25: custom_parameter_names__cleanup-2829328-25.patch, failed testing.

Wim Leers’s picture

This adds a \Drupal\jsonapi\JsonApiSpec::isValidCustomQueryParameter method, as per #15.

Includes additional unit test coverage to ensure the additional requirements are met.

This also means the final remaining @todo can be removed :)

Status: Needs review » Needs work

The last submitted patch, 27: custom_parameter_names__cleanup-2829328-26.patch, failed testing.

Wim Leers’s picture

+++ b/tests/src/Unit/JsonApiSpecTest.php
@@ -0,0 +1,112 @@
+    $data['unicode-above-u+0080-highest-allowed'] = ["12\u{10FFFF}", TRUE];

This is what is causing the test failures on PHP 5. Apparently it's a PHP 7 feature: http://php.net/manual/en/migration70.new-features.php#migration70.new-fe....

Looking into a solution.

Wim Leers’s picture

And this:

  const MEMBER_NAME_REGEXP = '/^' .
    // First character must be "globally allowed". Length must be >=1.
    self::MEMBER_NAME_GLOBALLY_ALLOWED_CHARACTER_CLASS . '{1}' .
    '(' .
      // As many non-globally allowed characters as desired.
      self::MEMBER_NAME_INNER_ALLOWED_CHARACTERS . '*' .
      // If lenght is >1, then it must end in a "globally allowed" character.
      self::MEMBER_NAME_GLOBALLY_ALLOWED_CHARACTER_CLASS . '{1}' .
    // >1 characters is optional.
    ')?' .
    '$/u';

is triggering a parse error on PHP 5.5:

PHP Parse error:  syntax error, unexpected '.', expecting ',' or ';' in /Users/wim.leers/Work/d8/modules/jsonapi/src/JsonApiSpec.php on line 48

Apparently constant expressions are a PHP 5.6 feature: http://php.net/manual/en/migration56.new-features.php.

Wim Leers’s picture

#27 failed precisely for the reasons mentioned in #15: the logic in HEAD was validating all parameter names, including official ones. We need to exclude official ones from "custom parameter" validation. We could apply member name validation to it, but it'd be pointless, since they're official query parameters anyway.

This fixes that.

I also took the opportunity to massively simplify \Drupal\jsonapi\Access\CustomParameterNamesAccessCheck::validate().

Wim Leers’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 31: custom_parameter_names__cleanup-2829328-31.patch, failed testing.

Wim Leers’s picture

This makes the patch work in 5.5 and 5.6 too.

+++ b/tests/src/Unit/JsonApiSpecTest.php
@@ -37,7 +37,7 @@ class JsonApiSpecTest extends UnitTestCase {
-    $data['unicode-above-u+0080-highest-allowed'] = ["12\u{10FFFF}", TRUE];
+    $data['unicode-above-u+0080-highest-allowed'] = ["12<U+10FFFF>", TRUE];

This is for both PHP 5.5 and PHP 5.6.

All other changes are only necessary for PHP 5.5.

P.S.: you can't quote that line of the diff directly, because drupal.org still runs on Drupal 7, which does not accept 4-byte Unicode characters.

dawehner’s picture

#27 failed precisely for the reasons mentioned in #15: the logic in HEAD was validating all parameter names, including official ones. We need to exclude official ones from "custom parameter" validation.

Why can't things be nice, seriously ...

  1. +++ b/src/Access/CustomParameterNamesAccessCheck.php
    @@ -41,21 +49,18 @@ class CustomParameterNames implements AccessInterface {
    +      // Ignore reserved (official) query parameters.
    +      if (in_array($name, JsonApiSpec::RESERVED_QUERY_PARAMETERS)) {
    +        continue;
           }
    
    +++ b/src/JsonApiSpec.php
    @@ -0,0 +1,103 @@
    +
    +  /**
    +   * The reserved (official) query parameters.
    +   */
    +  const RESERVED_QUERY_PARAMETERS = [
    +    'sort',
    +    'page',
    +    'filter',
    +  ];
    

    I'm wondering whether there should be its own helper method for that.

  2. +++ b/src/JsonApiSpec.php
    @@ -0,0 +1,103 @@
    +    // First character must be "globally allowed". Length must be >=1.
    +    self::MEMBER_NAME_GLOBALLY_ALLOWED_CHARACTER_CLASS . '{1}' .
    +    '(' .
    +      // As many non-globally allowed characters as desired.
    +      self::MEMBER_NAME_INNER_ALLOWED_CHARACTERS . '*' .
    +      // If lenght is >1, then it must end in a "globally allowed" character.
    +      self::MEMBER_NAME_GLOBALLY_ALLOWED_CHARACTER_CLASS . '{1}' .
    +    // >1 characters is optional.
    +    ')?' .
    +    '$/u';
    

    Wow I really like this documentation!

  3. +++ b/tests/src/Unit/JsonApiSpecTest.php
    @@ -0,0 +1,112 @@
    + */
    +class JsonApiSpecTest extends UnitTestCase {
    +
    

    Cool, thank you!

Wim Leers’s picture

#22:

  1. Technically this adds some coupling which didn't existed before. Not sure this is really needed.

    Hm… I guess that's true. Also: I don't see what the point of this is anymore. I think I did it because the test coverage was not setting this route requirement or something like that. In any case: agreed, removed.

  2. Done.

#35:

  1. Heh, #34 already did that :)
  2. :)
  3. :)
dawehner’s picture

Status: Needs review » Reviewed & tested by the community

This looks great for me!

Wim Leers’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
7.5 KB
17.72 KB

Did a final round of clean-up:

  1. added the official/reserved query parameter names to the CustomQueryParameterNamesAccessCheckTest test coverage
  2. clarified/corrected docs
  3. renamed _json_api_custom_parameter_names to _jsonapi_custom_query_parameter_names
  4. renamed CustomParameterNamesAccessCheck to CustomQueryParameterNamesAccessCheck

  • e0ipso committed 3ee6fe9 on 8.x-1.x authored by Wim Leers
    fix(Maintainability) Clean up Drupal\jsonapi\Access\CustomParameterNames...
e0ipso’s picture

Status: Needs review » Fixed

Thanks gentlemen! You are awesome and the dream of any contrib maintainer.

e0ipso’s picture

darn! I just saw #38! I jumped the gun.

Wim Leers’s picture

And just some final nits.

Wim Leers’s picture

Status: Fixed » Needs review
dawehner’s picture

Status: Needs review » Reviewed & tested by the community

The last submitted patch, 38: custom_parameter_names__cleanup-2829328-37.patch, failed testing.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 42: custom_parameter_names__cleanup-2829328-39.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Reviewed & tested by the community

It's failing because it's already committed.

Wim Leers’s picture

Issue summary: View changes
dawehner’s picture

Status: Reviewed & tested by the community » Needs work

So I guess you want to actually provide a different patch which is basically just the interdiff?

Wim Leers’s picture

Status: Needs work » Reviewed & tested by the community

I figured @e0ipso would just revert his commit for this issue and then commit the now-RTBC patch.

e0ipso’s picture

For the record, this is the patch git is coming up with after reverting the old and applying the new.

  • e0ipso committed fe6de6b on 8.x-1.x authored by Wim Leers
    fix(Maintainability) Clean up Drupal\jsonapi\Access\CustomParameterNames...
e0ipso’s picture

Status: Reviewed & tested by the community » Fixed
Wim Leers’s picture

Yay, thanks!

Wim Leers’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.