Skip to content

Commit

Permalink
Merge pull request #286 from cakephp/3.next
Browse files Browse the repository at this point in the history
3.2.0
  • Loading branch information
markstory authored Jul 25, 2024
2 parents 819b496 + 5c41fc4 commit 9cc8b11
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 6 deletions.
22 changes: 21 additions & 1 deletion src/Middleware/AuthorizationMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

use ArrayAccess;
use Authentication\IdentityInterface as AuthenIdentityInterface;
use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Exception\AuthorizationRequiredException;
Expand All @@ -26,6 +27,8 @@
use Authorization\IdentityDecorator;
use Authorization\IdentityInterface;
use Authorization\Middleware\UnauthorizedHandler\UnauthorizedHandlerTrait;
use Cake\Core\ContainerApplicationInterface;
use Cake\Core\ContainerInterface;
use Cake\Core\InstanceConfigTrait;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
Expand Down Expand Up @@ -71,16 +74,25 @@ class AuthorizationMiddleware implements MiddlewareInterface
*/
protected AuthorizationServiceInterface|AuthorizationServiceProviderInterface $subject;

/**
* The container instance from the application
*
* @var \Cake\Core\ContainerInterface|null
*/
protected ?ContainerInterface $container = null;

/**
* Constructor.
*
* @param \Authorization\AuthorizationServiceInterface|\Authorization\AuthorizationServiceProviderInterface $subject Authorization service or provider instance.
* @param array $config Config array.
* @param \Cake\Core\ContainerInterface|null $container The container instance from the application
* @throws \InvalidArgumentException
*/
public function __construct(
AuthorizationServiceInterface|AuthorizationServiceProviderInterface $subject,
array $config = []
array $config = [],
?ContainerInterface $container = null
) {
if ($this->_defaultConfig['identityDecorator'] === null) {
$this->_defaultConfig['identityDecorator'] = interface_exists(AuthenIdentityInterface::class)
Expand All @@ -89,6 +101,7 @@ public function __construct(
}

$this->subject = $subject;
$this->container = $container;
$this->setConfig($config);
}

Expand All @@ -104,6 +117,13 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
$service = $this->getAuthorizationService($request);
$request = $request->withAttribute('authorization', $service);

if ($this->subject instanceof ContainerApplicationInterface) {
$container = $this->subject->getContainer();
$container->add(AuthorizationService::class, $service);
} elseif ($this->container) {
$this->container->add(AuthorizationService::class, $service);
}

$attribute = $this->getConfig('identityAttribute');
$identity = $request->getAttribute($attribute);

Expand Down
16 changes: 15 additions & 1 deletion src/Policy/MapResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
namespace Authorization\Policy;

use Authorization\Policy\Exception\MissingPolicyException;
use Cake\Core\ContainerInterface;
use InvalidArgumentException;

/**
Expand All @@ -32,6 +33,13 @@ class MapResolver implements ResolverInterface
*/
protected array $map = [];

/**
* The DIC instance from the application
*
* @var \Cake\Core\ContainerInterface|null
*/
protected ?ContainerInterface $container;

/**
* Constructor.
*
Expand All @@ -45,9 +53,11 @@ class MapResolver implements ResolverInterface
* ```
*
* @param array $map Resource class name to policy map.
* @param \Cake\Core\ContainerInterface|null $container The DIC instance from the application
*/
public function __construct(array $map = [])
public function __construct(array $map = [], ?ContainerInterface $container = null)
{
$this->container = $container;
foreach ($map as $resourceClass => $policy) {
$this->map($resourceClass, $policy);
}
Expand Down Expand Up @@ -107,6 +117,10 @@ public function getPolicy($resource): mixed
return $policy;
}

if ($this->container && $this->container->has($policy)) {
return $this->container->get($policy);
}

return new $policy();
}
}
25 changes: 22 additions & 3 deletions src/Policy/OrmResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

use Authorization\Policy\Exception\MissingPolicyException;
use Cake\Core\App;
use Cake\Core\ContainerInterface;
use Cake\Datasource\EntityInterface;
use Cake\Datasource\QueryInterface;
use Cake\Datasource\RepositoryInterface;
Expand All @@ -43,16 +44,28 @@ class OrmResolver implements ResolverInterface
*/
protected array $overrides = [];

/**
* The DIC instance from the application
*
* @var \Cake\Core\ContainerInterface|null
*/
protected ?ContainerInterface $container;

/**
* Constructor
*
* @param string $appNamespace The application namespace
* @param array<string, string> $overrides A list of plugin name overrides.
* @param \Cake\Core\ContainerInterface|null $container The DIC instance from the application
*/
public function __construct(string $appNamespace = 'App', array $overrides = [])
{
public function __construct(
string $appNamespace = 'App',
array $overrides = [],
?ContainerInterface $container = null
) {
$this->appNamespace = $appNamespace;
$this->overrides = $overrides;
$this->container = $container;
}

/**
Expand Down Expand Up @@ -146,7 +159,13 @@ protected function findPolicy(string $class, string $name, string $namespace): m
throw new MissingPolicyException([$class]);
}

return new $policyClass();
if ($this->container && $this->container->has($policyClass)) {
$policy = $this->container->get($policyClass);
} else {
$policy = new $policyClass();
}

return $policy;
}

/**
Expand Down
46 changes: 46 additions & 0 deletions tests/TestCase/Middleware/AuthorizationMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,25 @@
*/
namespace Authorization\Test\TestCase\Middleware;

use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Exception\AuthorizationRequiredException;
use Authorization\Exception\Exception;
use Authorization\IdentityDecorator;
use Authorization\IdentityInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Cake\Core\Container;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\Http\ServerRequestFactory;
use Cake\TestSuite\TestCase;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
use stdClass;
use TestApp\Application;
use TestApp\Http\TestRequestHandler;
use TestApp\Identity;

Expand Down Expand Up @@ -270,4 +274,46 @@ public function testUnauthorizedHandlerRequireAuthz()
$result = $middleware->process($request, $handler);
$this->assertSame(200, $result->getStatusCode());
}

public function testMiddlewareInjectsServiceIntoDIC()
{
$request = ServerRequestFactory::fromGlobals(
['REQUEST_URI' => '/testpath'],
[],
['username' => 'mariano', 'password' => 'password']
);
$handler = new TestRequestHandler();
$application = new Application('config');

$middleware = new AuthorizationMiddleware($application, ['requireAuthorizationCheck' => false]);
$middleware->process($request, $handler);

$container = $application->getContainer();
$this->assertInstanceOf(AuthorizationService::class, $container->get(AuthorizationService::class));
}

public function testMiddlewareInjectsServiceIntoDICViaCustomContainerInstance()
{
$request = ServerRequestFactory::fromGlobals(
['REQUEST_URI' => '/testpath'],
[],
['username' => 'mariano', 'password' => 'password']
);
$handler = new TestRequestHandler();

$service = $this->createMock(AuthorizationServiceInterface::class);
$provider = $this->createMock(AuthorizationServiceProviderInterface::class);
$provider
->expects($this->once())
->method('getAuthorizationService')
->with($this->isInstanceOf(ServerRequestInterface::class))
->willReturn($service);

$container = new Container();

$middleware = new AuthorizationMiddleware($provider, ['requireAuthorizationCheck' => false], $container);
$middleware->process($request, $handler);

$this->assertEquals($service, $container->get(AuthorizationService::class));
}
}
25 changes: 25 additions & 0 deletions tests/TestCase/Policy/MapResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@
*/
namespace Authorization\Test\TestCase\Policy;

use Authorization\AuthorizationService;
use Authorization\IdentityDecorator;
use Authorization\Policy\Exception\MissingPolicyException;
use Authorization\Policy\MapResolver;
use Cake\Core\Container;
use Cake\TestSuite\TestCase;
use InvalidArgumentException;
use TestApp\Model\Entity\Article;
use TestApp\Policy\ArticlePolicy;
use TestApp\Service\TestService;

class MapResolverTest extends TestCase
{
Expand Down Expand Up @@ -102,4 +106,25 @@ public function testGetPolicyMissing()

$resolver->getPolicy(new Article());
}

public function testGetPolicyViaDIC()
{
$container = new Container();
$container->add(TestService::class);
$container->add(ArticlePolicy::class)
->addArgument(TestService::class);

$article = new Article();
$resolver = new MapResolver([], $container);
$resolver->map(Article::class, ArticlePolicy::class);

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

$policy = $resolver->getPolicy($article);
$this->assertInstanceOf(ArticlePolicy::class, $policy);
$this->assertTrue($policy->canWithInjectedService($user, $article));
}
}
24 changes: 24 additions & 0 deletions tests/TestCase/Policy/OrmResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
*/
namespace Authorization\Test\TestCase\Policy;

use Authorization\AuthorizationService;
use Authorization\IdentityDecorator;
use Authorization\Policy\Exception\MissingPolicyException;
use Authorization\Policy\OrmResolver;
use Cake\Core\Container;
use Cake\ORM\Entity;
use Cake\ORM\Locator\LocatorAwareTrait;
use Cake\TestSuite\TestCase;
Expand All @@ -27,6 +30,7 @@
use TestApp\Policy\ArticlePolicy;
use TestApp\Policy\ArticlesTablePolicy;
use TestApp\Policy\TestPlugin\BookmarkPolicy;
use TestApp\Service\TestService;
use TestPlugin\Model\Entity\Bookmark;
use TestPlugin\Model\Entity\Tag;
use TestPlugin\Policy\TagPolicy;
Expand Down Expand Up @@ -119,4 +123,24 @@ public function testGetPolicyUnknownTable()
$resolver = new OrmResolver('TestApp');
$resolver->getPolicy($articles);
}

public function testGetPolicyViaDIC()
{
$container = new Container();
$container->add(TestService::class);
$container->add(ArticlePolicy::class)
->addArgument(TestService::class);

$article = new Article();
$resolver = new OrmResolver('TestApp', [], $container);

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

$policy = $resolver->getPolicy($article);
$this->assertInstanceOf(ArticlePolicy::class, $policy);
$this->assertTrue($policy->canWithInjectedService($user, $article));
}
}
18 changes: 17 additions & 1 deletion tests/test_app/TestApp/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@

namespace TestApp;

use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Policy\MapResolver;
use Cake\Http\BaseApplication;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\RouteBuilder;
use Psr\Http\Message\ServerRequestInterface;
use TestApp\Model\Entity\Article;
use TestApp\Policy\ArticlePolicy;

class Application extends BaseApplication
class Application extends BaseApplication implements AuthorizationServiceProviderInterface
{
public function middleware(MiddlewareQueue $middleware): MiddlewareQueue
{
Expand All @@ -23,4 +30,13 @@ public function bootstrap(): void
$this->addPlugin('Authorization');
$this->addPlugin('Bake');
}

public function getAuthorizationService(ServerRequestInterface $request): AuthorizationServiceInterface
{
$resolver = new MapResolver([
Article::class => ArticlePolicy::class,
]);

return new AuthorizationService($resolver);
}
}
18 changes: 18 additions & 0 deletions tests/test_app/TestApp/Policy/ArticlePolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,22 @@
use Authorization\Policy\Result;
use Closure;
use TestApp\Model\Entity\Article;
use TestApp\Service\TestService;

class ArticlePolicy
{
/**
* A service class injected via DIC
*
* @var \TestApp\Service\TestService|null
*/
protected ?TestService $service;

public function __construct(?TestService $service = null)
{
$this->service = $service;
}

/**
* Create articles if you're an admin or author
*
Expand Down Expand Up @@ -131,4 +144,9 @@ public function canWithMultipleServices($user, Article $article, Closure $servic
{
return $service1() && $service2();
}

public function canWithInjectedService($user, Article $article)
{
return $this->service->serviceLogic();
}
}
Loading

0 comments on commit 9cc8b11

Please sign in to comment.