Skip to content

Commit

Permalink
Merge pull request #264 from jamisonbryant/feature/before-scope-inter…
Browse files Browse the repository at this point in the history
…face-2.x

Feature: BeforeScopeInterface (2.next)
  • Loading branch information
markstory authored Nov 10, 2023
2 parents d3b08f6 + cfe53ac commit 94fadea
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 1 deletion.
27 changes: 26 additions & 1 deletion docs/en/policies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,32 @@ Before hooks are expected to return one of three values:
- ``false`` The user is not allowed to proceed with the action.
- ``null`` The before hook did not make a decision, and the authorization method
will be invoked.


Scope Pre-conditions
====================

Like policies, scopes can also define pre-conditions. These are useful when you
want to apply common conditions to all scopes in a policy. To use pre-conditions
on scopes you need to implement the ``BeforeScopeInterface`` in your scope policy::

namespace App\Policy;

use Authorization\Policy\BeforeScopeInterface;

class ArticlesTablePolicy implements BeforeScopeInterface
{
public function beforeScope($user, $query, $action)
{
if ($user->getOriginalData()->is_trial_user) {
return $query->where(['Articles.is_paid_only' => false]);
}
// fall through
}
}

Before scope hooks are expected to return the modified resource object, or if
``null`` is returned then the scope method will be invoked as normal.

Applying Policies
-----------------
See :ref:`applying-policy-scopes` for how to apply policies in your controller actions.
10 changes: 10 additions & 0 deletions src/AuthorizationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

use Authorization\Exception\Exception;
use Authorization\Policy\BeforePolicyInterface;
use Authorization\Policy\BeforeScopeInterface;
use Authorization\Policy\Exception\MissingMethodException;
use Authorization\Policy\ResolverInterface;
use Authorization\Policy\Result;
Expand Down Expand Up @@ -120,6 +121,15 @@ public function applyScope(?IdentityInterface $user, string $action, $resource)
{
$this->authorizationChecked = true;
$policy = $this->resolver->getPolicy($resource);

if ($policy instanceof BeforeScopeInterface) {
$result = $policy->beforeScope($user, $resource, $action);

if ($result !== null) {
return $result;
}
}

$handler = $this->getScopeHandler($policy, $action);

return $handler($user, $resource);
Expand Down
39 changes: 39 additions & 0 deletions src/Policy/BeforeScopeInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);

/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @since 2.4.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Authorization\Policy;

use Authorization\IdentityInterface;

/**
* This interface should be implemented if a policy class needs to perform a
* pre-authorization check before the scope is applied to the resource.
*/
interface BeforeScopeInterface
{
/**
* Defines a pre-scope check.
*
* If a non-null value is returned, the scope application will be skipped and the un-scoped resource
* will be returned. In case of `null`, the scope will be applied.
*
* @param \Authorization\IdentityInterface|null $identity Identity object.
* @param mixed $resource The resource being operated on.
* @param string $action The action/operation being performed.
* @return mixed
*/
public function beforeScope(?IdentityInterface $identity, $resource, string $action);
}
65 changes: 65 additions & 0 deletions tests/TestCase/AuthorizationServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Authorization\AuthorizationService;
use Authorization\IdentityDecorator;
use Authorization\Policy\BeforePolicyInterface;
use Authorization\Policy\BeforeScopeInterface;
use Authorization\Policy\Exception\MissingMethodException;
use Authorization\Policy\MapResolver;
use Authorization\Policy\Result;
Expand Down Expand Up @@ -375,6 +376,70 @@ public function testBeforeOther()
$service->can($user, 'add', $entity);
}

public function testBeforeScopeNonNull()
{
$entity = new Article();

$policy = $this->getMockBuilder(BeforeScopeInterface::class)
->onlyMethods(['beforeScope'])
->addMethods(['scopeIndex'])
->getMock();

$policy->expects($this->once())
->method('beforeScope')
->with($this->isInstanceOf(IdentityDecorator::class), $entity, 'index')
->willReturn('foo');

$policy->expects($this->never())
->method('scopeIndex');

$resolver = new MapResolver([
Article::class => $policy,
]);

$service = new AuthorizationService($resolver);

$user = new IdentityDecorator($service, [
'role' => 'admin',
]);

$result = $service->applyScope($user, 'index', $entity);
$this->assertEquals('foo', $result);
}

public function testBeforeScopeNull()
{
$entity = new Article();

$policy = $this->getMockBuilder(BeforeScopeInterface::class)
->onlyMethods(['beforeScope'])
->addMethods(['scopeIndex'])
->getMock();

$policy->expects($this->once())
->method('beforeScope')
->with($this->isInstanceOf(IdentityDecorator::class), $entity, 'index')
->willReturn(null);

$policy->expects($this->once())
->method('scopeIndex')
->with($this->isInstanceOf(IdentityDecorator::class), $entity)
->willReturn('bar');

$resolver = new MapResolver([
Article::class => $policy,
]);

$service = new AuthorizationService($resolver);

$user = new IdentityDecorator($service, [
'role' => 'admin',
]);

$result = $service->applyScope($user, 'index', $entity);
$this->assertEquals('bar', $result);
}

public function testMissingMethod()
{
$entity = new Article();
Expand Down

0 comments on commit 94fadea

Please sign in to comment.