Skip to content

Commit ce57929

Browse files
committed
Merge branch 'encrypted-cookie-arrays'
2 parents 6978dde + 4aa5b3b commit ce57929

File tree

3 files changed

+123
-15
lines changed

3 files changed

+123
-15
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. If it does, return the cookie value with the prefix removed. Otherwise, return 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: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,14 @@ 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-
88-
$request->cookies->set(
89-
$key, $hasValidPrefix ? CookieValuePrefix::remove($value) : null
90-
);
86+
$request->cookies->set($key, $this->validateValue($key, $value));
9187
} catch (DecryptException $e) {
9288
$request->cookies->set($key, null);
9389
}
@@ -96,6 +92,38 @@ protected function decrypt(Request $request)
9692
return $request;
9793
}
9894

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

129161
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)