Skip to content
Snippets Groups Projects

Resolve #3143617 "Protect pager against"

Closes #3143617

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
4
5 use Drupal\Core\Pager\PagerParameters;
6 use Drupal\Tests\UnitTestCase;
7 use Symfony\Component\HttpFoundation\Request;
8 use Symfony\Component\HttpFoundation\RequestStack;
9
10 /**
11 * @coversDefaultClass \Drupal\Core\Pager\PagerParameters
12 * @group Pager
13 */
14 class PagerParametersTest extends UnitTestCase {
15
16 /**
17 * @covers ::getQueryParameters
18 */
19 public function testGetQueryParameters() {
  • 22 $parameters = new PagerParameters($request_stack);
    23 $query = $request_stack->getCurrentRequest()->query;
    24 $this->assertEquals([], $parameters->getQueryParameters());
    25 $query->set('page', 1);
    26 $this->assertEquals(['page' => 1], $query->all(), 'page query set correctly');
    27 $this->assertEquals([], $parameters->getQueryParameters(), 'Pager filtered from empty parameters');
    28 $query->set('test', 1);
    29 $this->assertEquals(['page' => 1, 'test' => 1], $query->all(), 'test value set correctly');
    30 $this->assertEquals(['test' => 1], $parameters->getQueryParameters(), 'Pager filtered with another parameter');
    31 }
    32
    33 /**
    34 * @covers ::findPage
    35 * @dataProvider providePagerQueries
    36 */
    37 public function testFindPage($raw_query, $parameter, $expected_query) {
  • 38 $request_stack = new RequestStack();
    39 $request_stack->push(new Request());
    40 $parameters = new PagerParameters($request_stack);
    41 $request_stack->getCurrentRequest()->query->set('page', $raw_query);
    42 // Ensure findPage finds 0 when the query is actually empty or invalid.
    43 $expected_query = $expected_query ?: [0];
    44 foreach ($expected_query as $key => $value) {
    45 $this->assertSame($value, $parameters->findPage($key));
    46 }
    47 }
    48
    49 /**
    50 * @covers ::getPagerQuery
    51 * @dataProvider providePagerQueries
    52 */
    53 public function testGetPagerQuery($raw_query, $parameter, $expected_query) {
  • 51 * @dataProvider providePagerQueries
    52 */
    53 public function testGetPagerQuery($raw_query, $parameter, $expected_query) {
    54 $request_stack = new RequestStack();
    55 $request_stack->push(new Request());
    56 $parameters = new PagerParameters($request_stack);
    57 $request_stack->getCurrentRequest()->query->set('page', $raw_query);
    58 $this->assertEquals($expected_query, $parameters->getPagerQuery());
    59 }
    60
    61 /**
    62 * Ensure missing request is handled cleanly.
    63 *
    64 * @covers ::getPagerParameter
    65 */
    66 public function testGetPagerParameterNoRequest() {
  • 69 $this->assertSame('', $parameters->getPagerParameter());
    70 }
    71
    72 /**
    73 * @covers ::getPagerParameter
    74 * @dataProvider providePagerQueries
    75 */
    76 public function testGetPagerParameter($raw_query, $parameter) {
    77 $request_stack = new RequestStack();
    78 $request_stack->push(new Request());
    79 $parameters = new PagerParameters($request_stack);
    80 $request_stack->getCurrentRequest()->query->set('page', $raw_query);
    81 $this->assertSame($parameter, $parameters->getPagerParameter());
    82 }
    83
    84 public static function providePagerQueries() {
  • 80 $request_stack->getCurrentRequest()->query->set('page', $raw_query);
    81 $this->assertSame($parameter, $parameters->getPagerParameter());
    82 }
    83
    84 public static function providePagerQueries() {
    85 return [
    86 'defensive null page value' => [NULL, '', []],
    87 // Array values aren't supported, so they default to empty.
    88 'invalid empty page array' => [[], '', []],
    89 'invalid populated array' => [[1, 2, 3], '', []],
    90 // Nothing to
    91 'empty string' => ['', '', []],
    92 // Conventional but "zero" page values.
    93 'page 0 as a string' => ['0', '0', [0]],
    94 'page 0 as a integer' => [0, '0', [0]],
    95
  • 69 $this->assertSame('', $parameters->getPagerParameter());
    70 }
    71
    72 /**
    73 * @covers ::getPagerParameter
    74 * @dataProvider providePagerQueries
    75 */
    76 public function testGetPagerParameter($raw_query, $parameter) {
    77 $request_stack = new RequestStack();
    78 $request_stack->push(new Request());
    79 $parameters = new PagerParameters($request_stack);
    80 $request_stack->getCurrentRequest()->query->set('page', $raw_query);
    81 $this->assertSame($parameter, $parameters->getPagerParameter());
    82 }
    83
    84 public static function providePagerQueries() {
  • added 1 commit

    • 314624fd - Apply 5 suggestion(s) to 1 file(s)

    Compare with previous version

  • Stephen Mustgrave added 1647 commits

    added 1647 commits

    • 314624fd...e5653c11 - 1644 commits from branch project:11.x
    • ffe9f7a1 - Merge branch '11.x' of github.com:drupal/drupal into 3143617-protect-pager-against
    • 8a3b6dbf - Merge branch '3143617-protect-pager-against' of...
    • bcaf7c1d - Address feedback

    Compare with previous version

  • Patrick Fey
    Patrick Fey @feyp started a thread on the diff
  • 73
    74 /**
    75 * @covers ::getPagerParameter
    76 * @dataProvider providePagerQueries
    77 */
    78 public function testGetPagerParameter($raw_query, $parameter) {
    79 $request_stack = new RequestStack();
    80 $request_stack->push(new Request());
    81 $parameters = new PagerParameters($request_stack);
    82 $request_stack->getCurrentRequest()->query->set('page', $raw_query);
    83 $this->assertSame($parameter, $parameters->getPagerParameter());
    84 }
    85
    86 /**
    87 * Data provider for testGetPagerParameter(), testGetPagerQuery(), and testFindPage().
    88 */
    • The change here is definitely an improvement over not having a doc block at all, but I wonder if this really addresses @quietone 's concerns:

      And since this is used multiple time it would help to explain the return values here.

      I don't see any explanation of the return values in relation to the parameters of the test methods. The test sets are named and have additional explanatory comments, which is good, I like that, but those were there before the latest commit to address the review. And I see no new explanations of the return values, so I think this is still open.

      It is a shame that we aren't using PHPUnit 11 yet. If we did, we could use named arguments and could then update the signature of the various test methods to only define those parameters they actually use. To fully address the above review comment right now, I think we could:

      1. Add a @return statement to the doc block, which explains the values of the different test sets.
      2. Already use named arguments, although they are not yet supported. In PHPUnit 10 and below, any keys in the test sets are just discarded by PHPUnit, so we should be able to already name the different arguments, but we can't remove the unused parameters from the test methods yet. So something like:
      'defensive null page value' => [
        'raw_query' => NULL, 
        'parameter' => '', 
        'expected_query' => [],
      ],

      I think doing either of those two options should be enough to fully address the earlier review as it should then be pretty clear what is in the test set combined with the new summary line.

    • Please register or sign in to reply
  • 63 /**
    64 * Ensure missing request is handled cleanly.
    65 *
    66 * @covers ::getPagerParameter
    67 */
    68 public function testGetPagerParameterNoRequest(): void {
    69 $request_stack = new RequestStack();
    70 $parameters = new PagerParameters($request_stack);
    71 $this->assertSame('', $parameters->getPagerParameter());
    72 }
    73
    74 /**
    75 * @covers ::getPagerParameter
    76 * @dataProvider providePagerQueries
    77 */
    78 public function testGetPagerParameter($raw_query, $parameter) {
  • 80 $request_stack->push(new Request());
    81 $parameters = new PagerParameters($request_stack);
    82 $request_stack->getCurrentRequest()->query->set('page', $raw_query);
    83 $this->assertSame($parameter, $parameters->getPagerParameter());
    84 }
    85
    86 /**
    87 * Data provider for testGetPagerParameter(), testGetPagerQuery(), and testFindPage().
    88 */
    89 public static function providePagerQueries(): array {
    90 return [
    91 'defensive null page value' => [NULL, '', []],
    92 // Array values aren't supported, so they default to empty.
    93 'invalid empty page array' => [[], '', []],
    94 'invalid populated array' => [[1, 2, 3], '', []],
    95 // Nothing to
  • Clicked the wrong button too early, not finished yet. Anyway, here is the current state.

  • Stephen Mustgrave added 109 commits

    added 109 commits

    • bcaf7c1d...694cb802 - 107 commits from branch project:11.x
    • b9f852d0 - Merge branch '11.x' of github.com:drupal/drupal into 3143617-protect-pager-against
    • a05a01fe - Address feedback

    Compare with previous version

  • 64 64 public function getPagerParameter() {
    65 65 $request = $this->requestStack->getCurrentRequest();
    66 66 if ($request) {
    67 return $request->query->get('page', '');
    67 $params = $request->query->all();
    68 $page = $params['page'] ?? '';
    69 // The "page" can be an array so validate the type before casting,
    70 // i.e., "?page[offset]=12"
    71 return is_scalar($page) ? (string) $page : '';
    • Comment on lines -67 to +71

      I'm pretty certain the intent of the change in Symfony was to safeguard against behaviour changes, e.g. if you blindly use ->get('some_param') and expect a scalar but someone has diddled the URL and its an array unexpected things might happen, hence the developer has to make a conscious choice between using ->get('page') and ->all('page'). The exception is there to protect us against unexpected params.

      So let's use the new API and simplify the amount of changes dramatically.

      Suggested change
      Applied
      67 $params = $request->query->all();
      68 $page = $params['page'] ?? '';
      69 // The "page" can be an array so validate the type before casting,
      70 // i.e., "?page[offset]=12"
      71 return is_scalar($page) ? (string) $page : '';
      67 try {
      68 return $request->query->get('page', '');
      69 }
      70 catch (\Symfony\Component\HttpFoundation\Exception\BadRequestException) {
      71 // Someone has tampered with the 'page' parameter.
      72 return '';
      73 }
    • Pierre Rudloff changed this line in version 7 of the diff

      changed this line in version 7 of the diff

    • Please register or sign in to reply
  • 24 $parameters = new PagerParameters($request_stack);
    25 $query = $request_stack->getCurrentRequest()->query;
    26 $this->assertEquals([], $parameters->getQueryParameters());
    27 $query->set('page', 1);
    28 $this->assertEquals(['page' => 1], $query->all(), 'page query set correctly');
    29 $this->assertEquals([], $parameters->getQueryParameters(), 'Pager filtered from empty parameters');
    30 $query->set('test', 1);
    31 $this->assertEquals(['page' => 1, 'test' => 1], $query->all(), 'test value set correctly');
    32 $this->assertEquals(['test' => 1], $parameters->getQueryParameters(), 'Pager filtered with another parameter');
    33 }
    34
    35 /**
    36 * @covers ::findPage
    37 * @dataProvider providePagerQueries
    38 */
    39 public function testFindPage($raw_query, $parameter, $expected_query): void {
  • 40 $request_stack = new RequestStack();
    41 $request_stack->push(new Request());
    42 $parameters = new PagerParameters($request_stack);
    43 $request_stack->getCurrentRequest()->query->set('page', $raw_query);
    44 // Ensure findPage finds 0 when the query is actually empty or invalid.
    45 $expected_query = $expected_query ?: [0];
    46 foreach ($expected_query as $key => $value) {
    47 $this->assertSame($value, $parameters->findPage($key));
    48 }
    49 }
    50
    51 /**
    52 * @covers ::getPagerQuery
    53 * @dataProvider providePagerQueries
    54 */
    55 public function testGetPagerQuery($raw_query, $parameter, $expected_query): void {
  • 63 /**
    64 * Ensure missing request is handled cleanly.
    65 *
    66 * @covers ::getPagerParameter
    67 */
    68 public function testGetPagerParameterNoRequest(): void {
    69 $request_stack = new RequestStack();
    70 $parameters = new PagerParameters($request_stack);
    71 $this->assertSame('', $parameters->getPagerParameter());
    72 }
    73
    74 /**
    75 * @covers ::getPagerParameter
    76 * @dataProvider providePagerQueries
    77 */
    78 public function testGetPagerParameter($raw_query, $parameter): void {
  • Nice work on the unit test

  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Please register or sign in to reply
    Loading