Skip to content

Commit

Permalink
Merge pull request #111 from dereuromark/policy
Browse files Browse the repository at this point in the history
Add Request Policy.
dereuromark authored Nov 9, 2019
2 parents d56091b + cbe51c8 commit a3a3240
Showing 9 changed files with 256 additions and 46 deletions.
20 changes: 20 additions & 0 deletions docs/AuthorizationPlugin.md
Original file line number Diff line number Diff line change
@@ -12,4 +12,24 @@ $this->loadComponent('TinyAuth.Authorization', [

For all the rest just follow the plugin's documentation.

For your resolver you need to use this map inside `Application::getAuthorizationService()`:
```php
use TinyAuth\Policy\RequestPolicy;

/**
* @param \Psr\Http\Message\ServerRequestInterface $request
* @param \Psr\Http\Message\ResponseInterface $response
*
* @return \Authorization\AuthorizationServiceInterface
*/
public function getAuthorizationService(ServerRequestInterface $request, ResponseInterface $response) {
$map = [
ServerRequest::class => new RequestPolicy(),
];
$resolver = new MapResolver($map);

return new AuthorizationService($resolver);
}
```

Then you use the [Authorization documention](Authorization.md) to set up roles and fill your INI config file.
36 changes: 27 additions & 9 deletions src/Auth/AclTrait.php
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use InvalidArgumentException;
use RuntimeException;
use TinyAuth\Auth\AclAdapter\AclAdapterInterface;
use TinyAuth\Utility\Cache;
use TinyAuth\Utility\Utility;
@@ -391,7 +392,7 @@ protected function _constructIniKey($params) {
* Will look for a roles array in
* Configure first, tries database roles table next.
*
* @return array List with all available roles
* @return int[] List with all available roles
* @throws \Cake\Core\Exception\Exception
*/
protected function _getAvailableRoles() {
@@ -410,13 +411,23 @@ protected function _getAvailableRoles() {
$key = $this->getConfig('superAdmin') ?: 'superadmin';
$roles[$key] = $this->getConfig('superAdminRole');
}

if (!$roles) {
throw new Exception('Invalid roles config: No roles found in config.');
}

return $roles;
}

$rolesTable = TableRegistry::get($rolesTableKey);
$result = $rolesTable->find()->formatResults(function (ResultSetInterface $results) {
return $results->combine($this->getConfig('aliasColumn'), 'id');
});
try {
$rolesTable = TableRegistry::get($rolesTableKey);
$result = $rolesTable->find()->formatResults(function (ResultSetInterface $results) {
return $results->combine($this->getConfig('aliasColumn'), 'id');
});
} catch (RuntimeException $e) {
throw new Exception('Invalid roles config: DB table `' . $rolesTableKey . '` cannot be found/accessed (`' . $e->getMessage() . '`).', null, $e);
}

$roles = $result->toArray();

if ($this->getConfig('superAdminRole')) {
@@ -448,7 +459,12 @@ protected function _getAvailableRoles() {
protected function _getUserRoles($user) {
// Single-role from session
if (!$this->getConfig('multiRole')) {
if (!array_key_exists($this->getConfig('roleColumn'), $user)) {
$roleColumn = $this->getConfig('roleColumn');
if (!$roleColumn) {
throw new Exception('Invalid TinyAuth config, `roleColumn` config missing.');
}

if (!array_key_exists($roleColumn, $user)) {
throw new Exception(sprintf('Missing TinyAuth role id field (%s) in user session', 'Auth.User.' . $this->getConfig('roleColumn')));
}
if (!isset($user[$this->getConfig('roleColumn')])) {
@@ -529,10 +545,12 @@ protected function _getRolesFromDb($pivotTableName, $id) {
}

/**
* @param array $roles
* @return array
* Returns current roles as [alias => id] pairs.
*
* @param int[] $roles
* @return int[]
*/
protected function _mapped($roles) {
protected function _mapped(array $roles) {
$availableRoles = $this->_getAvailableRoles();

$array = [];
13 changes: 0 additions & 13 deletions src/Controller/Component/AuthComponent.php
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@

use Cake\Controller\ComponentRegistry;
use Cake\Controller\Component\AuthComponent as CakeAuthComponent;
use Cake\Event\Event;
use RuntimeException;
use TinyAuth\Auth\AclTrait;
use TinyAuth\Auth\AllowTrait;
@@ -60,18 +59,6 @@ public function implementedEvents() {
] + parent::implementedEvents();
}

/**
* @param \Cake\Event\Event $event
* @return \Cake\Http\Response|null
*/
public function beforeRender(Event $event) {
/** @var \Cake\Controller\Controller $controller */
$controller = $event->getSubject();

$authUser = (array)$this->user();
$controller->set('_authUser', $authUser);
}

/**
* @param array $params
* @return void
32 changes: 16 additions & 16 deletions src/Controller/Component/AuthorizationComponent.php
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@

use Authorization\Controller\Component\AuthorizationComponent as CakeAuthorizationComponent;
use Cake\Controller\ComponentRegistry;
use Cake\Event\Event;
use RuntimeException;
use TinyAuth\Auth\AclTrait;
use TinyAuth\Utility\Config;
@@ -36,25 +35,26 @@ public function __construct(ComponentRegistry $registry, array $config = []) {
}

/**
* Callback for Controller.startup event.
* Action authorization handler.
*
* @param \Cake\Event\Event $event Event instance.
* @return \Cake\Http\Response|null
* Checks identity and model authorization.
*
* @return void
*/
public function startup(Event $event) {
$this->_prepareAuthorization($event);
public function authorizeAction() {
$request = $this->getController()->getRequest();

return null;
}
$action = $request->getParam('action');
$skipAuthorization = $this->checkAction($action, 'skipAuthorization');
if ($skipAuthorization) {
$this->skipAuthorization();

/**
* @param \Cake\Event\Event $event
* @return void
*/
protected function _prepareAuthorization(Event $event) {
//TODO 'skipAuthorization' from allow ini config
//TODO 'authorizeModel' and maybe 'actionMap'
//TODO rest
return;
}

$this->authorize($request, 'access');

parent::authorizeAction();
}

}
47 changes: 47 additions & 0 deletions src/Policy/RequestPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
namespace TinyAuth\Policy;

use Authorization\Policy\RequestPolicyInterface;
use Cake\Core\InstanceConfigTrait;
use Cake\Http\ServerRequest;
use TinyAuth\Auth\AclTrait;
use TinyAuth\Utility\Config;

/**
* Used for middleware approach.
*/
class RequestPolicy implements RequestPolicyInterface {

use AclTrait;
use InstanceConfigTrait;

/**
* @var array
*/
protected $_defaultConfig = [
];

/**
* @param array $config
*/
public function __construct(array $config = []) {
$config += Config::all();

$this->setConfig($config);
}

/**
* Method to check if the request can be accessed
*
* @param \Authorization\IdentityInterface|null $identity Identity
* @param \Cake\Http\ServerRequest $request Request
* @return bool
*/
public function canAccess($identity, ServerRequest $request) {
$params = $request->getAttribute('params');
$user = $identity->getOriginalData();

return $this->_checkUser((array)$user, $params);
}

}
2 changes: 1 addition & 1 deletion src/Utility/TinyAuth.php
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ public function __construct(array $config = []) {
}

/**
* @return string[]
* @return int[]
*/
public function getAvailableRoles() {
$roles = $this->_getAvailableRoles();
28 changes: 21 additions & 7 deletions tests/TestCase/Controller/Component/AuthorizationComponentTest.php
Original file line number Diff line number Diff line change
@@ -2,10 +2,11 @@

namespace TinyAuth\Test\TestCase\Controller\Component;

use Authorization\AuthorizationService;
use Cake\Controller\ComponentRegistry;
use Cake\Controller\Controller;
use Cake\Core\Configure;
use Cake\Core\Plugin;
use Cake\Event\Event;
use Cake\Http\ServerRequest;
use Cake\TestSuite\TestCase;
use TinyAuth\Controller\Component\AuthorizationComponent;
@@ -26,8 +27,14 @@ class AuthorizationComponentTest extends TestCase {
* @return void
*/
public function setUp() {
Configure::write('Roles', [
'user' => ROLE_USER,
'moderator' => ROLE_MODERATOR,
'admin' => ROLE_ADMIN
]);

$this->componentConfig = [
'allowFilePath' => Plugin::path('TinyAuth') . 'tests' . DS . 'test_files' . DS,
'aclFilePath' => Plugin::path('TinyAuth') . 'tests' . DS . 'test_files' . DS,
'autoClearCache' => true,
];
}
@@ -43,17 +50,24 @@ public function testValid() {
'_ext' => null,
'pass' => [1]
]]);
$authorization = $this->getMockBuilder(AuthorizationService::class)->disableOriginalConstructor()->getMock();
$authorization->expects($this->once())
->method('can')
->willReturn(true);

$request = $request->withAttribute('authorization', $authorization);
$controller = $this->getControllerMock($request);

$registry = new ComponentRegistry($controller);
$this->component = new AuthorizationComponent($registry, $this->componentConfig);

$config = [];
$this->component->initialize($config);
$this->component->authorizeAction();

$request = $this->component->getController()->getRequest();
/** @var \Authorization\AuthorizationService $service */
$service = $request->getAttribute('authorization');

$event = new Event('Controller.startup', $controller);
$response = $this->component->startup($event);
$this->assertNull($response);
$this->assertInstanceOf(AuthorizationService::class, $service);
}

/**
90 changes: 90 additions & 0 deletions tests/TestCase/Policy/RequestPolicyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace TinyAuth\Test\TestCase\Policy;

use Authorization\AuthorizationService;
use Authorization\IdentityDecorator;
use Cake\Core\Configure;
use Cake\Core\Plugin;
use Cake\Http\ServerRequest;
use Cake\TestSuite\TestCase;
use TinyAuth\Policy\RequestPolicy;

class RequestPolicyTest extends TestCase {

/**
* @var \TinyAuth\Controller\Component\AuthorizationComponent
*/
protected $policy;

/**
* @var array
*/
protected $config = [];

/**
* @return void
*/
public function setUp() {
Configure::write('Roles', [
'user' => ROLE_USER,
'moderator' => ROLE_MODERATOR,
'admin' => ROLE_ADMIN
]);

$this->config = [
'aclFilePath' => Plugin::path('TinyAuth') . 'tests' . DS . 'test_files' . DS,
'autoClearCache' => true,
];

$this->policy = new RequestPolicy($this->config);
}

/**
* @return void
*/
public function testPolicyCanAccessSuccess() {
$request = new ServerRequest(['params' => [
'controller' => 'Tags',
'action' => 'delete',
'plugin' => null,
]]);

$identityArray = [
'id' => 1,
'role_id' => ROLE_ADMIN,
];
$service = $this->getService();
$identity = new IdentityDecorator($service, $identityArray);
$result = $this->policy->canAccess($identity, $request);
$this->assertTrue($result);
}

/**
* @return void
*/
public function testPolicyCanAccessFail() {
$request = new ServerRequest(['params' => [
'controller' => 'Tags',
'action' => 'edit',
'plugin' => null,
]]);

$identityArray = [
'id' => 1,
'role_id' => ROLE_ADMIN,
];
$service = $this->getService();
$identity = new IdentityDecorator($service, $identityArray);
$result = $this->policy->canAccess($identity, $request);
$this->assertFalse($result);
}

/**
* @return \Authorization\AuthorizationService|\PHPUnit\Framework\MockObject\MockObject
*/
protected function getService() {
return $this->getMockBuilder(AuthorizationService::class)->disableOriginalConstructor()->getMock();
}

}
34 changes: 34 additions & 0 deletions tests/TestCase/Utility/TinyAuthTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace TinyAuth\Test\TestCase\Utility;

use Cake\TestSuite\TestCase;
use TinyAuth\Utility\TinyAuth;

class TinyAuthTest extends TestCase {

/**
* @var array
*/
public $fixtures = [
'plugin.TinyAuth.DatabaseRoles',
];

/**
* @return void
*/
public function testGetAvailableRoles() {
$config = [
'rolesTable' => 'DatabaseRoles',
];

$result = (new TinyAuth($config))->getAvailableRoles();
$expected = [
'user' => 11,
'moderator' => 12,
'admin' => 13
];
$this->assertEquals($expected, $result);
}

}

0 comments on commit a3a3240

Please sign in to comment.