Skip to content

Commit fbcd4dd

Browse files
committed
Allow Closure for requireAuthorizationCheck config option
1 parent 6540b9a commit fbcd4dd

File tree

4 files changed

+93
-3
lines changed

4 files changed

+93
-3
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"cakephp/authentication": "^3.0 || ^4.0",
3535
"cakephp/bake": "^3.2",
3636
"cakephp/cakephp": "^5.1",
37-
"cakephp/cakephp-codesniffer": "^5.1",
37+
"cakephp/cakephp-codesniffer": "^5.3",
3838
"phpunit/phpunit": "^10.5.58 || ^11.5.3 || ^12.4 || ^13.0"
3939
},
4040
"suggest": {

docs/en/middleware.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,26 @@ option::
176176
'requireAuthorizationCheck' => false
177177
]));
178178

179+
You can also use a Closure to conditionally skip the authorization check based
180+
on the request. This is useful when you need to bypass authorization for specific
181+
routes (e.g., plugin admin panels that manage their own authorization)::
182+
183+
$middlewareQueue->add(new AuthorizationMiddleware($this, [
184+
'requireAuthorizationCheck' => function ($request) {
185+
// Skip authorization check for specific routes
186+
$path = $request->getUri()->getPath();
187+
if (str_contains($path, '/admin/queue')) {
188+
return false;
189+
}
190+
191+
return true;
192+
}
193+
]));
194+
195+
The Closure receives the ``ServerRequestInterface`` and should return a boolean.
196+
Return ``true`` to require authorization check (default behavior), or ``false``
197+
to skip the check for that request.
198+
179199
Handling Unauthorized Requests
180200
------------------------------
181201

src/Middleware/AuthorizationMiddleware.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
use Cake\Core\ContainerApplicationInterface;
3131
use Cake\Core\ContainerInterface;
3232
use Cake\Core\InstanceConfigTrait;
33+
use Closure;
3334
use Psr\Http\Message\ResponseInterface;
3435
use Psr\Http\Message\ServerRequestInterface;
3536
use Psr\Http\Server\MiddlewareInterface;
@@ -56,7 +57,8 @@ class AuthorizationMiddleware implements MiddlewareInterface
5657
* - `requireAuthorizationCheck` When true the middleware will raise an exception
5758
* if no authorization checks were done. This aids in ensuring that all actions
5859
* check authorization. It is intended as a development aid and not to be relied upon
59-
* in production. Defaults to `true`.
60+
* in production. Defaults to `true`. Can also be a Closure that receives the
61+
* ServerRequestInterface and returns a boolean, allowing route-based control.
6062
*
6163
* @var array<string, mixed>
6264
*/
@@ -135,7 +137,11 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
135137
try {
136138
$response = $handler->handle($request);
137139

138-
if ($this->getConfig('requireAuthorizationCheck') && !$service->authorizationChecked()) {
140+
$requireCheck = $this->getConfig('requireAuthorizationCheck');
141+
if ($requireCheck instanceof Closure) {
142+
$requireCheck = $requireCheck($request);
143+
}
144+
if ($requireCheck && !$service->authorizationChecked()) {
139145
throw new AuthorizationRequiredException(['url' => $request->getRequestTarget()]);
140146
}
141147
} catch (Exception $exception) {

tests/TestCase/Middleware/AuthorizationMiddlewareTest.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,68 @@ public function testMiddlewareInjectsServiceIntoDICViaCustomContainerInstance()
316316

317317
$this->assertEquals($service, $container->get(AuthorizationService::class));
318318
}
319+
320+
public function testRequireAuthorizationCheckCallableReturnsTrue(): void
321+
{
322+
$this->expectException(AuthorizationRequiredException::class);
323+
324+
$service = $this->createMock(AuthorizationServiceInterface::class);
325+
$service->expects($this->once())
326+
->method('authorizationChecked')
327+
->willReturn(false);
328+
329+
$request = (new ServerRequest())->withAttribute('identity', ['id' => 1]);
330+
$handler = new TestRequestHandler();
331+
332+
$middleware = new AuthorizationMiddleware($service, [
333+
'requireAuthorizationCheck' => function (ServerRequestInterface $request): bool {
334+
return true;
335+
},
336+
'identityDecorator' => IdentityDecorator::class,
337+
]);
338+
$middleware->process($request, $handler);
339+
}
340+
341+
public function testRequireAuthorizationCheckCallableReturnsFalse(): void
342+
{
343+
$service = $this->createMock(AuthorizationServiceInterface::class);
344+
$service->expects($this->never())
345+
->method('authorizationChecked');
346+
347+
$request = (new ServerRequest())->withAttribute('identity', ['id' => 1]);
348+
$handler = new TestRequestHandler();
349+
350+
$middleware = new AuthorizationMiddleware($service, [
351+
'requireAuthorizationCheck' => function (ServerRequestInterface $request): bool {
352+
return false;
353+
},
354+
'identityDecorator' => IdentityDecorator::class,
355+
]);
356+
$result = $middleware->process($request, $handler);
357+
358+
$this->assertInstanceOf(ResponseInterface::class, $result);
359+
}
360+
361+
public function testRequireAuthorizationCheckCallableWithRouteBasedLogic(): void
362+
{
363+
$service = $this->createMock(AuthorizationServiceInterface::class);
364+
$service->expects($this->never())
365+
->method('authorizationChecked');
366+
367+
$request = ServerRequestFactory::fromGlobals(['REQUEST_URI' => '/admin/queue']);
368+
$handler = new TestRequestHandler();
369+
370+
$middleware = new AuthorizationMiddleware($service, [
371+
'requireAuthorizationCheck' => function (ServerRequestInterface $request): bool {
372+
// Skip authorization check for admin/queue routes
373+
$path = $request->getUri()->getPath();
374+
375+
return !str_contains($path, '/admin/queue');
376+
},
377+
'identityDecorator' => IdentityDecorator::class,
378+
]);
379+
$result = $middleware->process($request, $handler);
380+
381+
$this->assertInstanceOf(ResponseInterface::class, $result);
382+
}
319383
}

0 commit comments

Comments
 (0)