From 91c8c736b0039b24b3015b5cde8ddd5af33b9d4b Mon Sep 17 00:00:00 2001 From: mostafakhudair Date: Tue, 30 Mar 2021 11:04:06 +0200 Subject: [PATCH] Create Config::Cookie --- app/Config/App.php | 44 ++--- app/Config/Cookie.php | 119 +++++++++++ env | 13 ++ system/Cookie/CloneableCookieInterface.php | 38 ++-- system/Cookie/Cookie.php | 220 ++++++++++----------- system/Cookie/CookieInterface.php | 26 +-- system/Cookie/CookieStore.php | 15 +- system/HTTP/Response.php | 5 +- system/Security/Security.php | 12 +- system/Session/Session.php | 18 +- tests/system/Cookie/CookieTest.php | 39 ++-- tests/system/HTTP/ResponseCookieTest.php | 110 +++++------ tests/system/Security/SecurityTest.php | 11 +- tests/system/Session/SessionTest.php | 34 +++- 14 files changed, 413 insertions(+), 291 deletions(-) create mode 100644 app/Config/Cookie.php diff --git a/app/Config/App.php b/app/Config/App.php index c9d170c94cad..1f1cd3fc74d9 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -3,7 +3,6 @@ namespace Config; use CodeIgniter\Config\BaseConfig; -use DateTimeInterface; class App extends BaseConfig { @@ -242,6 +241,8 @@ class App extends BaseConfig * Set a cookie name prefix if you need to avoid collisions. * * @var string + * + * @deprecated use Config\Cookie::$prefix property instead. */ public $cookiePrefix = ''; @@ -253,6 +254,8 @@ class App extends BaseConfig * Set to `.your-domain.com` for site-wide cookies. * * @var string + * + * @deprecated use Config\Cookie::$domain property instead. */ public $cookieDomain = ''; @@ -264,6 +267,8 @@ class App extends BaseConfig * Typically will be a forward slash. * * @var string + * + * @deprecated use Config\Cookie::$path property instead. */ public $cookiePath = '/'; @@ -275,6 +280,8 @@ class App extends BaseConfig * Cookie will only be set if a secure HTTPS connection exists. * * @var boolean + * + * @deprecated use Config\Cookie::$secure property instead. */ public $cookieSecure = false; @@ -286,6 +293,8 @@ class App extends BaseConfig * Cookie will only be accessible via HTTP(S) (no JavaScript). * * @var boolean + * + * @deprecated use Config\Cookie::$httponly property instead. */ public $cookieHTTPOnly = true; @@ -310,40 +319,11 @@ class App extends BaseConfig * will be set on cookies. If set to `None`, `$cookieSecure` must also be set. * * @var string + * + * @deprecated use Config\Cookie::$samesite property instead. */ public $cookieSameSite = 'Lax'; - /** - * -------------------------------------------------------------------------- - * Cookie Raw - * -------------------------------------------------------------------------- - * - * This flag allows setting a "raw" cookie, i.e., its name and value are - * not URL encoded using `rawurlencode()`. - * - * If this is set to `true`, cookie names should be compliant of RFC 2616's - * list of allowed characters. - * - * @var boolean - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes - * @see https://tools.ietf.org/html/rfc2616#section-2.2 - */ - public $cookieRaw = false; - - /** - * -------------------------------------------------------------------------- - * Cookie Expires Timestamp - * -------------------------------------------------------------------------- - * - * Default expires timestamp for cookies. Setting this to `0` will mean the - * cookie will not have the `Expires` attribute and will behave as a session - * cookie. - * - * @var DateTimeInterface|integer|string - */ - public $cookieExpires = 0; - /** * -------------------------------------------------------------------------- * Reverse Proxy IPs diff --git a/app/Config/Cookie.php b/app/Config/Cookie.php new file mode 100644 index 000000000000..4e4e20d3a731 --- /dev/null +++ b/app/Config/Cookie.php @@ -0,0 +1,119 @@ + '', 'secure' => false, 'httponly' => true, - 'raw' => false, 'samesite' => self::SAMESITE_LAX, + 'raw' => false, ]; /** @@ -118,28 +119,29 @@ class Cookie implements ArrayAccess, CloneableCookieInterface /** * Set the default attributes to a Cookie instance by injecting - * the values from the `App` config or an array. + * the values from the `CookieConfig` config or an array. * - * @param App|array $config + * @param CookieConfig|array $config * * @return array The old defaults array. Useful for resetting. */ public static function setDefaults($config = []) { $oldDefaults = self::$defaults; + $newDefaults = []; - if ($config instanceof App) + if ($config instanceof CookieConfig) { $newDefaults = [ - 'prefix' => $config->cookiePrefix, - 'expires' => $config->cookieExpires ?? 0, - 'domain' => $config->cookieDomain, - 'path' => $config->cookiePath, - 'secure' => $config->cookieSecure, - 'httponly' => $config->cookieHTTPOnly, - 'raw' => $config->cookieRaw ?? false, - 'samesite' => $config->cookieSameSite ?? self::SAMESITE_LAX, + 'prefix' => $config->prefix, + 'expires' => $config->expires, + 'path' => $config->path, + 'domain' => $config->domain, + 'secure' => $config->secure, + 'httponly' => $config->httponly, + 'samesite' => $config->samesite, + 'raw' => $config->raw, ]; } elseif (is_array($config)) @@ -148,7 +150,7 @@ public static function setDefaults($config = []) } // This array union ensures that even if passed `$config` - // is not `App` or `array`, no empty defaults will occur. + // is not `CookieConfig` or `array`, no empty defaults will occur. self::$defaults = $newDefaults + $oldDefaults; return $oldDefaults; @@ -246,9 +248,9 @@ public static function create(string $name, string $value = '', array $options = * @param string|null $path The path on the server in which the cookie is available * @param string|null $domain The domain in which the cookie is available * @param boolean $secure Whether to send back the cookie over HTTPS - * @param boolean $httpOnly Whether to forbid JavaScript access to cookies + * @param boolean $httponly Whether to forbid JavaScript access to cookies * @param boolean $raw Whether the cookie should be sent with no URL encoding - * @param string $sameSite Whether the cookie will be available for cross-site requests + * @param string $samesite Whether the cookie will be available for cross-site requests * * @throws CookieException */ @@ -260,9 +262,9 @@ final public function __construct( string $path = null, string $domain = null, bool $secure = false, - bool $httpOnly = true, + bool $httponly = true, bool $raw = false, - string $sameSite = self::SAMESITE_LAX + string $samesite = self::SAMESITE_LAX ) { // to retain BC @@ -270,23 +272,23 @@ final public function __construct( $path = $path ?: self::$defaults['path']; $domain = $domain ?: self::$defaults['domain']; $secure = $secure ?: self::$defaults['secure']; - $httpOnly = $httpOnly ?: self::$defaults['httponly']; - $sameSite = $sameSite ?: self::$defaults['samesite']; + $httponly = $httponly ?: self::$defaults['httponly']; + $samesite = $samesite ?: self::$defaults['samesite']; $this->validateName($name, $raw); - $this->validatePrefix($prefix, $secure, $domain, $path); - $this->validateSameSite($sameSite, $secure); + $this->validatePrefix($prefix, $secure, $path, $domain); + $this->validateSameSite($samesite, $secure); + $this->prefix = $prefix; $this->name = $name; $this->value = $value; $this->expires = static::convertExpiresTimestamp($expires); - $this->prefix = $prefix; $this->path = $path; $this->domain = $domain; $this->secure = $secure; - $this->httpOnly = $httpOnly; + $this->httponly = $httponly; + $this->samesite = ucfirst(strtolower($samesite)); $this->raw = $raw; - $this->sameSite = ucfirst(strtolower($sameSite)); } //========================================================================= @@ -298,19 +300,7 @@ final public function __construct( */ public function getId(): string { - $name = $this->getPrefixedName(); - $domain = $this->getDomain(); - $path = $this->getPath(); - - return implode(';', [$name, $domain, $path]); - } - - /** - * {@inheritDoc} - */ - public function isRaw(): bool - { - return $this->raw; + return implode(';', [$this->getPrefixedName(), $this->getPath(), $this->getDomain()]); } /** @@ -398,17 +388,17 @@ public function getMaxAge(): int /** * {@inheritDoc} */ - public function getDomain(): string + public function getPath(): string { - return $this->domain; + return $this->path; } /** * {@inheritDoc} */ - public function getPath(): string + public function getDomain(): string { - return $this->path; + return $this->domain; } /** @@ -422,9 +412,9 @@ public function isSecure(): bool /** * {@inheritDoc} */ - public function isHttpOnly(): bool + public function isHTTPOnly(): bool { - return $this->httpOnly; + return $this->httponly; } /** @@ -432,7 +422,15 @@ public function isHttpOnly(): bool */ public function getSameSite(): string { - return $this->sameSite; + return $this->samesite; + } + + /** + * {@inheritDoc} + */ + public function isRaw(): bool + { + return $this->raw; } /** @@ -446,8 +444,8 @@ public function getOptions(): array 'path' => $this->path, 'domain' => $this->domain, 'secure' => $this->secure, - 'httponly' => $this->httpOnly, - 'samesite' => $this->sameSite ?: ucfirst(self::SAMESITE_LAX), + 'httponly' => $this->httponly, + 'samesite' => $this->samesite ?: ucfirst(self::SAMESITE_LAX), ]; } @@ -455,26 +453,12 @@ public function getOptions(): array // CLONING //========================================================================= - /** - * {@inheritDoc} - */ - public function withRaw(bool $raw = true) - { - $this->validateName($this->name, $raw); - - $cookie = clone $this; - - $cookie->raw = $raw; - - return $cookie; - } - /** * {@inheritDoc} */ public function withPrefix(string $prefix = '') { - $this->validatePrefix($prefix, $this->secure, $this->domain, $this->path); + $this->validatePrefix($prefix, $this->secure, $this->path, $this->domain); $cookie = clone $this; @@ -548,14 +532,14 @@ public function withNeverExpiring() /** * {@inheritDoc} */ - public function withDomain(?string $domain) + public function withPath(?string $path) { - $domain = $domain ?? self::$defaults['domain']; - $this->validatePrefix($this->prefix, $this->secure, $domain, $this->path); + $path = $path ?: self::$defaults['path']; + $this->validatePrefix($this->prefix, $this->secure, $path, $this->domain); $cookie = clone $this; - $cookie->domain = $domain; + $cookie->path = $path; return $cookie; } @@ -563,14 +547,14 @@ public function withDomain(?string $domain) /** * {@inheritDoc} */ - public function withPath(?string $path) + public function withDomain(?string $domain) { - $path = $path ?: self::$defaults['path']; - $this->validatePrefix($this->prefix, $this->secure, $this->domain, $path); + $domain = $domain ?? self::$defaults['domain']; + $this->validatePrefix($this->prefix, $this->secure, $this->path, $domain); $cookie = clone $this; - $cookie->path = $path; + $cookie->domain = $domain; return $cookie; } @@ -580,8 +564,8 @@ public function withPath(?string $path) */ public function withSecure(bool $secure = true) { - $this->validatePrefix($this->prefix, $secure, $this->domain, $this->path); - $this->validateSameSite($this->sameSite, $secure); + $this->validatePrefix($this->prefix, $secure, $this->path, $this->domain); + $this->validateSameSite($this->samesite, $secure); $cookie = clone $this; @@ -593,11 +577,11 @@ public function withSecure(bool $secure = true) /** * {@inheritDoc} */ - public function withHttpOnly(bool $httpOnly = true) + public function withHTTPOnly(bool $httponly = true) { $cookie = clone $this; - $cookie->httpOnly = $httpOnly; + $cookie->httponly = $httponly; return $cookie; } @@ -605,13 +589,27 @@ public function withHttpOnly(bool $httpOnly = true) /** * {@inheritDoc} */ - public function withSameSite(string $sameSite) + public function withSameSite(string $samesite) { - $this->validateSameSite($sameSite, $this->secure); + $this->validateSameSite($samesite, $this->secure); $cookie = clone $this; - $cookie->sameSite = ucfirst(strtolower($sameSite)); + $cookie->samesite = ucfirst(strtolower($samesite)); + + return $cookie; + } + + /** + * {@inheritDoc} + */ + public function withRaw(bool $raw = true) + { + $this->validateName($this->name, $raw); + + $cookie = clone $this; + + $cookie->raw = $raw; return $cookie; } @@ -629,12 +627,7 @@ public function withSameSite(string $sameSite) */ public function offsetExists($offset) { - if (in_array($offset, ['expire', 'httponly', 'samesite'], true)) - { - return true; - } - - return property_exists($this, $offset); + return $offset === 'expire' ? true : property_exists($this, $offset); } /** @@ -653,22 +646,7 @@ public function offsetGet($offset) throw new InvalidArgumentException(sprintf('Undefined offset "%s".', $offset)); } - if ($offset === 'expire') - { - return $this->expires; - } - - if ($offset === 'httponly') - { - return $this->httpOnly; - } - - if ($offset === 'samesite') - { - return $this->sameSite; - } - - return $this->{$offset}; + return $offset === 'expire' ? $this->expires : $this->{$offset}; } /** @@ -753,21 +731,21 @@ public function __toString() $cookieHeader[] = 'Secure'; } - if ($this->isHttpOnly()) + if ($this->isHTTPOnly()) { $cookieHeader[] = 'HttpOnly'; } - $sameSite = $this->getSameSite(); + $samesite = $this->getSameSite(); - if ($sameSite === '') + if ($samesite === '') { // modern browsers warn in console logs that an empty SameSite attribute // will be given the `Lax` value - $sameSite = self::SAMESITE_LAX; + $samesite = self::SAMESITE_LAX; } - $cookieHeader[] = 'SameSite=' . ucfirst(strtolower($sameSite)); + $cookieHeader[] = 'SameSite=' . ucfirst(strtolower($samesite)); return implode('; ', $cookieHeader); } @@ -852,14 +830,14 @@ protected function validateName(string $name, bool $raw): void * * @param string $prefix * @param boolean $secure - * @param string $domain * @param string $path + * @param string $domain * * @throws CookieException * * @return void */ - protected function validatePrefix(string $prefix, bool $secure, string $domain, string $path): void + protected function validatePrefix(string $prefix, bool $secure, string $path, string $domain): void { if (strpos($prefix, '__Secure-') === 0 && ! $secure) { @@ -875,7 +853,7 @@ protected function validatePrefix(string $prefix, bool $secure, string $domain, /** * Validates the `SameSite` to be within the allowed types. * - * @param string $sameSite + * @param string $samesite * @param boolean $secure * * @throws CookieException @@ -884,24 +862,24 @@ protected function validatePrefix(string $prefix, bool $secure, string $domain, * * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite */ - protected function validateSameSite(string $sameSite, bool $secure): void + protected function validateSameSite(string $samesite, bool $secure): void { - if ($sameSite === '') + if ($samesite === '') { - $sameSite = self::$defaults['samesite']; + $samesite = self::$defaults['samesite']; } - if ($sameSite === '') + if ($samesite === '') { - $sameSite = self::SAMESITE_LAX; + $samesite = self::SAMESITE_LAX; } - if (! in_array(strtolower($sameSite), self::ALLOWED_SAMESITE_VALUES, true)) + if (! in_array(strtolower($samesite), self::ALLOWED_SAMESITE_VALUES, true)) { - throw CookieException::forInvalidSameSite($sameSite); + throw CookieException::forInvalidSameSite($samesite); } - if (strtolower($sameSite) === self::SAMESITE_NONE && ! $secure) + if (strtolower($samesite) === self::SAMESITE_NONE && ! $secure) { throw CookieException::forInvalidSameSiteNone(); } diff --git a/system/Cookie/CookieInterface.php b/system/Cookie/CookieInterface.php index 712b34886789..1dddb13a1426 100644 --- a/system/Cookie/CookieInterface.php +++ b/system/Cookie/CookieInterface.php @@ -67,19 +67,12 @@ interface CookieInterface /** * Returns a unique identifier for the cookie consisting - * of its name, prefix, domain, and path. + * of its prefixed name, path, and domain. * * @return string */ public function getId(): string; - /** - * Checks if the cookie should be sent with no URL encoding. - * - * @return boolean - */ - public function isRaw(): bool; - /** * Gets the cookie prefix. * @@ -137,18 +130,18 @@ public function isExpired(): bool; public function getMaxAge(): int; /** - * Gets the "Domain" cookie attribute. + * Gets the "Path" cookie attribute. * * @return string */ - public function getDomain(): string; + public function getPath(): string; /** - * Gets the "Path" cookie attribute. + * Gets the "Domain" cookie attribute. * * @return string */ - public function getPath(): string; + public function getDomain(): string; /** * Gets the "Secure" cookie attribute. @@ -168,7 +161,7 @@ public function isSecure(): bool; * * @return boolean */ - public function isHttpOnly(): bool; + public function isHTTPOnly(): bool; /** * Gets the "SameSite" cookie attribute. @@ -177,6 +170,13 @@ public function isHttpOnly(): bool; */ public function getSameSite(): string; + /** + * Checks if the cookie should be sent with no URL encoding. + * + * @return boolean + */ + public function isRaw(): bool; + /** * Gets the options that are passable to the `setcookie` variant * available on PHP 7.3+ diff --git a/system/Cookie/CookieStore.php b/system/Cookie/CookieStore.php index f992b2615acc..e7b68047560c 100644 --- a/system/Cookie/CookieStore.php +++ b/system/Cookie/CookieStore.php @@ -167,11 +167,8 @@ public function put(Cookie $cookie) public function remove(string $name, string $prefix = '') { $default = Cookie::setDefaults(); - $name = $prefix . $name; - $domain = $default['domain']; - $path = $default['path']; - $id = implode(';', [$name, $domain, $path]); + $id = implode(';', [$prefix . $name, $default['path'], $default['domain']]); $store = clone $this; @@ -191,7 +188,7 @@ public function remove(string $name, string $prefix = '') * * @return void */ - public function dispatch() + public function dispatch(): void { foreach ($this->cookies as $cookie) { @@ -217,7 +214,7 @@ public function dispatch() * * @return array */ - public function display() + public function display(): array { return $this->cookies; } @@ -237,7 +234,7 @@ public function clear(): void * * @return integer */ - public function count() + public function count(): int { return count($this->cookies); } @@ -285,7 +282,7 @@ protected function validateCookies(array $cookies): void * * @return void */ - protected function setRawCookie(string $name, string $value, array $options) + protected function setRawCookie(string $name, string $value, array $options): void { setrawcookie($name, $value, $options); } @@ -301,7 +298,7 @@ protected function setRawCookie(string $name, string $value, array $options) * * @return void */ - protected function setCookie(string $name, string $value, array $options) + protected function setCookie(string $name, string $value, array $options): void { setcookie($name, $value, $options); } diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index 721762cbd664..2f4c1b5491b9 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -16,6 +16,7 @@ use CodeIgniter\HTTP\Exceptions\CookieException; use CodeIgniter\HTTP\Exceptions\HTTPException; use Config\App; +use Config\ContentSecurityPolicy as CSPConfig; /** * Representation of an outgoing, getServer-side response. @@ -151,7 +152,7 @@ public function __construct($config) $this->noCache(); // We need CSP object even if not enabled to avoid calls to non existing methods - $this->CSP = new ContentSecurityPolicy(new \Config\ContentSecurityPolicy()); + $this->CSP = new ContentSecurityPolicy(new CSPConfig()); $this->CSPEnabled = $config->CSPEnabled; @@ -173,7 +174,7 @@ public function __construct($config) } $this->cookieStore = new CookieStore([]); - Cookie::setDefaults($config); + Cookie::setDefaults(config('Cookie')); // Default to an HTML Content-Type. Devs can override if needed. $this->setContentType('text/html'); diff --git a/system/Security/Security.php b/system/Security/Security.php index 3ea20c7eebf8..ab1e066844f1 100644 --- a/system/Security/Security.php +++ b/system/Security/Security.php @@ -15,6 +15,7 @@ use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\Security\Exceptions\SecurityException; use Config\App; +use Config\Cookie as CookieConfig; use Config\Security as SecurityConfig; /** @@ -136,11 +137,18 @@ public function __construct(App $config) $this->headerName = $security->headerName ?? $config->CSRFHeaderName ?? $this->headerName; $this->regenerate = $security->regenerate ?? $config->CSRFRegenerate ?? $this->regenerate; $rawCookieName = $security->cookieName ?? $config->CSRFCookieName ?? $this->cookieName; - $this->cookieName = $config->cookiePrefix . $rawCookieName; + + /** + * @var CookieConfig + */ + $cookie = config('Cookie'); + + $cookiePrefix = $cookie->prefix ?? $config->cookiePrefix; + $this->cookieName = $cookiePrefix . $rawCookieName; $expires = $security->expires ?? $config->CSRFExpire ?? 7200; - Cookie::setDefaults($config); + Cookie::setDefaults($cookie); $this->cookie = Cookie::create($rawCookieName, $this->generateHash(), [ 'expires' => $expires === 0 ? 0 : time() + $expires, ]); diff --git a/system/Session/Session.php b/system/Session/Session.php index 9cdcdd4f6e4b..fabd2d81c1a6 100644 --- a/system/Session/Session.php +++ b/system/Session/Session.php @@ -13,6 +13,7 @@ use CodeIgniter\Cookie\Cookie; use Config\App; +use Config\Cookie as CookieConfig; use Psr\Log\LoggerAwareTrait; use Psr\Log\LoggerInterface; use SessionHandlerInterface; @@ -181,19 +182,24 @@ public function __construct(SessionHandlerInterface $driver, App $config) //--------------------------------------------------------------------- // DEPRECATED COOKIE MANAGEMENT //--------------------------------------------------------------------- - $this->cookieDomain = $config->cookieDomain ?? $this->cookieDomain; $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 + */ + $config = config('Cookie'); + $this->cookie = Cookie::create($this->sessionCookieName, '', [ 'expires' => $this->sessionExpiration === 0 ? 0 : time() + $this->sessionExpiration, - 'raw' => $config->cookieRaw ?? false, - 'domain' => $config->cookieDomain ?? '', - 'path' => $config->cookiePath ?? '/', - 'secure' => $config->cookieSecure ?? false, + 'path' => $config->path, + 'domain' => $config->domain, + 'secure' => $config->secure, 'httponly' => true, // for security - 'samesite' => $config->cookieSameSite ?? Cookie::SAMESITE_LAX, + 'samesite' => $config->samesite, + 'raw' => $config->raw, ]); helper('array'); diff --git a/tests/system/Cookie/CookieTest.php b/tests/system/Cookie/CookieTest.php index d28f6f549caa..7cc19ed3b74c 100644 --- a/tests/system/Cookie/CookieTest.php +++ b/tests/system/Cookie/CookieTest.php @@ -5,7 +5,7 @@ use CodeIgniter\Cookie\Cookie; use CodeIgniter\HTTP\Exceptions\CookieException; use CodeIgniter\Test\CIUnitTestCase; -use Config\App; +use Config\Cookie as CookieConfig; use DateTimeImmutable; use DateTimeZone; use InvalidArgumentException; @@ -41,35 +41,36 @@ public function testCookieInitializationWithDefaults(): void $this->assertSame('test', $cookie->getName()); $this->assertSame('value', $cookie->getValue()); $this->assertSame($options['prefix'], $cookie->getPrefix()); - $this->assertSame($options['raw'], $cookie->isRaw()); $this->assertSame($options['expires'], $cookie->getExpiresTimestamp()); - $this->assertSame($options['domain'], $cookie->getDomain()); $this->assertSame($options['path'], $cookie->getPath()); + $this->assertSame($options['domain'], $cookie->getDomain()); $this->assertSame($options['secure'], $cookie->isSecure()); - $this->assertSame($options['httponly'], $cookie->isHttpOnly()); + $this->assertSame($options['httponly'], $cookie->isHTTPOnly()); $this->assertSame($options['samesite'], $cookie->getSameSite()); + $this->assertSame($options['raw'], $cookie->isRaw()); } public function testConfigInjectionForDefaults(): void { /** - * @var App $app + * @var CookieConfig $config */ - $app = config('App', false); - $old = Cookie::setDefaults($app); + $config = new CookieConfig(); + + $old = Cookie::setDefaults($config); $cookie = Cookie::create('test', 'value'); - $this->assertSame($app->cookiePrefix . 'test', $cookie->getPrefixedName()); + $this->assertSame($config->prefix . 'test', $cookie->getPrefixedName()); $this->assertSame('test', $cookie->getName()); $this->assertSame('value', $cookie->getValue()); - $this->assertSame($app->cookiePrefix, $cookie->getPrefix()); - $this->assertSame($app->cookieRaw, $cookie->isRaw()); - $this->assertSame($app->cookieExpires, $cookie->getExpiresTimestamp()); - $this->assertSame($app->cookieDomain, $cookie->getDomain()); - $this->assertSame($app->cookiePath, $cookie->getPath()); - $this->assertSame($app->cookieSecure, $cookie->isSecure()); - $this->assertSame($app->cookieHTTPOnly, $cookie->isHttpOnly()); - $this->assertSame($app->cookieSameSite, $cookie->getSameSite()); + $this->assertSame($config->prefix, $cookie->getPrefix()); + $this->assertSame($config->expires, $cookie->getExpiresTimestamp()); + $this->assertSame($config->path, $cookie->getPath()); + $this->assertSame($config->domain, $cookie->getDomain()); + $this->assertSame($config->secure, $cookie->isSecure()); + $this->assertSame($config->httponly, $cookie->isHTTPOnly()); + $this->assertSame($config->samesite, $cookie->getSameSite()); + $this->assertSame($config->raw, $cookie->isRaw()); Cookie::setDefaults($old); } @@ -213,7 +214,7 @@ public function testCloningCookies(): void $i = $a->withDomain('localhost'); $j = $a->withPath('/web'); $k = $a->withSecure(); - $l = $a->withHttpOnly(); + $l = $a->withHTTPOnly(); $m = $a->withSameSite(Cookie::SAMESITE_STRICT); $this->assertNotSame($a, $b); @@ -236,7 +237,7 @@ public function testStringCastingOfCookies(): void $a = Cookie::create('cookie', 'lover'); $b = $a->withValue('monster')->withPath('/web')->withDomain('localhost')->withExpiresAt($date); - $c = $a->withSecure()->withHttpOnly(false)->withSameSite(Cookie::SAMESITE_STRICT); + $c = $a->withSecure()->withHTTPOnly(false)->withSameSite(Cookie::SAMESITE_STRICT); $max = (string) $b->getMaxAge(); $old = Cookie::setDefaults(['samesite' => '']); @@ -270,7 +271,7 @@ public function testArrayAccessOfCookie(): void $this->assertTrue(isset($cookie['expire'])); $this->assertSame($cookie['expire'], $cookie->getExpiresTimestamp()); $this->assertTrue(isset($cookie['httponly'])); - $this->assertSame($cookie['httponly'], $cookie->isHttpOnly()); + $this->assertSame($cookie['httponly'], $cookie->isHTTPOnly()); $this->assertTrue(isset($cookie['samesite'])); $this->assertSame($cookie['samesite'], $cookie->getSameSite()); $this->assertTrue(isset($cookie['path'])); diff --git a/tests/system/HTTP/ResponseCookieTest.php b/tests/system/HTTP/ResponseCookieTest.php index 6c89673e7061..dc23b3dfd282 100644 --- a/tests/system/HTTP/ResponseCookieTest.php +++ b/tests/system/HTTP/ResponseCookieTest.php @@ -6,6 +6,7 @@ use CodeIgniter\HTTP\Exceptions\CookieException; use CodeIgniter\Test\CIUnitTestCase; use Config\App; +use Config\Cookie as CookieConfig; /** * @internal @@ -30,9 +31,9 @@ protected function tearDown(): void public function testCookiePrefixed() { - $config = new App(); - $config->cookiePrefix = 'mine'; - $response = new Response($config); + $config = config('Cookie'); + $config->prefix = 'mine'; + $response = new Response(new App()); $response->setCookie('foo', 'bar'); $this->assertInstanceOf(Cookie::class, $response->getCookie('foo')); @@ -45,8 +46,7 @@ public function testCookiePrefixed() public function testCookiesAll() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie('foo', 'bar'); $response->setCookie('bee', 'bop'); @@ -57,8 +57,7 @@ public function testCookiesAll() public function testCookieGet() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie('foo', 'bar'); $response->setCookie('bee', 'bop'); @@ -68,8 +67,7 @@ public function testCookieGet() public function testCookieDomain() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie('foo', 'bar'); $cookie = $response->getCookie('foo'); @@ -79,8 +77,9 @@ public function testCookieDomain() $cookie = $response->getCookie('bee'); $this->assertSame('somewhere.com', $cookie->getDomain()); - $config->cookieDomain = 'mine.com'; - $response = new Response($config); + $config = config('Cookie'); + $config->domain = 'mine.com'; + $response = new Response(new App()); $response->setCookie('alu', 'la'); $cookie = $response->getCookie('alu'); $this->assertSame('mine.com', $cookie->getDomain()); @@ -88,8 +87,7 @@ public function testCookieDomain() public function testCookiePath() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie('foo', 'bar'); $cookie = $response->getCookie('foo'); @@ -102,8 +100,7 @@ public function testCookiePath() public function testCookieSecure() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie('foo', 'bar'); $cookie = $response->getCookie('foo'); @@ -116,33 +113,31 @@ public function testCookieSecure() public function testCookieHTTPOnly() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie('foo', 'bar'); $cookie = $response->getCookie('foo'); - $this->assertTrue($cookie->isHttpOnly()); + $this->assertTrue($cookie->isHTTPOnly()); $response->setCookie(['name' => 'bee', 'value' => 'bop', 'httponly' => false]); $cookie = $response->getCookie('bee'); - $this->assertTrue($cookie->isHttpOnly()); + $this->assertTrue($cookie->isHTTPOnly()); } public function testCookieExpiry() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie('foo', 'bar'); $cookie = $response->getCookie('foo'); $this->assertTrue($cookie->isExpired()); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); $cookie = $response->getCookie('bee'); $this->assertFalse($cookie->isExpired()); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => -1000]); $cookie = $response->getCookie('bee'); $this->assertSame(0, $cookie->getExpiresTimestamp()); @@ -150,8 +145,7 @@ public function testCookieExpiry() public function testCookieDelete() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); // foo is already expired, bee will stick around $response->setCookie('foo', 'bar'); @@ -160,36 +154,37 @@ public function testCookieDelete() $this->assertFalse($cookie->isExpired()); // delete cookie manually - $response = new Response($config); + $response = new Response(new App()); $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => '']); $cookie = $response->getCookie('bee'); $this->assertTrue($cookie->isExpired(), $cookie->getExpiresTimestamp() . ' should be less than ' . time()); // delete with no effect - $response = new Response($config); + $response = new Response(new App()); $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); $response->deleteCookie(); $cookie = $response->getCookie('bee'); $this->assertFalse($cookie->isExpired()); // delete cookie for real - $response = new Response($config); + $response = new Response(new App()); $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); $response->deleteCookie('bee'); $cookie = $response->getCookie('bee'); $this->assertTrue($cookie->isExpired(), $cookie->getExpiresTimestamp() . ' should be less than ' . time()); + $config = config('Cookie'); // delete cookie for real, with prefix - $config->cookiePrefix = 'mine'; - $response = new Response($config); + $config->prefix = 'mine'; + $response = new Response(new App()); $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); $response->deleteCookie('bee'); $cookie = $response->getCookie('bee'); $this->assertSame($cookie->getExpiresTimestamp(), 0); // delete cookie with wrong prefix? - $config->cookiePrefix = 'mine'; - $response = new Response($config); + $config->prefix = 'mine'; + $response = new Response(new App()); $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); $response->deleteCookie('bee', '', '', 'wrong'); $cookie = $response->getCookie('bee'); @@ -199,8 +194,8 @@ public function testCookieDelete() $this->assertSame($cookie->getExpiresTimestamp(), 0); // delete cookie with wrong domain? - $config->cookieDomain = '.mine.com'; - $response = new Response($config); + $config->domain = '.mine.com'; + $response = new Response(new App()); $response->setCookie(['name' => 'bees', 'value' => 'bop', 'expire' => 1000]); $response->deleteCookie('bees', 'wrong', '', ''); $cookie = $response->getCookie('bees'); @@ -212,8 +207,7 @@ public function testCookieDelete() public function testCookieDefaultSetSameSite() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie([ 'name' => 'bar', 'value' => 'foo', @@ -221,15 +215,15 @@ public function testCookieDefaultSetSameSite() $allCookies = $response->getCookies(); $this->assertCount(1, $allCookies); - $this->assertInstanceOf(Cookie::class, $allCookies['bar;;/']); - $this->assertSame('Lax', $allCookies['bar;;/']->getSameSite()); + $this->assertInstanceOf(Cookie::class, $allCookies['bar;/;']); + $this->assertSame('Lax', $allCookies['bar;/;']->getSameSite()); } public function testCookieStrictSetSameSite() { - $config = new App(); - $config->cookieSameSite = 'Strict'; - $response = new Response($config); + $config = config('Cookie'); + $config->samesite = 'Strict'; + $response = new Response(new App()); $response->setCookie([ 'name' => 'bar', 'value' => 'foo', @@ -237,15 +231,15 @@ public function testCookieStrictSetSameSite() $allCookies = $response->getCookies(); $this->assertCount(1, $allCookies); - $this->assertInstanceOf(Cookie::class, $allCookies['bar;;/']); - $this->assertSame('Strict', $allCookies['bar;;/']->getSameSite()); + $this->assertInstanceOf(Cookie::class, $allCookies['bar;/;']); + $this->assertSame('Strict', $allCookies['bar;/;']->getSameSite()); } public function testCookieBlankSetSameSite() { - $config = new App(); - $config->cookieSameSite = ''; - $response = new Response($config); + $config = config('Cookie'); + $config->samesite = ''; + $response = new Response(new App()); $response->setCookie([ 'name' => 'bar', 'value' => 'foo', @@ -253,15 +247,15 @@ public function testCookieBlankSetSameSite() $allCookies = $response->getCookies(); $this->assertCount(1, $allCookies); - $this->assertInstanceOf(Cookie::class, $allCookies['bar;;/']); - $this->assertSame('', $allCookies['bar;;/']->getSameSite()); + $this->assertInstanceOf(Cookie::class, $allCookies['bar;/;']); + $this->assertSame('', $allCookies['bar;/;']->getSameSite()); } public function testCookieWithoutSameSite() { - $config = new App(); - unset($config->cookieSameSite); - $response = new Response($config); + $config = new CookieConfig(); + unset($config->samesite); + $response = new Response(new App()); $response->setCookie([ 'name' => 'bar', 'value' => 'foo', @@ -269,14 +263,13 @@ public function testCookieWithoutSameSite() $allCookies = $response->getCookies(); $this->assertCount(1, $allCookies); - $this->assertInstanceOf(Cookie::class, $allCookies['bar;;/']); - $this->assertSame('Lax', $allCookies['bar;;/']->getSameSite()); + $this->assertInstanceOf(Cookie::class, $allCookies['bar;/;']); + $this->assertSame('Lax', $allCookies['bar;/;']->getSameSite()); } public function testCookieStrictSameSite() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $response->setCookie([ 'name' => 'bar', 'value' => 'foo', @@ -285,14 +278,13 @@ public function testCookieStrictSameSite() $allCookies = $response->getCookies(); $this->assertCount(1, $allCookies); - $this->assertInstanceOf(Cookie::class, $allCookies['bar;;/']); - $this->assertSame('Strict', $allCookies['bar;;/']->getSameSite()); + $this->assertInstanceOf(Cookie::class, $allCookies['bar;/;']); + $this->assertSame('Strict', $allCookies['bar;/;']->getSameSite()); } public function testCookieInvalidSameSite() { - $config = new App(); - $response = new Response($config); + $response = new Response(new App()); $this->expectException(CookieException::class); $this->expectExceptionMessage(lang('Cookie.invalidSameSite', ['Invalid'])); diff --git a/tests/system/Security/SecurityTest.php b/tests/system/Security/SecurityTest.php index bbb168d534e7..fd25d9205ad4 100644 --- a/tests/system/Security/SecurityTest.php +++ b/tests/system/Security/SecurityTest.php @@ -11,6 +11,7 @@ use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\Mock\MockAppConfig; use CodeIgniter\Test\Mock\MockSecurity; +use Config\Cookie as CookieConfig; use Config\Security as SecurityConfig; /** @@ -21,7 +22,10 @@ class SecurityTest extends CIUnitTestCase protected function setUp(): void { parent::setUp(); + $_COOKIE = []; + + Factories::reset(); } public function testBasicConfigIsSaved() @@ -209,12 +213,13 @@ public function testSendingCookiesFalse(): void $request = $this->createMock(IncomingRequest::class); $request->method('isSecure')->willReturn(false); - $config = new MockAppConfig(); + $config = new CookieConfig(); - $config->cookieSecure = true; + $config->secure = true; + Factories::injectMock('config', CookieConfig::class, $config); $security = $this->getMockBuilder(Security::class) - ->setConstructorArgs([$config]) + ->setConstructorArgs([new MockAppConfig()]) ->onlyMethods(['doSendCookie']) ->getMock(); diff --git a/tests/system/Session/SessionTest.php b/tests/system/Session/SessionTest.php index 141e06fed67f..9addefe6465a 100644 --- a/tests/system/Session/SessionTest.php +++ b/tests/system/Session/SessionTest.php @@ -2,13 +2,15 @@ namespace CodeIgniter\Session; +use CodeIgniter\Config\Factories; use CodeIgniter\HTTP\Exceptions\CookieException; use CodeIgniter\Session\Handlers\FileHandler; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\Mock\MockSession; use CodeIgniter\Test\TestLogger; use Config\App as AppConfig; -use Config\Logger; +use Config\Cookie as CookieConfig; +use Config\Logger as LoggerConfig; /** * @runTestsInSeparateProcesses @@ -50,7 +52,7 @@ protected function getInstance($options = []) } $session = new MockSession(new FileHandler($appConfig, '127.0.0.1'), $appConfig); - $session->setLogger(new TestLogger(new Logger())); + $session->setLogger(new TestLogger(new LoggerConfig())); return $session; } @@ -552,7 +554,11 @@ public function testGetDotKey() public function testLaxSameSite() { - $session = $this->getInstance(['cookieSameSite' => 'Lax']); + $config = new CookieConfig(); + $config->samesite = 'Lax'; + Factories::injectMock('config', CookieConfig::class, $config); + + $session = $this->getInstance(); $session->start(); $cookies = $session->cookies; $this->assertCount(1, $cookies); @@ -561,7 +567,13 @@ public function testLaxSameSite() public function testNoneSameSite() { - $session = $this->getInstance(['cookieSameSite' => 'None', 'cookieSecure' => true]); + $config = new CookieConfig(); + $config->secure = true; + $config->samesite = 'None'; + + Factories::injectMock('config', CookieConfig::class, $config); + + $session = $this->getInstance(); $session->start(); $cookies = $session->cookies; @@ -571,7 +583,12 @@ public function testNoneSameSite() public function testNoSameSiteReturnsDefault() { - $session = $this->getInstance(['cookieSameSite' => '']); + $config = new CookieConfig(); + $config->samesite = ''; + + Factories::injectMock('config', CookieConfig::class, $config); + + $session = $this->getInstance(); $session->start(); $cookies = $session->cookies; @@ -584,7 +601,12 @@ public function testInvalidSameSite() $this->expectException(CookieException::class); $this->expectExceptionMessage(lang('Cookie.invalidSameSite', ['Invalid'])); - $session = $this->getInstance(['cookieSameSite' => 'Invalid']); + $config = new CookieConfig(); + $config->samesite = 'Invalid'; + + Factories::injectMock('config', CookieConfig::class, $config); + + $session = $this->getInstance(); $session->start(); }