diff --git a/app/Config/App.php b/app/Config/App.php index f598e324152a..a5774a0e25b5 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -238,85 +238,6 @@ class App extends BaseConfig */ public ?string $sessionDBGroup = null; - /** - * -------------------------------------------------------------------------- - * Cookie Prefix - * -------------------------------------------------------------------------- - * - * Set a cookie name prefix if you need to avoid collisions. - * - * @deprecated use Config\Cookie::$prefix property instead. - */ - public string $cookiePrefix = ''; - - /** - * -------------------------------------------------------------------------- - * Cookie Domain - * -------------------------------------------------------------------------- - * - * Set to `.your-domain.com` for site-wide cookies. - * - * @deprecated use Config\Cookie::$domain property instead. - */ - public string $cookieDomain = ''; - - /** - * -------------------------------------------------------------------------- - * Cookie Path - * -------------------------------------------------------------------------- - * - * Typically will be a forward slash. - * - * @deprecated use Config\Cookie::$path property instead. - */ - public string $cookiePath = '/'; - - /** - * -------------------------------------------------------------------------- - * Cookie Secure - * -------------------------------------------------------------------------- - * - * Cookie will only be set if a secure HTTPS connection exists. - * - * @deprecated use Config\Cookie::$secure property instead. - */ - public bool $cookieSecure = false; - - /** - * -------------------------------------------------------------------------- - * Cookie HttpOnly - * -------------------------------------------------------------------------- - * - * Cookie will only be accessible via HTTP(S) (no JavaScript). - * - * @deprecated use Config\Cookie::$httponly property instead. - */ - public bool $cookieHTTPOnly = true; - - /** - * -------------------------------------------------------------------------- - * Cookie SameSite - * -------------------------------------------------------------------------- - * - * Configure cookie SameSite setting. Allowed values are: - * - None - * - Lax - * - Strict - * - '' - * - * Alternatively, you can use the constant names: - * - `Cookie::SAMESITE_NONE` - * - `Cookie::SAMESITE_LAX` - * - `Cookie::SAMESITE_STRICT` - * - * Defaults to `Lax` for compatibility with modern browsers. Setting `''` - * (empty string) means default SameSite attribute set by browsers (`Lax`) - * will be set on cookies. If set to `None`, `$cookieSecure` must also be set. - * - * @deprecated use Config\Cookie::$samesite property instead. - */ - public ?string $cookieSameSite = 'Lax'; - /** * -------------------------------------------------------------------------- * Reverse Proxy IPs diff --git a/app/Config/Cookie.php b/app/Config/Cookie.php index 440af5ee8070..84ccc0e99d80 100644 --- a/app/Config/Cookie.php +++ b/app/Config/Cookie.php @@ -84,6 +84,8 @@ class Cookie extends BaseConfig * Defaults to `Lax` for compatibility with modern browsers. Setting `''` * (empty string) means default SameSite attribute set by browsers (`Lax`) * will be set on cookies. If set to `None`, `$secure` must also be set. + * + * @phpstan-var 'None'|'Lax'|'Strict'|'' */ public string $samesite = 'Lax'; diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index 21db9acd4191..33cf7542635c 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -13,9 +13,9 @@ use CodeIgniter\Cookie\Cookie; use CodeIgniter\Cookie\CookieStore; -use CodeIgniter\Cookie\Exceptions\CookieException; use CodeIgniter\HTTP\Exceptions\HTTPException; use Config\App; +use Config\Cookie as CookieConfig; use Config\Services; /** @@ -156,31 +156,12 @@ public function __construct($config) $this->CSPEnabled = $config->CSPEnabled; - // DEPRECATED COOKIE MANAGEMENT - - $this->cookiePrefix = $config->cookiePrefix; - $this->cookieDomain = $config->cookieDomain; - $this->cookiePath = $config->cookiePath; - $this->cookieSecure = $config->cookieSecure; - $this->cookieHTTPOnly = $config->cookieHTTPOnly; - $this->cookieSameSite = $config->cookieSameSite ?? Cookie::SAMESITE_LAX; + $this->cookieStore = new CookieStore([]); - $config->cookieSameSite ??= Cookie::SAMESITE_LAX; + /** @var CookieConfig $cookie */ + $cookie = config('Cookie'); - if (! in_array(strtolower($config->cookieSameSite ?: Cookie::SAMESITE_LAX), Cookie::ALLOWED_SAMESITE_VALUES, true)) { - throw CookieException::forInvalidSameSite($config->cookieSameSite); - } - - $this->cookieStore = new CookieStore([]); - Cookie::setDefaults(config('Cookie') ?? [ - // @todo Remove this fallback when deprecated `App` members are removed - 'prefix' => $config->cookiePrefix, - 'path' => $config->cookiePath, - 'domain' => $config->cookieDomain, - 'secure' => $config->cookieSecure, - 'httponly' => $config->cookieHTTPOnly, - 'samesite' => $config->cookieSameSite ?? Cookie::SAMESITE_LAX, - ]); + Cookie::setDefaults($cookie); // Default to an HTML Content-Type. Devs can override if needed. $this->setContentType('text/html'); diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php index 0a11ce84b76d..592863739eba 100755 --- a/system/Helpers/cookie_helper.php +++ b/system/Helpers/cookie_helper.php @@ -9,7 +9,6 @@ * the LICENSE file that was distributed with this source code. */ -use Config\App; use Config\Cookie; use Config\Services; @@ -68,11 +67,10 @@ function set_cookie( function get_cookie($index, bool $xssClean = false, ?string $prefix = '') { if ($prefix === '') { - /** @var Cookie|null $cookie */ + /** @var Cookie $cookie */ $cookie = config('Cookie'); - // @TODO Remove Config\App fallback when deprecated `App` members are removed. - $prefix = $cookie instanceof Cookie ? $cookie->prefix : config('App')->cookiePrefix; + $prefix = $cookie->prefix; } $request = Services::request(); diff --git a/system/Security/Security.php b/system/Security/Security.php index 422e589b81cd..d0701f8eaa56 100644 --- a/system/Security/Security.php +++ b/system/Security/Security.php @@ -195,7 +195,10 @@ public function __construct(App $config) } if ($this->isCSRFCookie()) { - $this->configureCookie($config); + /** @var CookieConfig $cookie */ + $cookie = config('Cookie'); + + $this->configureCookie($cookie); } else { // Session based CSRF protection $this->configureSession(); @@ -220,20 +223,11 @@ private function configureSession(): void $this->session = Services::session(); } - private function configureCookie(App $config): void + private function configureCookie(CookieConfig $cookie): void { - /** @var CookieConfig|null $cookie */ - $cookie = config('Cookie'); - - if ($cookie instanceof CookieConfig) { - $cookiePrefix = $cookie->prefix; - $this->cookieName = $cookiePrefix . $this->rawCookieName; - Cookie::setDefaults($cookie); - } else { - // `Config/Cookie.php` is absence - $cookiePrefix = $config->cookiePrefix; - $this->cookieName = $cookiePrefix . $this->rawCookieName; - } + $cookiePrefix = $cookie->prefix; + $this->cookieName = $cookiePrefix . $this->rawCookieName; + Cookie::setDefaults($cookie); } /** diff --git a/system/Session/Handlers/BaseHandler.php b/system/Session/Handlers/BaseHandler.php index f7d6ff1a90c7..a579eb7e2f03 100644 --- a/system/Session/Handlers/BaseHandler.php +++ b/system/Session/Handlers/BaseHandler.php @@ -122,22 +122,13 @@ public function __construct(AppConfig $config, string $ipAddress) $this->savePath = $config->sessionSavePath; } - /** @var CookieConfig|null $cookie */ + /** @var CookieConfig $cookie */ $cookie = config('Cookie'); - if ($cookie instanceof CookieConfig) { - // Session cookies have no prefix. - $this->cookieDomain = $cookie->domain; - $this->cookiePath = $cookie->path; - $this->cookieSecure = $cookie->secure; - } else { - // @TODO Remove this fallback when deprecated `App` members are removed. - // `Config/Cookie.php` is absence - // Session cookies have no prefix. - $this->cookieDomain = $config->cookieDomain; - $this->cookiePath = $config->cookiePath; - $this->cookieSecure = $config->cookieSecure; - } + // Session cookies have no prefix. + $this->cookieDomain = $cookie->domain; + $this->cookiePath = $cookie->path; + $this->cookieSecure = $cookie->secure; $this->ipAddress = $ipAddress; } diff --git a/system/Session/Session.php b/system/Session/Session.php index 497111b60030..3ea5bcd00b53 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -188,22 +188,16 @@ public function __construct(SessionHandlerInterface $driver, App $config) $this->sessionRegenerateDestroy = $config->sessionRegenerateDestroy ?? $this->sessionRegenerateDestroy; } - // DEPRECATED COOKIE MANAGEMENT - $this->cookiePath = $config->cookiePath ?? $this->cookiePath; - $this->cookieDomain = $config->cookieDomain ?? $this->cookieDomain; - $this->cookieSecure = $config->cookieSecure ?? $this->cookieSecure; - $this->cookieSameSite = $config->cookieSameSite ?? $this->cookieSameSite; - /** @var CookieConfig $cookie */ $cookie = config('Cookie'); $this->cookie = (new Cookie($this->sessionCookieName, '', [ 'expires' => $this->sessionExpiration === 0 ? 0 : Time::now()->getTimestamp() + $this->sessionExpiration, - 'path' => $cookie->path ?? $config->cookiePath, - 'domain' => $cookie->domain ?? $config->cookieDomain, - 'secure' => $cookie->secure ?? $config->cookieSecure, + 'path' => $cookie->path, + 'domain' => $cookie->domain, + 'secure' => $cookie->secure, 'httponly' => true, // for security - 'samesite' => $cookie->samesite ?? $config->cookieSameSite ?? Cookie::SAMESITE_LAX, + 'samesite' => $cookie->samesite ?? Cookie::SAMESITE_LAX, 'raw' => $cookie->raw ?? false, ]))->withPrefix(''); // Cookie prefix should be ignored. diff --git a/system/Test/Mock/MockAppConfig.php b/system/Test/Mock/MockAppConfig.php index e21f7a6b7adf..0d3a22cc3ff6 100644 --- a/system/Test/Mock/MockAppConfig.php +++ b/system/Test/Mock/MockAppConfig.php @@ -17,12 +17,6 @@ class MockAppConfig extends App { public string $baseURL = 'http://example.com/'; public string $uriProtocol = 'REQUEST_URI'; - public string $cookiePrefix = ''; - public string $cookieDomain = ''; - public string $cookiePath = '/'; - public bool $cookieSecure = false; - public bool $cookieHTTPOnly = false; - public ?string $cookieSameSite = 'Lax'; public array $proxyIPs = []; public string $CSRFTokenName = 'csrf_test_name'; public string $CSRFHeaderName = 'X-CSRF-TOKEN'; diff --git a/system/Test/Mock/MockCLIConfig.php b/system/Test/Mock/MockCLIConfig.php index 261284887d8d..a1756f41e248 100644 --- a/system/Test/Mock/MockCLIConfig.php +++ b/system/Test/Mock/MockCLIConfig.php @@ -17,12 +17,6 @@ class MockCLIConfig extends App { public string $baseURL = 'http://example.com/'; public string $uriProtocol = 'REQUEST_URI'; - public string $cookiePrefix = ''; - public string $cookieDomain = ''; - public string $cookiePath = '/'; - public bool $cookieSecure = false; - public bool $cookieHTTPOnly = false; - public ?string $cookieSameSite = 'Lax'; public array $proxyIPs = []; public string $CSRFTokenName = 'csrf_test_name'; public string $CSRFCookieName = 'csrf_cookie_name'; diff --git a/tests/system/API/ResponseTraitTest.php b/tests/system/API/ResponseTraitTest.php index 0c060691b0b3..e6e18d508776 100644 --- a/tests/system/API/ResponseTraitTest.php +++ b/tests/system/API/ResponseTraitTest.php @@ -11,6 +11,7 @@ namespace CodeIgniter\API; +use CodeIgniter\Config\Factories; use CodeIgniter\Format\FormatterInterface; use CodeIgniter\Format\JSONFormatter; use CodeIgniter\Format\XMLFormatter; @@ -20,6 +21,7 @@ use CodeIgniter\Test\Mock\MockIncomingRequest; use CodeIgniter\Test\Mock\MockResponse; use Config\App; +use Config\Cookie; use stdClass; /** @@ -59,17 +61,25 @@ protected function makeController(array $userConfig = [], string $uri = 'http:// 'negotiateLocale' => false, 'supportedLocales' => ['en'], 'CSPEnabled' => false, - 'cookiePrefix' => '', - 'cookieDomain' => '', - 'cookiePath' => '/', - 'cookieSecure' => false, - 'cookieHTTPOnly' => false, 'proxyIPs' => [], - 'cookieSameSite' => 'Lax', ] as $key => $value) { $config->{$key} = $value; } + $cookie = new Cookie(); + + foreach ([ + 'prefix' => '', + 'domain' => '', + 'path' => '/', + 'secure' => false, + 'httponly' => false, + 'samesite' => 'Lax', + ] as $key => $value) { + $cookie->{$key} = $value; + } + Factories::injectMock('config', 'Cookie', $cookie); + if ($this->request === null) { $this->request = new MockIncomingRequest($config, new URI($uri), null, new UserAgent()); $this->response = new MockResponse($config); @@ -532,17 +542,25 @@ public function testFormatByRequestNegotiateIfFormatIsNotJsonOrXML() 'negotiateLocale' => false, 'supportedLocales' => ['en'], 'CSPEnabled' => false, - 'cookiePrefix' => '', - 'cookieDomain' => '', - 'cookiePath' => '/', - 'cookieSecure' => false, - 'cookieHTTPOnly' => false, 'proxyIPs' => [], - 'cookieSameSite' => 'Lax', ] as $key => $value) { $config->{$key} = $value; } + $cookie = new Cookie(); + + foreach ([ + 'prefix' => '', + 'domain' => '', + 'path' => '/', + 'secure' => false, + 'httponly' => false, + 'samesite' => 'Lax', + ] as $key => $value) { + $cookie->{$key} = $value; + } + Factories::injectMock('config', 'Cookie', $cookie); + $request = new MockIncomingRequest($config, new URI($config->baseURL), null, new UserAgent()); $response = new MockResponse($config); diff --git a/tests/system/CommonFunctionsTest.php b/tests/system/CommonFunctionsTest.php index a0b5280ec6bc..cf0555fba3d8 100644 --- a/tests/system/CommonFunctionsTest.php +++ b/tests/system/CommonFunctionsTest.php @@ -12,6 +12,7 @@ namespace CodeIgniter; use CodeIgniter\Config\BaseService; +use CodeIgniter\Config\Factories; use CodeIgniter\HTTP\CLIRequest; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\RedirectResponse; @@ -28,6 +29,7 @@ use CodeIgniter\Test\Mock\MockSession; use CodeIgniter\Test\TestLogger; use Config\App; +use Config\Cookie; use Config\Logger; use Config\Modules; use Config\Services; @@ -488,12 +490,9 @@ public function testReallyWritable() public function testSlashItem() { - $this->assertSame('/', slash_item('cookiePath')); // / - $this->assertSame('', slash_item('cookieDomain')); // '' $this->assertSame('en/', slash_item('defaultLocale')); // en $this->assertSame('7200/', slash_item('sessionExpiration')); // int 7200 $this->assertSame('', slash_item('negotiateLocale')); // false - $this->assertSame('1/', slash_item('cookieHTTPOnly')); // true } public function testSlashItemOnInexistentItem() @@ -514,6 +513,8 @@ public function testSlashItemThrowsErrorOnNonStringableItem() protected function injectSessionMock() { + $appConfig = new App(); + $defaults = [ 'sessionDriver' => FileHandler::class, 'sessionCookieName' => 'ci_session', @@ -522,19 +523,25 @@ protected function injectSessionMock() 'sessionMatchIP' => false, 'sessionTimeToUpdate' => 300, 'sessionRegenerateDestroy' => false, - 'cookieDomain' => '', - 'cookiePrefix' => '', - 'cookiePath' => '/', - 'cookieSecure' => false, - 'cookieSameSite' => 'Lax', ]; - $appConfig = new App(); - foreach ($defaults as $key => $config) { $appConfig->{$key} = $config; } + $cookie = new Cookie(); + + foreach ([ + 'prefix' => '', + 'domain' => '', + 'path' => '/', + 'secure' => false, + 'samesite' => 'Lax', + ] as $key => $value) { + $cookie->{$key} = $value; + } + Factories::injectMock('config', 'Cookie', $cookie); + $session = new MockSession(new FileHandler($appConfig, '127.0.0.1'), $appConfig); $session->setLogger(new TestLogger(new Logger())); BaseService::injectMock('session', $session); diff --git a/tests/system/HTTP/ResponseTest.php b/tests/system/HTTP/ResponseTest.php index 6947d8eea33d..6ad18e7f0e11 100644 --- a/tests/system/HTTP/ResponseTest.php +++ b/tests/system/HTTP/ResponseTest.php @@ -12,7 +12,6 @@ namespace CodeIgniter\HTTP; use CodeIgniter\Config\Factories; -use CodeIgniter\Cookie\Exceptions\CookieException; use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\Mock\MockResponse; @@ -512,14 +511,4 @@ public function testPretendOutput() $this->assertSame('Happy days', $actual); } - - public function testInvalidSameSiteCookie() - { - $config = new App(); - $config->cookieSameSite = 'Invalid'; - - $this->expectException(CookieException::class); - $this->expectExceptionMessage(lang('Cookie.invalidSameSite', ['Invalid'])); - new Response($config); - } } diff --git a/tests/system/Security/SecurityCSRFSessionRandomizeTokenTest.php b/tests/system/Security/SecurityCSRFSessionRandomizeTokenTest.php index 6086472d3f4c..f8f3fc01fbaa 100644 --- a/tests/system/Security/SecurityCSRFSessionRandomizeTokenTest.php +++ b/tests/system/Security/SecurityCSRFSessionRandomizeTokenTest.php @@ -26,6 +26,7 @@ use CodeIgniter\Test\Mock\MockSession; use CodeIgniter\Test\TestLogger; use Config\App as AppConfig; +use Config\Cookie; use Config\Logger as LoggerConfig; use Config\Security as SecurityConfig; @@ -75,20 +76,28 @@ private function createSession($options = []): Session 'sessionMatchIP' => false, 'sessionTimeToUpdate' => 300, 'sessionRegenerateDestroy' => false, - 'cookieDomain' => '', - 'cookiePrefix' => '', - 'cookiePath' => '/', - 'cookieSecure' => false, - 'cookieSameSite' => 'Lax', ]; + $config = array_merge($defaults, $options); - $config = array_merge($defaults, $options); $appConfig = new AppConfig(); foreach ($config as $key => $c) { $appConfig->{$key} = $c; } + $cookie = new Cookie(); + + foreach ([ + 'prefix' => '', + 'domain' => '', + 'path' => '/', + 'secure' => false, + 'samesite' => 'Lax', + ] as $key => $value) { + $cookie->{$key} = $value; + } + Factories::injectMock('config', 'Cookie', $cookie); + $session = new MockSession(new ArrayHandler($appConfig, '127.0.0.1'), $appConfig); $session->setLogger(new TestLogger(new LoggerConfig())); diff --git a/tests/system/Security/SecurityCSRFSessionTest.php b/tests/system/Security/SecurityCSRFSessionTest.php index 7fbd7096853f..495f9e58b06d 100644 --- a/tests/system/Security/SecurityCSRFSessionTest.php +++ b/tests/system/Security/SecurityCSRFSessionTest.php @@ -25,6 +25,7 @@ use CodeIgniter\Test\Mock\MockSession; use CodeIgniter\Test\TestLogger; use Config\App as AppConfig; +use Config\Cookie; use Config\Logger as LoggerConfig; use Config\Security as SecurityConfig; @@ -68,20 +69,28 @@ private function createSession($options = []): Session 'sessionMatchIP' => false, 'sessionTimeToUpdate' => 300, 'sessionRegenerateDestroy' => false, - 'cookieDomain' => '', - 'cookiePrefix' => '', - 'cookiePath' => '/', - 'cookieSecure' => false, - 'cookieSameSite' => 'Lax', ]; + $config = array_merge($defaults, $options); - $config = array_merge($defaults, $options); $appConfig = new AppConfig(); foreach ($config as $key => $c) { $appConfig->{$key} = $c; } + $cookie = new Cookie(); + + foreach ([ + 'prefix' => '', + 'domain' => '', + 'path' => '/', + 'secure' => false, + 'samesite' => 'Lax', + ] as $key => $value) { + $cookie->{$key} = $value; + } + Factories::injectMock('config', 'Cookie', $cookie); + $session = new MockSession(new ArrayHandler($appConfig, '127.0.0.1'), $appConfig); $session->setLogger(new TestLogger(new LoggerConfig())); diff --git a/user_guide_src/source/changelogs/v4.4.0.rst b/user_guide_src/source/changelogs/v4.4.0.rst index a685e948e630..c80d12602c98 100644 --- a/user_guide_src/source/changelogs/v4.4.0.rst +++ b/user_guide_src/source/changelogs/v4.4.0.rst @@ -74,6 +74,7 @@ Message Changes Changes ******* +- **Config:** The deprecated Cookie items in **app/Config/App.php** has been removed. - **DownloadResponse:** When generating response headers, does not replace the ``Content-Disposition`` header if it was previously specified. Deprecations diff --git a/user_guide_src/source/installation/upgrade_440.rst b/user_guide_src/source/installation/upgrade_440.rst new file mode 100644 index 000000000000..16995c1d897e --- /dev/null +++ b/user_guide_src/source/installation/upgrade_440.rst @@ -0,0 +1,63 @@ +############################## +Upgrading from 4.3.x to 4.4.0 +############################## + +Please refer to the upgrade instructions corresponding to your installation method. + +- :ref:`Composer Installation App Starter Upgrading ` +- :ref:`Composer Installation Adding CodeIgniter4 to an Existing Project Upgrading ` +- :ref:`Manual Installation Upgrading ` + +.. contents:: + :local: + :depth: 2 + +Breaking Changes +**************** + +Mandatory File Changes +********************** + +Config Files +============ + +app/Config/Cookie.php +--------------------- + +The Cookie config items in **app/Config/App.php** are no longer used. + +1. Copy **app/Config/Cookie.php** from the new framework to your **app/Config** + directory, and configure it. +2. Remove the properties (from ``$cookiePrefix`` to ``$cookieSameSite``) in + **app/Config/App.php**. + +Breaking Enhancements +********************* + +Project Files +************* + +Some files in the **project space** (root, app, public, writable) received updates. Due to +these files being outside of the **system** scope they will not be changed without your intervention. + +There are some third-party CodeIgniter modules available to assist with merging changes to +the project space: `Explore on Packagist `_. + +Content Changes +=============== + +The following files received significant changes (including deprecations or visual adjustments) +and it is recommended that you merge the updated versions with your application: + +Config +------ + +- @TODO + +All Changes +=========== + +This is a list of all files in the **project space** that received changes; +many will be simple comments or formatting that have no effect on the runtime: + +- @TODO diff --git a/user_guide_src/source/installation/upgrading.rst b/user_guide_src/source/installation/upgrading.rst index d754d020b55c..6ac83fd4c239 100644 --- a/user_guide_src/source/installation/upgrading.rst +++ b/user_guide_src/source/installation/upgrading.rst @@ -16,6 +16,7 @@ See also :doc:`./backward_compatibility_notes`. backward_compatibility_notes + upgrade_440 upgrade_432 upgrade_431 upgrade_430