Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Request Policy. #111

Merged
merged 2 commits into from
Nov 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/AuthorizationPlugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand All @@ -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')) {
Expand Down Expand Up @@ -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')])) {
Expand Down Expand Up @@ -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 = [];
Expand Down
13 changes: 0 additions & 13 deletions src/Controller/Component/AuthComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
32 changes: 16 additions & 16 deletions src/Controller/Component/AuthorizationComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Up @@ -25,7 +25,7 @@ public function __construct(array $config = []) {
}

/**
* @return string[]
* @return int[]
*/
public function getAvailableRoles() {
$roles = $this->_getAvailableRoles();
Expand Down
28 changes: 21 additions & 7 deletions tests/TestCase/Controller/Component/AuthorizationComponentTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
];
}
Expand All @@ -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);
}

/**
Expand Down
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();
}

}
Loading