Skip to content

Commit

Permalink
Merge pull request #271 from cakephp/3.x
Browse files Browse the repository at this point in the history
merge 3.x -> 3.next
  • Loading branch information
markstory authored Dec 26, 2023
2 parents 1dbd98a + abd8f07 commit 3850b02
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open for 120 days with no activity. Remove the `stale` label or comment or this will be closed in 15 days'
Expand Down
48 changes: 24 additions & 24 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
{
"name": "cakephp/authorization",
"description": "Authorization abstraction layer plugin for CakePHP",
"license": "MIT",
"type": "cakephp-plugin",
"keywords": [
"auth",
"authorization",
"access",
"cakephp"
],
"type": "cakephp-plugin",
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/authorization/graphs/contributors"
}
],
"support": {
"issues": "https://github.com/cakephp/authorization/issues",
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"source": "https://github.com/cakephp/authorization",
"docs": "https://cakephp.org/authorization/2/en/"
},
"require": {
"php": ">=8.1",
"cakephp/http": "^5.0",
Expand All @@ -27,7 +41,6 @@
"cakephp/http": "To use \"RequestPolicyInterface\" (Not needed separately if using full CakePHP framework).",
"cakephp/orm": "To use \"OrmResolver\" (Not needed separately if using full CakePHP framework)."
},
"license": "MIT",
"autoload": {
"psr-4": {
"Authorization\\": "src/"
Expand All @@ -41,44 +54,31 @@
"TestPlugin\\": "tests/test_app/Plugin/TestPlugin/src/"
}
},
"authors": [
{
"name": "CakePHP Community",
"homepage": "https://github.com/cakephp/authorization/graphs/contributors"
}
],
"support": {
"issues": "https://github.com/cakephp/authorization/issues",
"forum": "https://stackoverflow.com/tags/cakephp",
"irc": "irc://irc.freenode.org/cakephp",
"source": "https://github.com/cakephp/authorization",
"docs": "https://cakephp.org/authorization/2/en/"
"config": {
"allow-plugins": {
"cakephp/plugin-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
},
"scripts": {
"check": [
"@cs-check",
"@stan",
"@test"
],
"cs-check": "phpcs --colors -p src/ tests/",
"cs-fix": "phpcbf --colors -p src/ tests/",
"phpstan": "tools/phpstan analyse",
"psalm": "tools/psalm --show-info=false",
"psalm-baseline": "tools/psalm --set-baseline=psalm-baseline.xml",
"stan": [
"@phpstan",
"@psalm"
],
"stan-baseline": "tools/phpstan --generate-baseline",
"psalm-baseline": "tools/psalm --set-baseline=psalm-baseline.xml",
"stan-setup": "phive install",
"cs-check": "phpcs --colors -p src/ tests/",
"cs-fix": "phpcbf --colors -p src/ tests/",
"test": "phpunit",
"test-coverage": "phpunit --coverage-clover=clover.xml"
},
"config": {
"sort-packages": true,
"allow-plugins": {
"cakephp/plugin-installer": true,
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
15 changes: 6 additions & 9 deletions docs.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Generate the HTML output.
FROM ghcr.io/cakephp/docs-builder as builder

COPY docs /data/docs

ENV LANGS="en es fr ja"

WORKDIR /data/docs-builder

COPY docs /data/docs

# Build docs with sphinx
RUN cd /data/docs-builder && \
make website LANGS="$LANGS" SOURCE=/data/docs DEST=/data/website
RUN make website LANGS="$LANGS" SOURCE=/data/docs DEST=/data/website

# Build a small nginx container with just the static site in it.
FROM ghcr.io/cakephp/docs-builder:runtime as runtime
Expand All @@ -18,11 +19,7 @@ ENV SEARCH_SOURCE="/usr/share/nginx/html"
ENV SEARCH_URL_PREFIX="/authorization/3"

COPY --from=builder /data/docs /data/docs
COPY --from=builder /data/website /data/website
COPY --from=builder /data/website/html/ /usr/share/nginx/html/
COPY --from=builder /data/docs-builder/nginx.conf /etc/nginx/conf.d/default.conf

# Move files into final location
RUN cp -R /data/website/html/* /usr/share/nginx/html \
&& rm -rf /data/website/

RUN ln -s /usr/share/nginx/html /usr/share/nginx/html/2.x
26 changes: 26 additions & 0 deletions docs/en/policies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,32 @@ Before hooks are expected to return one of three values:
- ``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
-----------------

Expand Down
5 changes: 3 additions & 2 deletions docs/en/request-authorization-middleware.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and add::

use Authorization\Policy\RequestPolicyInterface;
use Cake\Http\ServerRequest;
use Authorization\Policy\ResultInterface

class RequestPolicy implements RequestPolicyInterface
{
Expand All @@ -31,9 +32,9 @@ and add::
*
* @param \Authorization\IdentityInterface|null $identity Identity
* @param \Cake\Http\ServerRequest $request Server Request
* @return bool
* @return \Authorization\Policy\ResultInterface|bool
*/
public function canAccess($identity, ServerRequest $request)
public function canAccess($identity, ServerRequest $request): bool|ResultInterface
{
if ($request->getParam('controller') === 'Articles'
&& $request->getParam('action') === 'index'
Expand Down
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 @@ -115,6 +116,15 @@ public function applyScope(?IdentityInterface $user, string $action, mixed $reso
{
$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, ...$optionalArgs);
Expand Down
2 changes: 1 addition & 1 deletion src/Identity.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function __construct(AuthorizationServiceInterface $service, AuthenIdenti
/**
* Get the primary key/id field for the identity.
*
* @return array|string|int|null
* @return array<array-key, mixed>|string|int|null
*/
public function getIdentifier(): string|int|array|null
{
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, mixed $resource, string $action): mixed;
}
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\OrmResolver;
Expand Down Expand Up @@ -470,6 +471,70 @@ public function testBeforeResultFalse()
$this->assertFalse($result);
}

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 3850b02

Please sign in to comment.