Skip to content

Commit 3f9b514

Browse files
committed
Merge branch '2.next' into 2.x
2 parents 905181a + 75ae0ed commit 3f9b514

File tree

8 files changed

+157
-30
lines changed

8 files changed

+157
-30
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- 2.x
7+
- 2.next
78
pull_request:
89
branches:
910
- '*'

src/Middleware/AuthorizationMiddleware.php

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@
2424
use Authorization\Identity;
2525
use Authorization\IdentityDecorator;
2626
use Authorization\IdentityInterface;
27-
use Authorization\Middleware\UnauthorizedHandler\HandlerFactory;
28-
use Authorization\Middleware\UnauthorizedHandler\HandlerInterface;
27+
use Authorization\Middleware\UnauthorizedHandler\UnauthorizedHandlerTrait;
2928
use Cake\Core\InstanceConfigTrait;
3029
use InvalidArgumentException;
3130
use Psr\Http\Message\ResponseInterface;
@@ -42,6 +41,7 @@
4241
class AuthorizationMiddleware implements MiddlewareInterface
4342
{
4443
use InstanceConfigTrait;
44+
use UnauthorizedHandlerTrait;
4545

4646
/**
4747
* Default config.
@@ -132,37 +132,16 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
132132
throw new AuthorizationRequiredException(['url' => $request->getRequestTarget()]);
133133
}
134134
} catch (Exception $exception) {
135-
$unauthorizedHandler = $this->getHandler();
136-
$response = $unauthorizedHandler->handle(
135+
$response = $this->handleException(
137136
$exception,
138137
$request,
139-
(array)$this->getConfig('unauthorizedHandler')
138+
$this->getConfig('unauthorizedHandler')
140139
);
141140
}
142141

143142
return $response;
144143
}
145144

146-
/**
147-
* Returns unauthorized handler.
148-
*
149-
* @return \Authorization\Middleware\UnauthorizedHandler\HandlerInterface
150-
*/
151-
protected function getHandler(): HandlerInterface
152-
{
153-
$handler = $this->getConfig('unauthorizedHandler');
154-
if (!is_array($handler)) {
155-
$handler = [
156-
'className' => $handler,
157-
];
158-
}
159-
if (!isset($handler['className'])) {
160-
throw new RuntimeException('Missing `className` key from handler config.');
161-
}
162-
163-
return HandlerFactory::create($handler['className']);
164-
}
165-
166145
/**
167146
* Returns AuthorizationServiceInterface instance.
168147
*

src/Middleware/RequestAuthorizationMiddleware.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
namespace Authorization\Middleware;
1818

1919
use Authorization\AuthorizationServiceInterface;
20+
use Authorization\Exception\Exception;
2021
use Authorization\Exception\ForbiddenException;
22+
use Authorization\Middleware\UnauthorizedHandler\UnauthorizedHandlerTrait;
2123
use Cake\Core\InstanceConfigTrait;
2224
use Psr\Http\Message\ResponseInterface;
2325
use Psr\Http\Message\ServerRequestInterface;
@@ -38,6 +40,7 @@
3840
class RequestAuthorizationMiddleware implements MiddlewareInterface
3941
{
4042
use InstanceConfigTrait;
43+
use UnauthorizedHandlerTrait;
4144

4245
/**
4346
* Default Config
@@ -48,6 +51,7 @@ class RequestAuthorizationMiddleware implements MiddlewareInterface
4851
'authorizationAttribute' => 'authorization',
4952
'identityAttribute' => 'identity',
5053
'method' => 'access',
54+
'unauthorizedHandler' => 'Authorization.Exception',
5155
];
5256

5357
/**
@@ -95,8 +99,12 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
9599
$identity = $request->getAttribute($this->getConfig('identityAttribute'));
96100

97101
$result = $service->canResult($identity, $this->getConfig('method'), $request);
98-
if (!$result->getStatus()) {
99-
throw new ForbiddenException($result, [$this->getConfig('method'), $request->getRequestTarget()]);
102+
try {
103+
if (!$result->getStatus()) {
104+
throw new ForbiddenException($result, [$this->getConfig('method'), $request->getRequestTarget()]);
105+
}
106+
} catch (Exception $exception) {
107+
return $this->handleException($exception, $request, $this->getConfig('unauthorizedHandler'));
100108
}
101109

102110
return $handler->handle($request);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6+
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
7+
*
8+
* Licensed under The MIT License
9+
* For full copyright and license information, please see the LICENSE.txt
10+
* Redistributions of files must retain the above copyright notice.
11+
*
12+
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
13+
* @link https://cakephp.org CakePHP(tm) Project
14+
* @since 2.3.0
15+
* @license https://opensource.org/licenses/mit-license.php MIT License
16+
*/
17+
namespace Authorization\Middleware\UnauthorizedHandler;
18+
19+
use Authorization\Exception\Exception;
20+
use Psr\Http\Message\ResponseInterface;
21+
use Psr\Http\Message\ServerRequestInterface;
22+
use RuntimeException;
23+
24+
trait UnauthorizedHandlerTrait
25+
{
26+
/**
27+
* Handle exception.
28+
*
29+
* @param \Authorization\Exception\Exception $exception Exception to handle.
30+
* @param \Psr\Http\Message\ServerRequestInterface $request Request instance.
31+
* @param array|string $handler Handler config.
32+
* @return \Psr\Http\Message\ResponseInterface
33+
*/
34+
protected function handleException(
35+
Exception $exception,
36+
ServerRequestInterface $request,
37+
$handler
38+
): ResponseInterface {
39+
if (is_string($handler)) {
40+
$handler = [
41+
'className' => $handler,
42+
];
43+
}
44+
if (!isset($handler['className'])) {
45+
throw new RuntimeException('Missing `className` key from handler config.');
46+
}
47+
48+
$unauthorizedHandler = HandlerFactory::create($handler['className']);
49+
50+
return $unauthorizedHandler->handle(
51+
$exception,
52+
$request,
53+
$handler
54+
);
55+
}
56+
}

src/Policy/Exception/MissingPolicyException.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
namespace Authorization\Policy\Exception;
1818

1919
use Authorization\Exception\Exception;
20+
use Cake\Datasource\RepositoryInterface;
2021
use Throwable;
2122

2223
class MissingPolicyException extends Exception
@@ -37,7 +38,20 @@ class MissingPolicyException extends Exception
3738
public function __construct($resource, ?int $code = null, ?Throwable $previous = null)
3839
{
3940
if (is_object($resource)) {
40-
$resource = [get_class($resource)];
41+
$resourceClass = get_class($resource);
42+
if (
43+
method_exists($resource, 'getRepository') &&
44+
$resource->getRepository() &&
45+
$resource->getRepository() instanceof RepositoryInterface
46+
) {
47+
$repositoryClass = get_class($resource->getRepository());
48+
$resource = sprintf($this->_messageTemplate, $resourceClass);
49+
$queryMessage = ' This resource looks like a `Query`. If you are using `OrmResolver`, ' .
50+
'you should create a new policy class for your `%s` class in `src/Policy/`.';
51+
$resource .= sprintf($queryMessage, $repositoryClass);
52+
} else {
53+
$resource = [$resourceClass];
54+
}
4155
}
4256

4357
parent::__construct($resource, $code, $previous);

tests/TestCase/Middleware/RequestAuthorizationMiddlewareTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,4 +121,29 @@ public function testInvokeServiceWithResult()
121121
throw $e;
122122
}
123123
}
124+
125+
public function testUnauthorizedHandlerSuppress()
126+
{
127+
$request = (new ServerRequest([
128+
'url' => '/articles/index',
129+
]))
130+
->withParam('action', 'add')
131+
->withParam('controller', 'Articles');
132+
133+
$handler = new TestRequestHandler();
134+
135+
$resolver = new MapResolver([
136+
ServerRequest::class => new RequestPolicy(),
137+
]);
138+
139+
$authService = new AuthorizationService($resolver);
140+
$request = $request->withAttribute('authorization', $authService);
141+
142+
$middleware = new RequestAuthorizationMiddleware([
143+
'unauthorizedHandler' => 'Suppress',
144+
]);
145+
$result = $middleware->process($request, $handler);
146+
147+
$this->assertSame(200, $result->getStatusCode());
148+
}
124149
}

tests/TestCase/Middleware/UnauthorizedHandler/CakeRedirectHandlerTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,14 @@ public function setUp(): void
2929
{
3030
parent::setUp();
3131

32-
Router::reload();
3332
Router::fullBaseUrl('http://localhost');
3433
$builder = Router::createRouteBuilder('/');
3534
$builder->connect(
3635
'/login',
3736
['controller' => 'Users', 'action' => 'login'],
3837
['_name' => 'login']
3938
);
40-
$builder->connect('/:controller/:action');
39+
$builder->connect('/{controller}/{action}');
4140
}
4241

4342
public function testHandleRedirectionDefault()
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
/**
5+
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6+
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
7+
*
8+
* Licensed under The MIT License
9+
* For full copyright and license information, please see the LICENSE.txt
10+
* Redistributions of files must retain the above copyright notice.
11+
*
12+
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
13+
* @link https://cakephp.org CakePHP(tm) Project
14+
* @since 1.0.0
15+
* @license https://opensource.org/licenses/mit-license.php MIT License
16+
*/
17+
namespace Authorization\Test\TestCase\Policy\Exception;
18+
19+
use Authorization\Policy\Exception\MissingPolicyException;
20+
use Cake\Datasource\QueryInterface;
21+
use Cake\TestSuite\TestCase;
22+
use TestApp\Model\Table\ArticlesTable;
23+
24+
class MissingPolicyExceptionTest extends TestCase
25+
{
26+
public function testConstructQueryInstance(): void
27+
{
28+
$articles = new ArticlesTable();
29+
$query = $this->createMock(QueryInterface::class);
30+
$query->method('getRepository')
31+
->willReturn($articles);
32+
$missingPolicyException = new MissingPolicyException($query);
33+
$needle = 'This resource looks like a `Query`. If you are using ' .
34+
'`OrmResolver`, you should create a new policy class for ' .
35+
'your `TestApp\Model\Table\ArticlesTable` class in `src/Policy/`.';
36+
$this->assertTextContains($needle, $missingPolicyException->getMessage());
37+
}
38+
39+
public function testConstructAnotherInstance(): void
40+
{
41+
$missingPolicyException = new MissingPolicyException(new \stdClass());
42+
$needle = 'Policy for `stdClass` has not been defined.';
43+
$this->assertSame($needle, $missingPolicyException->getMessage());
44+
}
45+
}

0 commit comments

Comments
 (0)