Skip to content

Commit

Permalink
Merge pull request #53 from robertpustulka/provider
Browse files Browse the repository at this point in the history
Add AuthorizationServiceProviderInterface
  • Loading branch information
markstory committed Jun 12, 2018
2 parents 6a67086 + 7c4b682 commit c1084a1
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 77 deletions.
37 changes: 19 additions & 18 deletions docs/Middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,37 @@ Authorization is applied to your application as a middleware. The
`applyScope` if necessary.
* Ensuring that authorization has been checked/bypassed in the request.

To use the middleware, update the `middleware` hook of your `Application` with
the following:
To use the middleware implement `AuthorizationServiceProviderInterface` in your
application class. Then pass your app instance into the middlware and add the
middleware to the queue.

```php
// Import the class.
use Authorization\Middleware\AuthorizationMiddleware;

// inside your application's middleware hook.
$middlewareQueue->add(new AuthorizationMiddleware($this));
```

By passing your application instance into the middlware, it can invoke the
``authorization`` hook method on your application which should return
a configured `AuthorizationService`. A very simple example would be:
A very simple example would be:

```php
namespace App;

use Authorization\AuthorizationService;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Middleware\AuthorizationMiddleware;
use Authorization\Policy\OrmResolver;
use Cake\Http\BaseApplication;

class Application extends BaseApplication
class Application extends BaseApplication implements AuthorizationServiceProviderInterface
{
public function authorization($request)
public function getAuthorizationService(ServerRequestInterface $request, ResponseInterface $response)
{
$resolver = new OrmResolver();

return new AuthorizationService($resolver);
}

public function middleware($middlewareQueue)
{
// other middleware
$middlewareQueue->add(new AuthorizationMiddleware($this));

return $middlewareQueue;
}
}
```

Expand Down Expand Up @@ -109,7 +110,7 @@ setup:
// In your Application::middleware() method;

// Authorization
$middleware->add(new AuthorizationMiddleware($this, [
$middlewareQueue->add(new AuthorizationMiddleware($this, [
'identityDecorator' => function ($auth, $user) {
return $user->setAuthorization($auth);
}
Expand All @@ -130,7 +131,7 @@ a helpful aid during development/testing. You can disable this behavior via an
option:

```php
$middlewareStack->add(new AuthorizationMiddleware($this, [
$middlewareQueue->add(new AuthorizationMiddleware($this, [
'requireAuthorizationCheck' => false
]));
```
Expand All @@ -157,7 +158,7 @@ Both redirect handlers share the same configuration options:
For example:

```php
$middlewareStack->add(new AuthorizationMiddleware($this, [
$middlewareQueue->add(new AuthorizationMiddleware($this, [
'unauthorizedHandler' => [
'className' => 'Authorization.Redirect',
'url' => '/users/login',
Expand Down
34 changes: 34 additions & 0 deletions src/AuthorizationServiceProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* 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 1.0.0
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
namespace Authorization;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

/**
* This interface should be implemented by the authorization service provider.
*/
interface AuthorizationServiceProviderInterface
{

/**
* Returns authorization service instance.
*
* @param \Psr\Http\Message\ServerRequestInterface $request Request
* @param \Psr\Http\Message\ResponseInterface $response Response
* @return \Authorization\AuthorizationServiceInterface
*/
public function getAuthorizationService(ServerRequestInterface $request, ResponseInterface $response);
}
26 changes: 9 additions & 17 deletions src/Middleware/AuthorizationMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
namespace Authorization\Middleware;

use Authorization\AuthorizationServiceInterface;
use Authorization\AuthorizationServiceProviderInterface;
use Authorization\Exception\AuthorizationRequiredException;
use Authorization\Exception\Exception;
use Authorization\IdentityDecorator;
use Authorization\IdentityInterface;
use Authorization\Middleware\UnauthorizedHandler\HandlerFactory;
use Cake\Core\HttpApplicationInterface;
use Cake\Core\InstanceConfigTrait;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
Expand Down Expand Up @@ -60,23 +60,23 @@ class AuthorizationMiddleware
/**
* Authorization service or application instance.
*
* @var \Authorization\AuthorizationServiceInterface|\Cake\Core\HttpApplicationInterface
* @var \Authorization\AuthorizationServiceInterface|\Authorization\AuthorizationServiceProviderInterface
*/
protected $subject;

/**
* Constructor.
*
* @param \Authorization\AuthorizationServiceInterface|\Cake\Core\HttpApplicationInterface $subject Authorization service or application instance.
* @param \Authorization\AuthorizationServiceInterface|\Authorization\AuthorizationServiceProviderInterface $subject Authorization service or provider instance.
* @param array $config Config array.
* @throws \InvalidArgumentException
*/
public function __construct($subject, array $config = [])
{
if (!$subject instanceof AuthorizationServiceInterface && !$subject instanceof HttpApplicationInterface) {
if (!$subject instanceof AuthorizationServiceInterface && !$subject instanceof AuthorizationServiceProviderInterface) {
$expected = implode('` or `', [
AuthorizationServiceInterface::class,
HttpApplicationInterface::class
AuthorizationServiceProviderInterface::class
]);
$type = is_object($subject) ? get_class($subject) : gettype($subject);
$message = sprintf('Subject must be an instance of `%s`, `%s` given.', $expected, $type);
Expand Down Expand Up @@ -152,22 +152,14 @@ protected function getHandler()
*/
protected function getAuthorizationService($request, $response)
{
if ($this->subject instanceof AuthorizationServiceInterface) {
return $this->subject;
$service = $this->subject;
if ($this->subject instanceof AuthorizationServiceProviderInterface) {
$service = $this->subject->getAuthorizationService($request, $response);
}

$method = 'authorization';
if (!method_exists($this->subject, $method)) {
$message = sprintf('Method `%s` has not been defined in your `Application` class.', $method);
throw new RuntimeException($message);
}

$service = $this->subject->$method($request, $response);

if (!$service instanceof AuthorizationServiceInterface) {
throw new RuntimeException(sprintf(
'Invalid service returned from `%s` method. `%s` does not implement `%s`.',
$method,
'Invalid service returned from the provider. `%s` does not implement `%s`.',
is_object($service) ? get_class($service) : gettype($service),
AuthorizationServiceInterface::class
));
Expand Down
53 changes: 11 additions & 42 deletions tests/TestCase/Middleware/AuthorizationMiddlewareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
namespace Authorization\Test\TestCase\Middleware;

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\HttpApplicationInterface;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\TestSuite\TestCase;
Expand Down Expand Up @@ -78,13 +78,10 @@ public function testInvokeAuthorizationRequiredError()
public function testInvokeApp()
{
$service = $this->createMock(AuthorizationServiceInterface::class);

$application = $this->getMockBuilder(HttpApplicationInterface::class)
->setMethods(['authorization', 'middleware', 'routes', 'bootstrap', '__invoke'])
->getMock();
$application
$provider = $this->createMock(AuthorizationServiceProviderInterface::class);
$provider
->expects($this->once())
->method('authorization')
->method('getAuthorizationService')
->with(
$this->isInstanceOf(ServerRequestInterface::class),
$this->isInstanceOf(ResponseInterface::class)
Expand All @@ -97,7 +94,7 @@ public function testInvokeApp()
return $request;
};

$middleware = new AuthorizationMiddleware($application, ['requireAuthorizationCheck' => false]);
$middleware = new AuthorizationMiddleware($provider, ['requireAuthorizationCheck' => false]);

$result = $middleware($request, $response, $next);

Expand All @@ -106,40 +103,12 @@ public function testInvokeApp()
$this->assertNull($result->getAttribute('identity'));
}

public function testInvokeAppMissing()
{
$service = $this->createMock(AuthorizationServiceInterface::class);

$application = $this->getMockBuilder(HttpApplicationInterface::class)
->setMethods(['middleware', 'routes', 'bootstrap', '__invoke'])
->getMock();

$request = new ServerRequest();
$response = new Response();
$next = function ($request) {
return $request;
};

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

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Method `authorization` has not been defined in your `Application` class.');

$result = $middleware($request, $response, $next);
}

public function testInvokeAppInvalid()
{
$application = $this->getMockBuilder(HttpApplicationInterface::class)
->setMethods(['middleware', 'routes', 'bootstrap', '__invoke'])
->getMock();

$application = $this->getMockBuilder(HttpApplicationInterface::class)
->setMethods(['authorization', 'middleware', 'routes', 'bootstrap', '__invoke'])
->getMock();
$application
$provider = $this->createMock(AuthorizationServiceProviderInterface::class);
$provider
->expects($this->once())
->method('authorization')
->method('getAuthorizationService')
->with(
$this->isInstanceOf(ServerRequestInterface::class),
$this->isInstanceOf(ResponseInterface::class)
Expand All @@ -152,11 +121,11 @@ public function testInvokeAppInvalid()
return $request;
};

$middleware = new AuthorizationMiddleware($application, ['requireAuthorizationCheck' => false]);
$middleware = new AuthorizationMiddleware($provider, ['requireAuthorizationCheck' => false]);

$this->expectException(RuntimeException::class);
$this->expectExceptionMessage(
'Invalid service returned from `authorization` method. ' .
'Invalid service returned from the provider. ' .
'`stdClass` does not implement `Authorization\AuthorizationServiceInterface`.'
);

Expand All @@ -176,7 +145,7 @@ public function testInvokeInvalid()
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(
'Subject must be an instance of `Authorization\AuthorizationServiceInterface` ' .
'or `Cake\Core\HttpApplicationInterface`, `stdClass` given.'
'or `Authorization\AuthorizationServiceProviderInterface`, `stdClass` given.'
);

$middleware = new AuthorizationMiddleware(new stdClass());
Expand Down

0 comments on commit c1084a1

Please sign in to comment.