Skip to content

Commit e8509e0

Browse files
committed
Merge branch '1.1.x' into master
2 parents 72a5cbf + d429ac8 commit e8509e0

18 files changed

+410
-27
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ script:
4040
- if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION != 7.3 ]]; then vendor/bin/phpunit; fi
4141

4242
- if [[ $PHPCS = 1 ]]; then vendor/bin/phpcs -n -p --extensions=php --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/; fi
43-
- if [[ $PHPSTAN = 1 ]]; then vendor/bin/phpstan analyse -l 4 src; fi
43+
- if [[ $PHPSTAN = 1 ]]; then vendor/bin/phpstan analyse -c phpstan.neon -l 4 src; fi
4444

4545
after_success:
4646
- if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION = 7.3 ]]; then bash <(curl -s https://codecov.io/bash); fi

docs/Checking-Authorization.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ if ($user->can('delete', $article)) {
1515
}
1616
```
1717

18+
If your policies return [Result objects](../Policies.md#policy-result-objects)
19+
be sure to check their status as `can()` returns the result instance:
20+
21+
```php
22+
// Assuming our policy returns a result.
23+
$result = $user->can('delete', $article);
24+
if ($result->getStatus()) {
25+
// Do deletion
26+
}
27+
```
28+
1829
You can also use the `identity` to apply scopes:
1930

2031
```php

docs/Policies.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,31 @@ public function canUpdate(IdentityInterface $user, Article $article)
4545
}
4646
```
4747

48-
Policy methods must return `true` to indicate success. All other values will be
49-
interpreted as failure.
50-
51-
Policy methods will get ``null`` for the ``$user`` parameter when handling
48+
Policy methods will receive ``null`` for the ``$user`` parameter when handling
5249
unauthencticated users. If you want to automatically fail policy methods for
5350
anonymous users you can use the `IdentityInterface` typehint.
5451

52+
### Policy Result Objects
53+
54+
In addition to booleans, policy methods can return ``Result`` objects which
55+
allow more context to be provided on why the policy passed/failed.
56+
57+
```php
58+
use Authorization\Policy\Result;
59+
60+
public function canUpdate(IdentityInterface $user, Article $article)
61+
{
62+
if ($user->id == $article->user_id) {
63+
return new Result(true);
64+
}
65+
// Results let you define a 'reason' for the failure.
66+
return new Result(false, 'not-owner');
67+
}
68+
```
69+
70+
Any return value that is not `true` or a `ResultInterface` object will be
71+
considered a failure.
72+
5573
### Policy Scopes
5674

5775
In addition to policies being able to define pass/fail authorization checks,

phpstan.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
parameters:
2+
ignoreErrors:
3+
- '#Casting to [a-z]+ something that.s already [a-z]+.#'

src/AuthorizationService.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Authorization\Policy\BeforePolicyInterface;
1818
use Authorization\Policy\Exception\MissingMethodException;
1919
use Authorization\Policy\ResolverInterface;
20+
use Authorization\Policy\ResultInterface;
2021
use RuntimeException;
2122

2223
class AuthorizationService implements AuthorizationServiceInterface
@@ -55,17 +56,22 @@ public function can($user, $action, $resource)
5556
if ($policy instanceof BeforePolicyInterface) {
5657
$result = $policy->before($user, $resource, $action);
5758

58-
if (is_bool($result)) {
59+
if ($result instanceof ResultInterface || is_bool($result)) {
5960
return $result;
6061
}
6162
if ($result !== null) {
62-
throw new RuntimeException('Pre-authorization check must return `bool` or `null`.');
63+
$message = sprintf('Pre-authorization check must return `%s`, `bool` or `null`.', ResultInterface::class);
64+
throw new RuntimeException($message);
6365
}
6466
}
6567

6668
$handler = $this->getCanHandler($policy, $action);
6769
$result = $handler($user, $resource);
6870

71+
if ($result instanceof ResultInterface) {
72+
return $result;
73+
}
74+
6975
return $result === true;
7076
}
7177

src/AuthorizationServiceInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ interface AuthorizationServiceInterface
2828
* @param \Authorization\IdentityInterface|null $user The user to check permissions for.
2929
* @param string $action The action/operation being performed.
3030
* @param mixed $resource The resource being operated on.
31-
* @return bool
31+
* @return bool|\Authorization\Policy\ResultInterface
3232
*/
3333
public function can($user, $action, $resource);
3434

src/Controller/Component/AuthorizationComponent.php

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
use Authorization\AuthorizationServiceInterface;
1818
use Authorization\Exception\ForbiddenException;
19-
use Authorization\Exception\MissingIdentityException;
2019
use Authorization\IdentityInterface;
20+
use Authorization\Policy\Result;
21+
use Authorization\Policy\ResultInterface;
2122
use Cake\Controller\Component;
2223
use Cake\Http\ServerRequest;
2324
use InvalidArgumentException;
@@ -65,7 +66,11 @@ public function authorize($resource, $action = null)
6566
$action = $this->getDefaultAction($request);
6667
}
6768

68-
if ($this->can($resource, $action)) {
69+
$result = $this->can($resource, $action);
70+
if (!$result instanceof ResultInterface) {
71+
$result = new Result($result);
72+
}
73+
if ($result->getStatus()) {
6974
return;
7075
}
7176

@@ -76,7 +81,7 @@ public function authorize($resource, $action = null)
7681
} else {
7782
$name = gettype($resource);
7883
}
79-
throw new ForbiddenException([$action, $name]);
84+
throw new ForbiddenException($result, [$action, $name]);
8085
}
8186

8287
/**
@@ -87,7 +92,7 @@ public function authorize($resource, $action = null)
8792
*
8893
* @param object $resource The resource to check authorization on.
8994
* @param string|null $action The action to check authorization for.
90-
* @return bool
95+
* @return bool|\Authorization\Policy\ResultInterface
9196
*/
9297
public function can($resource, $action = null)
9398
{
@@ -97,15 +102,11 @@ public function can($resource, $action = null)
97102
}
98103

99104
$identity = $this->getIdentity($request);
100-
if (empty($identity) && $this->getService($this->request)->can(null, $action, $resource)) {
101-
return true;
102-
}
103-
104-
if (!empty($identity) && $identity->can($action, $resource)) {
105-
return true;
105+
if (empty($identity)) {
106+
return $this->getService($this->request)->can(null, $action, $resource);
106107
}
107108

108-
return false;
109+
return $identity->can($action, $resource);
109110
}
110111

111112
/**

src/Exception/ForbiddenException.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
namespace Authorization\Exception;
1717

18+
use Authorization\Policy\ResultInterface;
19+
1820
class ForbiddenException extends Exception
1921
{
2022
/**
@@ -26,4 +28,42 @@ class ForbiddenException extends Exception
2628
* {@inheritDoc}
2729
*/
2830
protected $_messageTemplate = 'Identity is not authorized to perform `%s` on `%s`.';
31+
32+
/**
33+
* Policy check result.
34+
*
35+
* @var \Authorization\Policy\ResultInterface|null
36+
*/
37+
protected $result;
38+
39+
/**
40+
* Constructor
41+
*
42+
* @param ResultInterface|null $result Policy check result.
43+
* @param string|array $message Either the string of the error message, or an array of attributes
44+
* that are made available in the view, and sprintf()'d into Exception::$_messageTemplate
45+
* @param int|null $code The code of the error, is also the HTTP status code for the error.
46+
* @param \Exception|null $previous the previous exception.
47+
*/
48+
public function __construct($result = null, $message = '', $code = null, $previous = null)
49+
{
50+
if ($result instanceof ResultInterface) {
51+
$this->result = $result;
52+
53+
parent::__construct($message, $code, $previous);
54+
} else {
55+
//shift off first argument for BC
56+
parent::__construct($result, $message, $code);
57+
}
58+
}
59+
60+
/**
61+
* Returns policy check result if passed to the exception.
62+
*
63+
* @return \Authorization\Policy\ResultInterface|null
64+
*/
65+
public function getResult()
66+
{
67+
return $this->result;
68+
}
2969
}

src/IdentityInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ interface IdentityInterface extends ArrayAccess
3030
*
3131
* @param string $action The action/operation being performed.
3232
* @param mixed $resource The resource being operated on.
33-
* @return bool
33+
* @return bool|\Authorization\Policy\ResultInterface
3434
*/
3535
public function can($action, $resource);
3636

src/Middleware/RequestAuthorizationMiddleware.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
use Authorization\AuthorizationServiceInterface;
1818
use Authorization\Exception\ForbiddenException;
19+
use Authorization\Policy\Result;
20+
use Authorization\Policy\ResultInterface;
1921
use Cake\Core\InstanceConfigTrait;
2022
use Psr\Http\Message\ResponseInterface;
2123
use Psr\Http\Message\ServerRequestInterface;
@@ -92,8 +94,12 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res
9294
$service = $this->getServiceFromRequest($request);
9395
$identity = $request->getAttribute($this->getConfig('identityAttribute'));
9496

95-
if (!$service->can($identity, $this->getConfig('method'), $request)) {
96-
throw new ForbiddenException();
97+
$result = $service->can($identity, $this->getConfig('method'), $request);
98+
if (!$result instanceof ResultInterface) {
99+
$result = new Result($result);
100+
}
101+
if (!$result->getStatus()) {
102+
throw new ForbiddenException($result);
97103
}
98104

99105
return $next($request, $response);

0 commit comments

Comments
 (0)