1717use Config \App ;
1818use Config \Cookie as CookieConfig ;
1919use Config \Security as SecurityConfig ;
20+ use Config \Services ;
2021
2122/**
2223 * Class Security
@@ -77,8 +78,6 @@ class Security implements SecurityInterface
7778 * Defaults to two hours (in seconds).
7879 *
7980 * @var int
80- *
81- * @deprecated
8281 */
8382 protected $ expires = 7200 ;
8483
@@ -117,6 +116,18 @@ class Security implements SecurityInterface
117116 */
118117 protected $ samesite = Cookie::SAMESITE_LAX ;
119118
119+ /**
120+ * @var RequestInterface
121+ */
122+ private $ request ;
123+
124+ /**
125+ * CSRF Cookie Name without Prefix
126+ *
127+ * @var string
128+ */
129+ private $ rawCookieName ;
130+
120131 /**
121132 * Constructor.
122133 *
@@ -125,27 +136,46 @@ class Security implements SecurityInterface
125136 */
126137 public function __construct (App $ config )
127138 {
128- /** @var SecurityConfig $security */
139+ /** @var SecurityConfig|null $security */
129140 $ security = config ('Security ' );
130141
131142 // Store CSRF-related configurations
132- $ this ->tokenName = $ security ->tokenName ?? $ config ->CSRFTokenName ?? $ this ->tokenName ;
133- $ this ->headerName = $ security ->headerName ?? $ config ->CSRFHeaderName ?? $ this ->headerName ;
134- $ this ->regenerate = $ security ->regenerate ?? $ config ->CSRFRegenerate ?? $ this ->regenerate ;
135- $ rawCookieName = $ security ->cookieName ?? $ config ->CSRFCookieName ?? $ this ->cookieName ;
143+ if ($ security instanceof SecurityConfig) {
144+ $ this ->tokenName = $ security ->tokenName ?? $ this ->tokenName ;
145+ $ this ->headerName = $ security ->headerName ?? $ this ->headerName ;
146+ $ this ->regenerate = $ security ->regenerate ?? $ this ->regenerate ;
147+ $ this ->rawCookieName = $ security ->cookieName ?? $ this ->rawCookieName ;
148+ $ this ->expires = $ security ->expires ?? $ this ->expires ;
149+ } else {
150+ // `Config/Security.php` is absence
151+ $ this ->tokenName = $ config ->CSRFTokenName ?? $ this ->tokenName ;
152+ $ this ->headerName = $ config ->CSRFHeaderName ?? $ this ->headerName ;
153+ $ this ->regenerate = $ config ->CSRFRegenerate ?? $ this ->regenerate ;
154+ $ this ->rawCookieName = $ config ->CSRFCookieName ?? $ this ->rawCookieName ;
155+ $ this ->expires = $ config ->CSRFExpire ?? $ this ->expires ;
156+ }
136157
137- /** @var CookieConfig $cookie */
138- $ cookie = config ('Cookie ' );
158+ $ this ->configureCookie ($ config );
139159
140- $ cookiePrefix = $ cookie ->prefix ?? $ config ->cookiePrefix ;
141- $ this ->cookieName = $ cookiePrefix . $ rawCookieName ;
160+ $ this ->request = Services::request ();
142161
143- $ expires = $ security ->expires ?? $ config ->CSRFExpire ?? 7200 ;
162+ $ this ->generateHash ();
163+ }
164+
165+ private function configureCookie (App $ config ): void
166+ {
167+ /** @var CookieConfig|null $cookie */
168+ $ cookie = config ('Cookie ' );
144169
145- Cookie::setDefaults ($ cookie );
146- $ this ->cookie = new Cookie ($ rawCookieName , $ this ->generateHash (), [
147- 'expires ' => $ expires === 0 ? 0 : time () + $ expires ,
148- ]);
170+ if ($ cookie instanceof CookieConfig) {
171+ $ cookiePrefix = $ cookie ->prefix ;
172+ $ this ->cookieName = $ cookiePrefix . $ this ->rawCookieName ;
173+ Cookie::setDefaults ($ cookie );
174+ } else {
175+ // `Config/Cookie.php` is absence
176+ $ cookiePrefix = $ config ->cookiePrefix ;
177+ $ this ->cookieName = $ cookiePrefix . $ this ->rawCookieName ;
178+ }
149179 }
150180
151181 /**
@@ -202,26 +232,15 @@ public function verify(RequestInterface $request)
202232 return $ this ->sendCookie ($ request );
203233 }
204234
205- // Does the token exist in POST, HEADER or optionally php:://input - json data.
206- if ($ request ->hasHeader ($ this ->headerName ) && ! empty ($ request ->getHeader ($ this ->headerName )->getValue ())) {
207- $ tokenName = $ request ->getHeader ($ this ->headerName )->getValue ();
208- } else {
209- $ json = json_decode ($ request ->getBody ());
210-
211- if (! empty ($ request ->getBody ()) && ! empty ($ json ) && json_last_error () === JSON_ERROR_NONE ) {
212- $ tokenName = $ json ->{$ this ->tokenName } ?? null ;
213- } else {
214- $ tokenName = null ;
215- }
216- }
217-
218- $ token = $ _POST [$ this ->tokenName ] ?? $ tokenName ;
235+ $ token = $ this ->getPostedToken ($ request );
219236
220237 // Does the tokens exist in both the POST/POSTed JSON and COOKIE arrays and match?
221- if (! isset ($ token , $ _COOKIE [ $ this ->cookieName ] ) || ! hash_equals ($ token , $ _COOKIE [ $ this ->cookieName ] )) {
238+ if (! isset ($ token , $ this ->hash ) || ! hash_equals ($ this ->hash , $ token )) {
222239 throw SecurityException::forDisallowedAction ();
223240 }
224241
242+ $ json = json_decode ($ request ->getBody ());
243+
225244 if (isset ($ _POST [$ this ->tokenName ])) {
226245 // We kill this since we're done and we don't want to pollute the POST array.
227246 unset($ _POST [$ this ->tokenName ]);
@@ -237,14 +256,31 @@ public function verify(RequestInterface $request)
237256 unset($ _COOKIE [$ this ->cookieName ]);
238257 }
239258
240- $ this ->cookie = $ this ->cookie ->withValue ($ this ->generateHash ());
241- $ this ->sendCookie ($ request );
259+ $ this ->generateHash ();
242260
243261 log_message ('info ' , 'CSRF token verified. ' );
244262
245263 return $ this ;
246264 }
247265
266+ private function getPostedToken (RequestInterface $ request ): ?string
267+ {
268+ // Does the token exist in POST, HEADER or optionally php:://input - json data.
269+ if ($ request ->hasHeader ($ this ->headerName ) && ! empty ($ request ->header ($ this ->headerName )->getValue ())) {
270+ $ tokenName = $ request ->header ($ this ->headerName )->getValue ();
271+ } else {
272+ $ json = json_decode ($ request ->getBody ());
273+
274+ if (! empty ($ request ->getBody ()) && ! empty ($ json ) && json_last_error () === JSON_ERROR_NONE ) {
275+ $ tokenName = $ json ->{$ this ->tokenName } ?? null ;
276+ } else {
277+ $ tokenName = null ;
278+ }
279+ }
280+
281+ return $ request ->getPost ($ this ->tokenName ) ?? $ tokenName ;
282+ }
283+
248284 /**
249285 * Returns the CSRF Hash.
250286 */
@@ -373,19 +409,37 @@ protected function generateHash(): string
373409 // We don't necessarily want to regenerate it with
374410 // each page load since a page could contain embedded
375411 // sub-pages causing this feature to fail
376- if (isset ($ _COOKIE [$ this ->cookieName ])
377- && is_string ($ _COOKIE [$ this ->cookieName ])
378- && preg_match ('#^[0-9a-f]{32}$#iS ' , $ _COOKIE [$ this ->cookieName ]) === 1
379- ) {
412+ if ($ this ->isHashInCookie ()) {
380413 return $ this ->hash = $ _COOKIE [$ this ->cookieName ];
381414 }
382415
383416 $ this ->hash = bin2hex (random_bytes (16 ));
417+
418+ $ this ->saveHashInCookie ();
384419 }
385420
386421 return $ this ->hash ;
387422 }
388423
424+ private function isHashInCookie (): bool
425+ {
426+ return isset ($ _COOKIE [$ this ->cookieName ])
427+ && is_string ($ _COOKIE [$ this ->cookieName ])
428+ && preg_match ('#^[0-9a-f]{32}$#iS ' , $ _COOKIE [$ this ->cookieName ]) === 1 ;
429+ }
430+
431+ private function saveHashInCookie ()
432+ {
433+ $ this ->cookie = new Cookie (
434+ $ this ->rawCookieName ,
435+ $ this ->hash ,
436+ [
437+ 'expires ' => $ this ->expires === 0 ? 0 : time () + $ this ->expires ,
438+ ]
439+ );
440+ $ this ->sendCookie ($ this ->request );
441+ }
442+
389443 /**
390444 * CSRF Send Cookie
391445 *
0 commit comments