Browse Source

Implemented #504

Maurits van der Schee 6 years ago
parent
commit
e4c4048f4e
3 changed files with 158 additions and 3 deletions
  1. 3
    0
      README.md
  2. 87
    3
      api.php
  3. 68
    0
      src/Tqdev/PhpCrudApi/Middleware/PageLimitsMiddleware.php

+ 3
- 0
README.md View File

@@ -161,6 +161,7 @@ You can enable the following middleware using the "middlewares" config parameter
161 161
 - "validation": Return input validation errors for custom rules
162 162
 - "sanitation": Apply input sanitation on create and update
163 163
 - "multiTenancy": Restricts tenants access in a multi-tenant scenario
164
+- "pageLimits": Restricts list operations to become too heavy
164 165
 - "customization": Provides handlers for request and response customization
165 166
 
166 167
 The "middlewares" config parameter is a comma separated list of enabled middlewares.
@@ -197,6 +198,8 @@ You can tune the middleware behavior using middleware specific configuration par
197 198
 - "validation.handler": Handler to implement validation rules for input values ("")
198 199
 - "sanitation.handler": Handler to implement sanitation rules for input values ("")
199 200
 - "multiTenancy.handler": Handler to implement simple multi-tenancy rules ("")
201
+- "pageLimits.pages": The maximum page number that may be requested ("100")
202
+- "pageLimits.records": The maximum number of records that a page may contain ("1000")
200 203
 - "customization.beforeHandler": Handler to implement request customization ("")
201 204
 - "customization.afterHandler": Handler to implement response customization ("")
202 205
 

+ 87
- 3
api.php View File

@@ -3463,6 +3463,65 @@ class MultiTenancyMiddleware extends Middleware
3463 3463
     }
3464 3464
 }
3465 3465
 
3466
+// file: src/Tqdev/PhpCrudApi/Middleware/PageLimitsMiddleware.php
3467
+
3468
+class PageLimitsMiddleware extends Middleware
3469
+{
3470
+    private $reflection;
3471
+
3472
+    public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
3473
+    {
3474
+        parent::__construct($router, $responder, $properties);
3475
+        $this->reflection = $reflection;
3476
+        $this->utils = new RequestUtils($reflection);
3477
+    }
3478
+
3479
+    private function getMissingOrderParam(ReflectedTable $table): String
3480
+    {
3481
+        $pk = $table->getPk();
3482
+        if (!$pk) {
3483
+            $columnNames = $table->getColumnNames();
3484
+            if (!$columnNames) {
3485
+                return '';
3486
+            }
3487
+            return $columnNames[0];
3488
+        }
3489
+        return $pk->getName();
3490
+    }
3491
+
3492
+    public function handle(Request $request): Response
3493
+    {
3494
+        $operation = $this->utils->getOperation($request);
3495
+        if ($operation == 'list') {
3496
+            $tableName = $request->getPathSegment(2);
3497
+            $table = $this->reflection->getTable($tableName);
3498
+            if ($table) {
3499
+                $params = $request->getParams();
3500
+                if (!isset($params['order']) || !$params['order']) {
3501
+                    $params['order'] = array($this->getMissingOrderParam($table));
3502
+                }
3503
+                $maxPage = (int) $this->getProperty('pages', '100');
3504
+                if (isset($params['page']) && $params['page']) {
3505
+                    if (strpos($params['page'][0], ',') === false) {
3506
+                        $params['page'] = array(min($params['page'][0], $maxPage));
3507
+                    } else {
3508
+                        list($page, $size) = explode(',', $params['page'][0], 2);
3509
+                        $params['page'] = array(min($page, $maxPage) . ',' . $size);
3510
+                    }
3511
+                }
3512
+                $maxSize = (int) $this->getProperty('records', '1000');
3513
+                if (!isset($params['size']) || !$params['size']) {
3514
+                    $params['size'] = array($maxSize);
3515
+                } else {
3516
+                    $params['size'] = array(min($params['size'][0], $maxSize));
3517
+                }
3518
+                $request->setParams($params);
3519
+            }
3520
+        }
3521
+        return $this->next->handle($request);
3522
+    }
3523
+}
3524
+
3466 3525
 // file: src/Tqdev/PhpCrudApi/Middleware/SanitationMiddleware.php
3467 3526
 
3468 3527
 class SanitationMiddleware extends Middleware
@@ -4464,7 +4523,7 @@ class PaginationInfo
4464 4523
         return $offset;
4465 4524
     }
4466 4525
 
4467
-    public function getPageSize(array $params): int
4526
+    private function getPageSize(array $params): int
4468 4527
     {
4469 4528
         $pageSize = $this->DEFAULT_PAGE_SIZE;
4470 4529
         if (isset($params['page'])) {
@@ -4489,6 +4548,23 @@ class PaginationInfo
4489 4548
         return $numberOfRows;
4490 4549
     }
4491 4550
 
4551
+    public function getPageLimit(array $params): int
4552
+    {
4553
+        $pageLimit = -1;
4554
+        if ($this->hasPage($params)) {
4555
+            $pageLimit = $this->getPageSize($params);
4556
+        }
4557
+        $resultSize = $this->getResultSize($params);
4558
+        if ($resultSize >= 0) {
4559
+            if ($pageLimit >= 0) {
4560
+                $pageLimit = min($pageLimit, $resultSize);
4561
+            } else {
4562
+                $pageLimit = $resultSize;
4563
+            }
4564
+        }
4565
+        return $pageLimit;
4566
+    }
4567
+
4492 4568
 }
4493 4569
 
4494 4570
 // file: src/Tqdev/PhpCrudApi/Record/PathTree.php
@@ -4675,11 +4751,11 @@ class RecordService
4675 4751
         $columnOrdering = $this->ordering->getColumnOrdering($table, $params);
4676 4752
         if (!$this->pagination->hasPage($params)) {
4677 4753
             $offset = 0;
4678
-            $limit = $this->pagination->getResultSize($params);
4754
+            $limit = $this->pagination->getPageLimit($params);
4679 4755
             $count = 0;
4680 4756
         } else {
4681 4757
             $offset = $this->pagination->getPageOffset($params);
4682
-            $limit = $this->pagination->getPageSize($params);
4758
+            $limit = $this->pagination->getPageLimit($params);
4683 4759
             $count = $this->db->selectCount($table, $condition);
4684 4760
         }
4685 4761
         $records = $this->db->selectAll($table, $columnNames, $condition, $columnOrdering, $offset, $limit);
@@ -5082,6 +5158,9 @@ class Api
5082 5158
                 case 'xsrf':
5083 5159
                     new XsrfMiddleware($router, $responder, $properties);
5084 5160
                     break;
5161
+                case 'pageLimits':
5162
+                    new PageLimitsMiddleware($router, $responder, $properties, $reflection);
5163
+                    break;
5085 5164
                 case 'customization':
5086 5165
                     new CustomizationMiddleware($router, $responder, $properties, $reflection);
5087 5166
                     break;
@@ -5428,6 +5507,11 @@ class Request
5428 5507
         return $this->params;
5429 5508
     }
5430 5509
 
5510
+    public function setParams(array $params) /*: void*/
5511
+    {
5512
+        $this->params = $params;
5513
+    }
5514
+
5431 5515
     public function getBody() /*: ?array*/
5432 5516
     {
5433 5517
         return $this->body;

+ 68
- 0
src/Tqdev/PhpCrudApi/Middleware/PageLimitsMiddleware.php View File

@@ -0,0 +1,68 @@
1
+<?php
2
+namespace Tqdev\PhpCrudApi\Middleware;
3
+
4
+use Tqdev\PhpCrudApi\Column\ReflectionService;
5
+use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
6
+use Tqdev\PhpCrudApi\Controller\Responder;
7
+use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
8
+use Tqdev\PhpCrudApi\Middleware\Router\Router;
9
+use Tqdev\PhpCrudApi\Record\RequestUtils;
10
+use Tqdev\PhpCrudApi\Request;
11
+use Tqdev\PhpCrudApi\Response;
12
+
13
+class PageLimitsMiddleware extends Middleware
14
+{
15
+    private $reflection;
16
+
17
+    public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
18
+    {
19
+        parent::__construct($router, $responder, $properties);
20
+        $this->reflection = $reflection;
21
+        $this->utils = new RequestUtils($reflection);
22
+    }
23
+
24
+    private function getMissingOrderParam(ReflectedTable $table): String
25
+    {
26
+        $pk = $table->getPk();
27
+        if (!$pk) {
28
+            $columnNames = $table->getColumnNames();
29
+            if (!$columnNames) {
30
+                return '';
31
+            }
32
+            return $columnNames[0];
33
+        }
34
+        return $pk->getName();
35
+    }
36
+
37
+    public function handle(Request $request): Response
38
+    {
39
+        $operation = $this->utils->getOperation($request);
40
+        if ($operation == 'list') {
41
+            $tableName = $request->getPathSegment(2);
42
+            $table = $this->reflection->getTable($tableName);
43
+            if ($table) {
44
+                $params = $request->getParams();
45
+                if (!isset($params['order']) || !$params['order']) {
46
+                    $params['order'] = array($this->getMissingOrderParam($table));
47
+                }
48
+                $maxPage = (int) $this->getProperty('pages', '100');
49
+                if (isset($params['page']) && $params['page']) {
50
+                    if (strpos($params['page'][0], ',') === false) {
51
+                        $params['page'] = array(min($params['page'][0], $maxPage));
52
+                    } else {
53
+                        list($page, $size) = explode(',', $params['page'][0], 2);
54
+                        $params['page'] = array(min($page, $maxPage) . ',' . $size);
55
+                    }
56
+                }
57
+                $maxSize = (int) $this->getProperty('records', '1000');
58
+                if (!isset($params['size']) || !$params['size']) {
59
+                    $params['size'] = array($maxSize);
60
+                } else {
61
+                    $params['size'] = array(min($params['size'][0], $maxSize));
62
+                }
63
+                $request->setParams($params);
64
+            }
65
+        }
66
+        return $this->next->handle($request);
67
+    }
68
+}

Loading…
Cancel
Save