api de gestion de ticket, basé sur php-crud-api. Le but est de décorrélé les outils de gestion des données, afin
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ValidationMiddleware.php 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. <?php
  2. namespace Tqdev\PhpCrudApi\Middleware;
  3. use Psr\Http\Message\ResponseInterface;
  4. use Psr\Http\Message\ServerRequestInterface;
  5. use Psr\Http\Server\RequestHandlerInterface;
  6. use Tqdev\PhpCrudApi\Column\ReflectionService;
  7. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedTable;
  8. use Tqdev\PhpCrudApi\Column\Reflection\ReflectedColumn;
  9. use Tqdev\PhpCrudApi\Controller\Responder;
  10. use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
  11. use Tqdev\PhpCrudApi\Middleware\Router\Router;
  12. use Tqdev\PhpCrudApi\Record\ErrorCode;
  13. use Tqdev\PhpCrudApi\RequestUtils;
  14. class ValidationMiddleware extends Middleware
  15. {
  16. private $reflection;
  17. public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
  18. {
  19. parent::__construct($router, $responder, $properties);
  20. $this->reflection = $reflection;
  21. }
  22. private function callHandler($handler, $record, string $operation, ReflectedTable $table) /*: ResponseInterface?*/
  23. {
  24. $context = (array) $record;
  25. $details = array();
  26. $tableName = $table->getName();
  27. foreach ($context as $columnName => $value) {
  28. if ($table->hasColumn($columnName)) {
  29. $column = $table->getColumn($columnName);
  30. $valid = call_user_func($handler, $operation, $tableName, $column->serialize(), $value, $context);
  31. if ($valid === true || $valid === '') {
  32. $valid = $this->validateType($column, $value);
  33. }
  34. if ($valid !== true && $valid !== '') {
  35. $details[$columnName] = $valid;
  36. }
  37. }
  38. }
  39. if (count($details) > 0) {
  40. return $this->responder->error(ErrorCode::INPUT_VALIDATION_FAILED, $tableName, $details);
  41. }
  42. return null;
  43. }
  44. private function validateType(ReflectedColumn $column, $value)
  45. {
  46. $types = $this->getArrayProperty('types', 'all');
  47. if (in_array('all', $types) || in_array($column->getType(), $types)) {
  48. if (is_null($value)) {
  49. return ($column->getNullable() ? true : "cannot be null");
  50. }
  51. if (is_string($value)) {
  52. // check for whitespace
  53. switch ($column->getType()) {
  54. case 'varchar':
  55. case 'clob':
  56. break;
  57. default:
  58. if (strlen(trim($value)) != strlen($value)) {
  59. return 'illegal whitespace';
  60. }
  61. break;
  62. }
  63. // try to parse
  64. switch ($column->getType()) {
  65. case 'integer':
  66. case 'bigint':
  67. if (
  68. filter_var($value, FILTER_SANITIZE_NUMBER_INT) !== $value ||
  69. filter_var($value, FILTER_VALIDATE_INT) === false
  70. ) {
  71. return 'invalid integer';
  72. }
  73. break;
  74. case 'varchar':
  75. if (mb_strlen($value, 'UTF-8') > $column->getLength()) {
  76. return 'string too long';
  77. }
  78. break;
  79. case 'decimal':
  80. if (!is_numeric($value)) {
  81. return 'invalid decimal';
  82. }
  83. break;
  84. case 'float':
  85. case 'double':
  86. if (
  87. filter_var($value, FILTER_SANITIZE_NUMBER_FLOAT) !== $value ||
  88. filter_var($value, FILTER_VALIDATE_FLOAT) === false
  89. ) {
  90. return 'invalid float';
  91. }
  92. break;
  93. case 'boolean':
  94. if (
  95. filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === null
  96. ) {
  97. return 'invalid boolean';
  98. }
  99. break;
  100. case 'date':
  101. if (date_create_from_format('Y-m-d', $value) === false) {
  102. return 'invalid date';
  103. }
  104. break;
  105. case 'time':
  106. if (date_create_from_format('H:i:s', $value) === false) {
  107. return 'invalid time';
  108. }
  109. break;
  110. case 'timestamp':
  111. if (date_create_from_format('Y-m-d H:i:s', $value) === false) {
  112. return 'invalid timestamp';
  113. }
  114. break;
  115. case 'clob':
  116. // no checks needed
  117. break;
  118. case 'blob':
  119. case 'varbinary':
  120. if (base64_decode($value, true) === false) {
  121. return 'invalid base64';
  122. }
  123. break;
  124. case 'geometry':
  125. // no checks yet
  126. break;
  127. }
  128. } else { // check non-string types
  129. switch ($column->getType()) {
  130. case 'integer':
  131. case 'bigint':
  132. if (!is_int($value)) {
  133. return 'invalid integer';
  134. }
  135. break;
  136. case 'decimal':
  137. case 'float':
  138. case 'double':
  139. if (!is_float($value) && !is_int($value)) {
  140. return 'invalid float';
  141. }
  142. break;
  143. case 'boolean':
  144. if (!(is_int($value) && ($value === 1 || $value === 0)) && !is_bool($value)) {
  145. return 'invalid boolean';
  146. }
  147. break;
  148. default:
  149. return 'invalid ' . $column->getType();
  150. }
  151. }
  152. // extra checks
  153. switch ($column->getType()) {
  154. case 'integer': // 4 byte signed
  155. $value = filter_var($value, FILTER_VALIDATE_INT);
  156. if ($value > 2147483647 || $value < -2147483648) {
  157. return 'invalid integer';
  158. }
  159. break;
  160. case 'decimal':
  161. $value = "$value";
  162. if (strpos($value, '.') !== false) {
  163. list($whole, $decimals) = explode('.', $value, 2);
  164. } else {
  165. list($whole, $decimals) = array($value, '');
  166. }
  167. if (strlen($whole) > 0 && !ctype_digit($whole)) {
  168. return 'invalid decimal';
  169. }
  170. if (strlen($decimals) > 0 && !ctype_digit($decimals)) {
  171. return 'invalid decimal';
  172. }
  173. if (strlen($whole) > $column->getPrecision() - $column->getScale()) {
  174. return 'decimal too large';
  175. }
  176. if (strlen($decimals) > $column->getScale()) {
  177. return 'decimal too precise';
  178. }
  179. break;
  180. case 'varbinary':
  181. if (strlen(base64_decode($value)) > $column->getLength()) {
  182. return 'string too long';
  183. }
  184. break;
  185. }
  186. }
  187. return (true);
  188. }
  189. public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
  190. {
  191. $operation = RequestUtils::getOperation($request);
  192. if (in_array($operation, ['create', 'update', 'increment'])) {
  193. $tableName = RequestUtils::getPathSegment($request, 2);
  194. if ($this->reflection->hasTable($tableName)) {
  195. $record = $request->getParsedBody();
  196. if ($record !== null) {
  197. $handler = $this->getProperty('handler', '');
  198. if ($handler !== '') {
  199. $table = $this->reflection->getTable($tableName);
  200. if (is_array($record)) {
  201. foreach ($record as $r) {
  202. $response = $this->callHandler($handler, $r, $operation, $table);
  203. if ($response !== null) {
  204. return $response;
  205. }
  206. }
  207. } else {
  208. $response = $this->callHandler($handler, $record, $operation, $table);
  209. if ($response !== null) {
  210. return $response;
  211. }
  212. }
  213. }
  214. }
  215. }
  216. }
  217. return $next->handle($request);
  218. }
  219. }