Skip to content

Commit 2cc9135

Browse files
committed
Allow callable for requireAuthorizationCheck config option
This allows applications to conditionally skip the authorization check based on the request. This is useful when integrating plugins that have their own admin panels and manage authorization independently (e.g., queue management dashboards). The callable receives the ServerRequestInterface and should return a boolean - true to require authorization check (default), false to skip. Example use case: ```php $middlewareQueue->add(new AuthorizationMiddleware($this, [ 'requireAuthorizationCheck' => function ($request) { $path = $request->getUri()->getPath(); if (str_contains($path, '/admin/queue')) { return false; } return true; } ])); ```
1 parent 6540b9a commit 2cc9135

File tree

3 files changed

+92
-2
lines changed

3 files changed

+92
-2
lines changed

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
@@ -28,6 +28,7 @@
2828
use Authorization\IdentityInterface;
2929
use Authorization\Middleware\UnauthorizedHandler\UnauthorizedHandlerTrait;
3030
use Cake\Core\ContainerApplicationInterface;
31+
use Closure;
3132
use Cake\Core\ContainerInterface;
3233
use Cake\Core\InstanceConfigTrait;
3334
use Psr\Http\Message\ResponseInterface;
@@ -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)