1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
11: * @link http://cakephp.org CakePHP(tm) Project
12: * @since 3.5.0
13: * @license http://www.opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Http\Middleware;
16:
17: use Cake\Http\Cookie\CookieCollection;
18: use Cake\Http\Response;
19: use Cake\Utility\CookieCryptTrait;
20: use Psr\Http\Message\ResponseInterface;
21: use Psr\Http\Message\ServerRequestInterface;
22:
23: /**
24: * Middlware for encrypting & decrypting cookies.
25: *
26: * This middleware layer will encrypt/decrypt the named cookies with the given key
27: * and cipher type. To support multiple keys/cipher types use this middleware multiple
28: * times.
29: *
30: * Cookies in request data will be decrypted, while cookies in response headers will
31: * be encrypted automatically. If the response is a Cake\Http\Response, the cookie
32: * data set with `withCookie()` and `cookie()`` will also be encrypted.
33: *
34: * The encryption types and padding are compatible with those used by CookieComponent
35: * for backwards compatibility.
36: */
37: class EncryptedCookieMiddleware
38: {
39: use CookieCryptTrait;
40:
41: /**
42: * The list of cookies to encrypt/decrypt
43: * @var array
44: */
45: protected $cookieNames;
46:
47: /**
48: * Encryption key to use.
49: *
50: * @var string
51: */
52: protected $key;
53:
54: /**
55: * Encryption type.
56: *
57: * @var string
58: */
59: protected $cipherType;
60:
61: /**
62: * Constructor
63: *
64: * @param array $cookieNames The list of cookie names that should have their values encrypted.
65: * @param string $key The encryption key to use.
66: * @param string $cipherType The cipher type to use. Defaults to 'aes', but can also be 'rijndael' for
67: * backwards compatibility.
68: */
69: public function __construct(array $cookieNames, $key, $cipherType = 'aes')
70: {
71: $this->cookieNames = $cookieNames;
72: $this->key = $key;
73: $this->cipherType = $cipherType;
74: }
75:
76: /**
77: * Apply cookie encryption/decryption.
78: *
79: * @param \Psr\Http\Message\ServerRequestInterface $request The request.
80: * @param \Psr\Http\Message\ResponseInterface $response The response.
81: * @param callable $next The next middleware to call.
82: * @return \Psr\Http\Message\ResponseInterface A response.
83: */
84: public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
85: {
86: if ($request->getCookieParams()) {
87: $request = $this->decodeCookies($request);
88: }
89: $response = $next($request, $response);
90: if ($response->hasHeader('Set-Cookie')) {
91: $response = $this->encodeSetCookieHeader($response);
92: }
93: if ($response instanceof Response) {
94: $response = $this->encodeCookies($response);
95: }
96:
97: return $response;
98: }
99:
100: /**
101: * Fetch the cookie encryption key.
102: *
103: * Part of the CookieCryptTrait implementation.
104: *
105: * @return string
106: */
107: protected function _getCookieEncryptionKey()
108: {
109: return $this->key;
110: }
111:
112: /**
113: * Decode cookies from the request.
114: *
115: * @param \Psr\Http\Message\ServerRequestInterface $request The request to decode cookies from.
116: * @return \Psr\Http\Message\ServerRequestInterface Updated request with decoded cookies.
117: */
118: protected function decodeCookies(ServerRequestInterface $request)
119: {
120: $cookies = $request->getCookieParams();
121: foreach ($this->cookieNames as $name) {
122: if (isset($cookies[$name])) {
123: $cookies[$name] = $this->_decrypt($cookies[$name], $this->cipherType, $this->key);
124: }
125: }
126:
127: return $request->withCookieParams($cookies);
128: }
129:
130: /**
131: * Encode cookies from a response's CookieCollection.
132: *
133: * @param \Cake\Http\Response $response The response to encode cookies in.
134: * @return \Cake\Http\Response Updated response with encoded cookies.
135: */
136: protected function encodeCookies(Response $response)
137: {
138: $cookies = $response->getCookieCollection();
139: foreach ($cookies as $cookie) {
140: if (in_array($cookie->getName(), $this->cookieNames, true)) {
141: $value = $this->_encrypt($cookie->getValue(), $this->cipherType);
142: $response = $response->withCookie($cookie->withValue($value));
143: }
144: }
145:
146: return $response;
147: }
148:
149: /**
150: * Encode cookies from a response's Set-Cookie header
151: *
152: * @param \Psr\Http\Message\ResponseInterface $response The response to encode cookies in.
153: * @return \Psr\Http\Message\ResponseInterface Updated response with encoded cookies.
154: */
155: protected function encodeSetCookieHeader(ResponseInterface $response)
156: {
157: $cookies = CookieCollection::createFromHeader($response->getHeader('Set-Cookie'));
158: $header = [];
159: foreach ($cookies as $cookie) {
160: if (in_array($cookie->getName(), $this->cookieNames, true)) {
161: $value = $this->_encrypt($cookie->getValue(), $this->cipherType);
162: $cookie = $cookie->withValue($value);
163: }
164: $header[] = $cookie->toHeaderValue();
165: }
166:
167: return $response->withHeader('Set-Cookie', $header);
168: }
169: }
170: