Skip to content

Commit ef82f51

Browse files
author
Greg
committed
support nested arrays of encrypted cookies such asa[b][c]
1 parent 4066ca3 commit ef82f51

File tree

3 files changed

+120
-13
lines changed

3 files changed

+120
-13
lines changed

src/Illuminate/Cookie/CookieValuePrefix.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,19 @@ public static function remove($cookieValue)
2626
{
2727
return substr($cookieValue, 41);
2828
}
29+
30+
/**
31+
* Validate a cookie value contains a valid prefix and return it without or null.
32+
*
33+
* @param string $cookieName
34+
* @param string $cookieValue
35+
* @param string $key
36+
* @return string|null
37+
*/
38+
public static function validate($cookieName, $cookieValue, $key)
39+
{
40+
$hasValidPrefix = strpos($cookieValue, static::create($cookieName, $key)) === 0;
41+
42+
return $hasValidPrefix ? static::remove($cookieValue) : null;
43+
}
2944
}

src/Illuminate/Cookie/Middleware/EncryptCookies.php

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,17 +76,15 @@ public function handle($request, Closure $next)
7676
protected function decrypt(Request $request)
7777
{
7878
foreach ($request->cookies as $key => $cookie) {
79-
if ($this->isDisabled($key) || is_array($cookie)) {
79+
if ($this->isDisabled($key)) {
8080
continue;
8181
}
8282

8383
try {
8484
$value = $this->decryptCookie($key, $cookie);
8585

86-
$hasValidPrefix = strpos($value, CookieValuePrefix::create($key, $this->encrypter->getKey())) === 0;
87-
8886
$request->cookies->set(
89-
$key, $hasValidPrefix ? CookieValuePrefix::remove($value) : null
87+
$key, $this->validateValue($key, $value)
9088
);
9189
} catch (DecryptException $e) {
9290
$request->cookies->set($key, null);
@@ -96,6 +94,36 @@ protected function decrypt(Request $request)
9694
return $request;
9795
}
9896

97+
/**
98+
* Validate and remove the cookie value prefix from the value.
99+
*
100+
* @param string $key
101+
* @param string $value
102+
* @return string|array|null
103+
*/
104+
protected function validateValue(string $key, $value)
105+
{
106+
return is_array($value) ? $this->validateArray($key, $value) :
107+
CookieValuePrefix::validate($key, $value, $this->encrypter->getKey());
108+
}
109+
110+
/**
111+
* Validate and remove the cookie value prefix from all values of an array.
112+
*
113+
* @param string $key
114+
* @param array $value
115+
* @return array
116+
*/
117+
protected function validateArray(string $key, array $value)
118+
{
119+
$stripped = [];
120+
foreach ($value as $subKey => $subValue) {
121+
$stripped[$subKey] = $this->validateValue("${key}[${subKey}]", $subValue);
122+
}
123+
124+
return $stripped;
125+
}
126+
99127
/**
100128
* Decrypt the given cookie and return the value.
101129
*
@@ -124,6 +152,9 @@ protected function decryptArray(array $cookie)
124152
if (is_string($value)) {
125153
$decrypted[$key] = $this->encrypter->decrypt($value, static::serialized($key));
126154
}
155+
if (is_array($value)) {
156+
$decrypted[$key] = $this->decryptArray($value);
157+
}
127158
}
128159

129160
return $decrypted;

tests/Cookie/Middleware/EncryptCookiesTest.php

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Container\Container;
66
use Illuminate\Contracts\Encryption\Encrypter as EncrypterContract;
77
use Illuminate\Cookie\CookieJar;
8+
use Illuminate\Cookie\CookieValuePrefix;
89
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
910
use Illuminate\Cookie\Middleware\EncryptCookies;
1011
use Illuminate\Encryption\Encrypter;
@@ -18,6 +19,11 @@
1819

1920
class EncryptCookiesTest extends TestCase
2021
{
22+
/**
23+
* @var \Illuminate\Container\Container
24+
*/
25+
protected $container;
26+
2127
/**
2228
* @var \Illuminate\Routing\Router
2329
*/
@@ -30,12 +36,12 @@ protected function setUp(): void
3036
{
3137
parent::setUp();
3238

33-
$container = new Container;
34-
$container->singleton(EncrypterContract::class, function () {
39+
$this->container = new Container;
40+
$this->container->singleton(EncrypterContract::class, function () {
3541
return new Encrypter(str_repeat('a', 16));
3642
});
3743

38-
$this->router = new Router(new Dispatcher, $container);
44+
$this->router = new Router(new Dispatcher, $this->container);
3945
}
4046

4147
public function testSetCookieEncryption()
@@ -48,11 +54,14 @@ public function testSetCookieEncryption()
4854
$response = $this->router->dispatch(Request::create($this->setCookiePath, 'GET'));
4955

5056
$cookies = $response->headers->getCookies();
51-
$this->assertCount(2, $cookies);
57+
$this->assertCount(4, $cookies);
5258
$this->assertSame('encrypted_cookie', $cookies[0]->getName());
5359
$this->assertNotSame('value', $cookies[0]->getValue());
54-
$this->assertSame('unencrypted_cookie', $cookies[1]->getName());
55-
$this->assertSame('value', $cookies[1]->getValue());
60+
$this->assertSame('encrypted[array_cookie]', $cookies[1]->getName());
61+
$this->assertNotSame('value', $cookies[1]->getValue());
62+
$this->assertSame('encrypted[nested][array_cookie]', $cookies[2]->getName());
63+
$this->assertSame('unencrypted_cookie', $cookies[3]->getName());
64+
$this->assertSame('value', $cookies[3]->getValue());
5665
}
5766

5867
public function testQueuedCookieEncryption()
@@ -65,11 +74,59 @@ public function testQueuedCookieEncryption()
6574
$response = $this->router->dispatch(Request::create($this->queueCookiePath, 'GET'));
6675

6776
$cookies = $response->headers->getCookies();
68-
$this->assertCount(2, $cookies);
77+
$this->assertCount(4, $cookies);
6978
$this->assertSame('encrypted_cookie', $cookies[0]->getName());
7079
$this->assertNotSame('value', $cookies[0]->getValue());
71-
$this->assertSame('unencrypted_cookie', $cookies[1]->getName());
72-
$this->assertSame('value', $cookies[1]->getValue());
80+
$this->assertSame('encrypted[array_cookie]', $cookies[1]->getName());
81+
$this->assertNotSame('value', $cookies[1]->getValue());
82+
$this->assertSame('encrypted[nested][array_cookie]', $cookies[2]->getName());
83+
$this->assertNotSame('value', $cookies[2]->getValue());
84+
$this->assertSame('unencrypted_cookie', $cookies[3]->getName());
85+
$this->assertSame('value', $cookies[3]->getValue());
86+
}
87+
88+
protected function getEncryptedCookieValue($key, $value)
89+
{
90+
$encrypter = $this->container->make(EncrypterContract::class);
91+
92+
return $encrypter->encrypt(
93+
CookieValuePrefix::create($key, $encrypter->getKey()).$value,
94+
false
95+
);
96+
}
97+
98+
public function testCookieDecryption()
99+
{
100+
$cookies = [
101+
'encrypted_cookie' => $this->getEncryptedCookieValue('encrypted_cookie', 'value'),
102+
'encrypted' => [
103+
'array_cookie' => $this->getEncryptedCookieValue('encrypted[array_cookie]', 'value'),
104+
'nested' => [
105+
'array_cookie' => $this->getEncryptedCookieValue('encrypted[nested][array_cookie]', 'value'),
106+
],
107+
],
108+
'unencrypted_cookie' => 'value',
109+
];
110+
111+
$this->container->make(EncryptCookiesTestMiddleware::class)->handle(
112+
Request::create('/cookie/read', 'GET', [], $cookies),
113+
function ($request) {
114+
$cookies = $request->cookies->all();
115+
$this->assertCount(3, $cookies);
116+
$this->assertArrayHasKey('encrypted_cookie', $cookies);
117+
$this->assertSame('value', $cookies['encrypted_cookie']);
118+
$this->assertArrayHasKey('encrypted', $cookies);
119+
$this->assertArrayHasKey('array_cookie', $cookies['encrypted']);
120+
$this->assertSame('value', $cookies['encrypted']['array_cookie']);
121+
$this->assertArrayHasKey('nested', $cookies['encrypted']);
122+
$this->assertArrayHasKey('array_cookie', $cookies['encrypted']['nested']);
123+
$this->assertSame('value', $cookies['encrypted']['nested']['array_cookie']);
124+
$this->assertArrayHasKey('unencrypted_cookie', $cookies);
125+
$this->assertSame('value', $cookies['unencrypted_cookie']);
126+
127+
return new Response;
128+
}
129+
);
73130
}
74131
}
75132

@@ -79,6 +136,8 @@ public function setCookies()
79136
{
80137
$response = new Response;
81138
$response->headers->setCookie(new Cookie('encrypted_cookie', 'value'));
139+
$response->headers->setCookie(new Cookie('encrypted[array_cookie]', 'value'));
140+
$response->headers->setCookie(new Cookie('encrypted[nested][array_cookie]', 'value'));
82141
$response->headers->setCookie(new Cookie('unencrypted_cookie', 'value'));
83142

84143
return $response;
@@ -103,6 +162,8 @@ public function __construct()
103162
{
104163
$cookie = new CookieJar;
105164
$cookie->queue(new Cookie('encrypted_cookie', 'value'));
165+
$cookie->queue(new Cookie('encrypted[array_cookie]', 'value'));
166+
$cookie->queue(new Cookie('encrypted[nested][array_cookie]', 'value'));
106167
$cookie->queue(new Cookie('unencrypted_cookie', 'value'));
107168

108169
$this->cookies = $cookie;

0 commit comments

Comments
 (0)