diff --git a/phpstan-baseline.php b/phpstan-baseline.php index e500681a699b..0a7945af6d65 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -1791,41 +1791,6 @@ 'count' => 1, 'path' => __DIR__ . '/system/Database/BaseBuilder.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:_deleteBatch\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/BaseBuilder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:_insert\\(\\) has parameter \\$keys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/BaseBuilder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:_insert\\(\\) has parameter \\$unescapedKeys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/BaseBuilder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:_like\\(\\) has parameter \\$field with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/BaseBuilder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:_replace\\(\\) has parameter \\$keys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/BaseBuilder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:_replace\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/BaseBuilder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:_update\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/BaseBuilder.php', -]; $ignoreErrors[] = [ 'message' => '#^Method CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:_whereIn\\(\\) has parameter \\$values with no signature specified for Closure\\.$#', 'count' => 1, @@ -2206,6 +2171,11 @@ 'count' => 1, 'path' => __DIR__ . '/system/Database/BaseBuilder.php', ]; +$ignoreErrors[] = [ + 'message' => '#^Parameter \\#3 \\.\\.\\.\\$arrays of function array_map expects array, int\\|string given\\.$#', + 'count' => 1, + 'path' => __DIR__ . '/system/Database/BaseBuilder.php', +]; $ignoreErrors[] = [ 'message' => '#^Property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$QBFrom type has no value type specified in iterable type array\\.$#', 'count' => 1, @@ -3122,37 +3092,22 @@ 'path' => __DIR__ . '/system/Database/OCI8/Builder.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:_deleteBatch\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/OCI8/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:_replace\\(\\) has parameter \\$keys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/OCI8/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:_replace\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/OCI8/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:_update\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', + 'message' => '#^Method CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:fieldsFromQuery\\(\\) return type has no value type specified in iterable type array\\.$#', 'count' => 1, 'path' => __DIR__ . '/system/Database/OCI8/Builder.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:fieldsFromQuery\\(\\) return type has no value type specified in iterable type array\\.$#', + 'message' => '#^Method CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:resetSelect\\(\\) has no return type specified\\.$#', 'count' => 1, 'path' => __DIR__ . '/system/Database/OCI8/Builder.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:resetSelect\\(\\) has no return type specified\\.$#', + 'message' => '#^PHPDoc type CodeIgniter\\\\Database\\\\OCI8\\\\Connection of property CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:\\$db is not the same as PHPDoc type CodeIgniter\\\\Database\\\\BaseConnection of overridden property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$db\\.$#', 'count' => 1, 'path' => __DIR__ . '/system/Database/OCI8/Builder.php', ]; $ignoreErrors[] = [ - 'message' => '#^PHPDoc type CodeIgniter\\\\Database\\\\OCI8\\\\Connection of property CodeIgniter\\\\Database\\\\OCI8\\\\Builder\\:\\:\\$db is not the same as PHPDoc type CodeIgniter\\\\Database\\\\BaseConnection of overridden property CodeIgniter\\\\Database\\\\BaseBuilder\\:\\:\\$db\\.$#', + 'message' => '#^Parameter \\#3 \\.\\.\\.\\$arrays of function array_map expects array, int\\|string given\\.$#', 'count' => 1, 'path' => __DIR__ . '/system/Database/OCI8/Builder.php', ]; @@ -3317,27 +3272,12 @@ 'path' => __DIR__ . '/system/Database/Postgre/Builder.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\Postgre\\\\Builder\\:\\:_deleteBatch\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/Postgre/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\Postgre\\\\Builder\\:\\:_insert\\(\\) has parameter \\$keys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/Postgre/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\Postgre\\\\Builder\\:\\:_insert\\(\\) has parameter \\$unescapedKeys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/Postgre/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\Postgre\\\\Builder\\:\\:_update\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', + 'message' => '#^Method CodeIgniter\\\\Database\\\\Postgre\\\\Builder\\:\\:replace\\(\\) has parameter \\$set with no value type specified in iterable type array\\.$#', 'count' => 1, 'path' => __DIR__ . '/system/Database/Postgre/Builder.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\Postgre\\\\Builder\\:\\:replace\\(\\) has parameter \\$set with no value type specified in iterable type array\\.$#', + 'message' => '#^Parameter \\#3 \\.\\.\\.\\$arrays of function array_map expects array, int\\|string given\\.$#', 'count' => 1, 'path' => __DIR__ . '/system/Database/Postgre/Builder.php', ]; @@ -3676,31 +3616,6 @@ 'count' => 9, 'path' => __DIR__ . '/system/Database/SQLSRV/Builder.php', ]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\SQLSRV\\\\Builder\\:\\:_insert\\(\\) has parameter \\$keys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/SQLSRV/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\SQLSRV\\\\Builder\\:\\:_insert\\(\\) has parameter \\$unescapedKeys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/SQLSRV/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\SQLSRV\\\\Builder\\:\\:_replace\\(\\) has parameter \\$keys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/SQLSRV/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\SQLSRV\\\\Builder\\:\\:_replace\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/SQLSRV/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\SQLSRV\\\\Builder\\:\\:_update\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/SQLSRV/Builder.php', -]; $ignoreErrors[] = [ 'message' => '#^Method CodeIgniter\\\\Database\\\\SQLSRV\\\\Builder\\:\\:fieldsFromQuery\\(\\) return type has no value type specified in iterable type array\\.$#', 'count' => 1, @@ -3907,17 +3822,7 @@ 'path' => __DIR__ . '/system/Database/SQLite3/Builder.php', ]; $ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\SQLite3\\\\Builder\\:\\:_deleteBatch\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/SQLite3/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\SQLite3\\\\Builder\\:\\:_replace\\(\\) has parameter \\$keys with no value type specified in iterable type array\\.$#', - 'count' => 1, - 'path' => __DIR__ . '/system/Database/SQLite3/Builder.php', -]; -$ignoreErrors[] = [ - 'message' => '#^Method CodeIgniter\\\\Database\\\\SQLite3\\\\Builder\\:\\:_replace\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', + 'message' => '#^Parameter \\#3 \\.\\.\\.\\$arrays of function array_map expects array, int\\|string given\\.$#', 'count' => 1, 'path' => __DIR__ . '/system/Database/SQLite3/Builder.php', ]; diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 1f0807cdbbde..c20f59920ff2 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -1090,7 +1090,7 @@ public function orNotHavingLike($field, string $match = '', string $side = 'both * @used-by notHavingLike() * @used-by orNotHavingLike() * - * @param array|RawSql|string $field + * @param array|RawSql|string $field * * @return $this */ @@ -2376,7 +2376,9 @@ protected function validateInsert(): bool /** * Generates a platform-specific insert string from the supplied data * - * @param string $table Protected table name + * @param string $table Protected table name + * @param list $keys Keys of QBSet + * @param list $unescapedKeys Values of QBSet */ protected function _insert(string $table, array $keys, array $unescapedKeys): string { @@ -2416,7 +2418,9 @@ public function replace(?array $set = null) /** * Generates a platform-specific replace string from the supplied data * - * @param string $table Protected table name + * @param string $table Protected table name + * @param list $keys Keys of QBSet + * @param list $values Values of QBSet */ protected function _replace(string $table, array $keys, array $values): string { @@ -2512,7 +2516,8 @@ public function update($set = null, $where = null, ?int $limit = null): bool /** * Generates a platform-specific update string from the supplied data * - * @param string $table Protected table name + * @param string $table Protected table name + * @param array $values QBSet */ protected function _update(string $table, array $values): string { @@ -2863,9 +2868,9 @@ public function deleteBatch($set = null, $constraints = null, int $batchSize = 1 * * @used-by batchExecute() * - * @param string $table Protected table name - * @param list $keys QBKeys - * @paramst> $values QBSet + * @param string $table Protected table name + * @param list $keys QBKeys + * @param list $values QBSet */ protected function _deleteBatch(string $table, array $keys, array $values): string { diff --git a/system/HTTP/ResponseTrait.php b/system/HTTP/ResponseTrait.php index 2d4295116807..45f07d186170 100644 --- a/system/HTTP/ResponseTrait.php +++ b/system/HTTP/ResponseTrait.php @@ -670,7 +670,7 @@ private function dispatchCookies(): void foreach ($this->cookieStore->display() as $cookie) { if ($cookie->isSecure() && ! $request->isSecure()) { - throw SecurityException::forDisallowedAction(); + throw SecurityException::forInsecureCookie(); } $name = $cookie->getPrefixedName(); diff --git a/system/Language/en/Security.php b/system/Language/en/Security.php index 145abaab71e7..fd906e378a29 100644 --- a/system/Language/en/Security.php +++ b/system/Language/en/Security.php @@ -14,6 +14,7 @@ // Security language settings return [ 'disallowedAction' => 'The action you requested is not allowed.', + 'insecureCookie' => 'Attempted to send a secure cookie over a non-secure connection.', // @deprecated 'invalidSameSite' => 'The SameSite value must be None, Lax, Strict, or a blank string. Given: "{0}"', diff --git a/system/Security/Exceptions/SecurityException.php b/system/Security/Exceptions/SecurityException.php index 16383fc25bfc..ed1d76825036 100644 --- a/system/Security/Exceptions/SecurityException.php +++ b/system/Security/Exceptions/SecurityException.php @@ -20,6 +20,7 @@ class SecurityException extends FrameworkException implements HTTPExceptionInter { /** * Throws when some specific action is not allowed. + * This is used for CSRF protection. * * @return static */ @@ -28,6 +29,15 @@ public static function forDisallowedAction() return new static(lang('Security.disallowedAction'), 403); } + /** + * Throws if a secure cookie is dispatched when the current connection is not + * secure. + */ + public static function forInsecureCookie(): static + { + return new static(lang('Security.insecureCookie')); + } + /** * Throws when the source string contains invalid UTF-8 characters. * diff --git a/tests/_support/Entity/UserWithCasts.php b/tests/_support/Entity/UserWithCasts.php new file mode 100644 index 000000000000..cfe1981fa3fa --- /dev/null +++ b/tests/_support/Entity/UserWithCasts.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Entity; + +use CodeIgniter\Entity\Entity; + +class UserWithCasts extends Entity +{ + protected $casts = [ + 'email' => 'json', + ]; +} diff --git a/tests/_support/Models/UserEntityWithCastsModel.php b/tests/_support/Models/UserEntityWithCastsModel.php new file mode 100644 index 000000000000..2ea32c88f842 --- /dev/null +++ b/tests/_support/Models/UserEntityWithCastsModel.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Models; + +use CodeIgniter\Model; +use Tests\Support\Entity\UserWithCasts; + +class UserEntityWithCastsModel extends Model +{ + protected $table = 'user'; + protected $allowedFields = [ + 'name', + 'email', + 'country', + 'deleted_at', + ]; + protected $returnType = UserWithCasts::class; +} diff --git a/tests/system/HTTP/ResponseSendTest.php b/tests/system/HTTP/ResponseSendTest.php index a4cc67765d3d..6159ee1140ba 100644 --- a/tests/system/HTTP/ResponseSendTest.php +++ b/tests/system/HTTP/ResponseSendTest.php @@ -162,14 +162,11 @@ public function testRedirectResponseCookies(): void /** * Make sure secure cookies are not sent with HTTP request - * - * @ runInSeparateProcess - * @ preserveGlobalState disabled */ public function testDoNotSendUnSecureCookie(): void { $this->expectException(SecurityException::class); - $this->expectExceptionMessage('The action you requested is not allowed'); + $this->expectExceptionMessage('Attempted to send a secure cookie over a non-secure connection.'); $request = $this->createMock(IncomingRequest::class); $request->method('isSecure')->willReturn(false); diff --git a/tests/system/Models/FindModelTest.php b/tests/system/Models/FindModelTest.php index d27fcb791bb3..b0b835bc1067 100644 --- a/tests/system/Models/FindModelTest.php +++ b/tests/system/Models/FindModelTest.php @@ -15,8 +15,10 @@ use CodeIgniter\Database\Exceptions\DataException; use CodeIgniter\Exceptions\ModelException; +use Tests\Support\Entity\UserWithCasts; use Tests\Support\Models\JobModel; use Tests\Support\Models\SecondaryModel; +use Tests\Support\Models\UserEntityWithCastsModel; use Tests\Support\Models\UserModel; /** @@ -32,6 +34,24 @@ public function testFindReturnsRow(): void $this->assertSame('Musician', $this->model->find(4)->name); } + public function testFindReturnsEntityWithCasts(): void + { + $this->createModel(UserEntityWithCastsModel::class); + $this->model->builder()->truncate(); + $user = new UserWithCasts([ + 'name' => 'John Smith', + 'email' => ['foo@example.jp', 'bar@example.com'], + 'country' => 'US', + ]); + $id = $this->model->insert($user, true); + + /** @var UserWithCasts $user */ + $user = $this->model->find($id); + + $this->assertSame('John Smith', $user->name); + $this->assertSame(['foo@example.jp', 'bar@example.com'], $user->email); + } + public function testFindReturnsMultipleRows(): void { $this->createModel(JobModel::class); diff --git a/user_guide_src/source/changelogs/v4.5.2.rst b/user_guide_src/source/changelogs/v4.5.2.rst index d578360b720a..8824d3c72b96 100644 --- a/user_guide_src/source/changelogs/v4.5.2.rst +++ b/user_guide_src/source/changelogs/v4.5.2.rst @@ -18,6 +18,8 @@ BREAKING Message Changes *************** +- Added ``Security.insecureCookie`` message. + ******* Changes ******* diff --git a/user_guide_src/source/database/examples.rst b/user_guide_src/source/database/examples.rst index a88608e85651..43a2bdceac62 100644 --- a/user_guide_src/source/database/examples.rst +++ b/user_guide_src/source/database/examples.rst @@ -1,6 +1,6 @@ -################################## -Database Quick Start: Example Code -################################## +########################### +Quick Start: Usage Examples +########################### The following page contains example code showing how the database class is used. For complete details please read the individual pages @@ -24,7 +24,7 @@ your :doc:`configuration ` settings: Once loaded the class is ready to be used as described below. .. note:: If all your pages require database access you can connect - automatically. See the :doc:`connecting ` page for details. + automatically. See the :doc:`Connecting to a Database ` page for details. Standard Query With Multiple Results (Object Version) ===================================================== @@ -66,7 +66,7 @@ Standard Insert Query Builder Query =================== -The :doc:`Query Builder Pattern ` gives you a simplified +The :doc:`Query Builder ` gives you a simplified means of retrieving data: .. literalinclude:: examples/007.php