Implemented controller permissions
This commit is contained in:
parent
55beca95cd
commit
c9d7e88cc7
|
@ -4,5 +4,16 @@ namespace Engelsystem\Controllers;
|
|||
|
||||
abstract class BaseController
|
||||
{
|
||||
/** @var string[]|string[][] A list of Permissions required to access the controller or certain pages */
|
||||
protected $permissions = [];
|
||||
|
||||
/**
|
||||
* Returns the list of permissions
|
||||
*
|
||||
* @return string[]|string[][]
|
||||
*/
|
||||
public function getPermissions()
|
||||
{
|
||||
return $this->permissions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,6 @@ class AuthenticatorServiceProvider extends ServiceProvider
|
|||
|
||||
$this->app->instance(Authenticator::class, $authenticator);
|
||||
$this->app->instance('authenticator', $authenticator);
|
||||
$this->app->instance('auth', $authenticator);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace Engelsystem\Http\Exceptions;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class HttpForbidden extends HttpException
|
||||
{
|
||||
/**
|
||||
* @param string $message
|
||||
* @param array $headers
|
||||
* @param int $code
|
||||
* @param Throwable|null $previous
|
||||
*/
|
||||
public function __construct(
|
||||
string $message = '',
|
||||
array $headers = [],
|
||||
int $code = 0,
|
||||
Throwable $previous = null
|
||||
) {
|
||||
parent::__construct(403, $message, $headers, $code, $previous);
|
||||
}
|
||||
}
|
|
@ -363,7 +363,7 @@ class Request extends SymfonyRequest implements ServerRequestInterface
|
|||
foreach ($uploadedFiles as $file) {
|
||||
/** @var UploadedFileInterface $file */
|
||||
$filename = tempnam(sys_get_temp_dir(), 'upload');
|
||||
$handle = fopen($filename, "w");
|
||||
$handle = fopen($filename, 'w');
|
||||
fwrite($handle, $file->getStream()->getContents());
|
||||
fclose($handle);
|
||||
|
||||
|
|
|
@ -74,4 +74,12 @@ class CallableHandler implements MiddlewareInterface, RequestHandlerInterface
|
|||
$response = $this->container->get('response');
|
||||
return $response->withContent($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public function getCallable()
|
||||
{
|
||||
return $this->callable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
namespace Engelsystem\Middleware;
|
||||
|
||||
use Engelsystem\Application;
|
||||
use Engelsystem\Controllers\BaseController;
|
||||
use Engelsystem\Helpers\Authenticator;
|
||||
use Engelsystem\Http\Exceptions\HttpForbidden;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
@ -37,6 +40,14 @@ class RequestHandler implements MiddlewareInterface
|
|||
$requestHandler = $request->getAttribute('route-request-handler');
|
||||
$requestHandler = $this->resolveRequestHandler($requestHandler);
|
||||
|
||||
if ($requestHandler instanceof CallableHandler) {
|
||||
$callable = $requestHandler->getCallable();
|
||||
|
||||
if (is_array($callable) && $callable[0] instanceof BaseController) {
|
||||
$this->checkPermissions($callable[0], $callable[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($requestHandler instanceof MiddlewareInterface) {
|
||||
return $requestHandler->process($request, $handler);
|
||||
}
|
||||
|
@ -49,6 +60,8 @@ class RequestHandler implements MiddlewareInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* Resolve the given class
|
||||
*
|
||||
* @param string|callable|MiddlewareInterface|RequestHandlerInterface $handler
|
||||
* @return MiddlewareInterface|RequestHandlerInterface
|
||||
*/
|
||||
|
@ -76,4 +89,36 @@ class RequestHandler implements MiddlewareInterface
|
|||
|
||||
return $this->resolveMiddleware($handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check required page permissions
|
||||
*
|
||||
* @param BaseController $controller
|
||||
* @param string $method
|
||||
* @return bool
|
||||
*/
|
||||
protected function checkPermissions(BaseController $controller, string $method): bool
|
||||
{
|
||||
/** @var Authenticator $auth */
|
||||
$auth = $this->container->get('auth');
|
||||
$permissions = $controller->getPermissions();
|
||||
|
||||
// Merge action permissions
|
||||
if (isset($permissions[$method])) {
|
||||
$permissions = array_merge($permissions, (array)$permissions[$method]);
|
||||
}
|
||||
|
||||
foreach ($permissions as $key => $permission) {
|
||||
// Skip all action permission entries
|
||||
if (!is_int($key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$auth->can($permission)) {
|
||||
throw new HttpForbidden();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Engelsystem\Test\Unit\Controllers;
|
||||
|
||||
use Engelsystem\Test\Unit\Controllers\Stub\ControllerImplementation;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class BaseControllerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Engelsystem\Controllers\BaseController::getPermissions
|
||||
*/
|
||||
public function testGetPermissions()
|
||||
{
|
||||
$controller = new ControllerImplementation();
|
||||
|
||||
$this->assertEquals([
|
||||
'foo',
|
||||
'lorem' => [
|
||||
'ipsum',
|
||||
'dolor',
|
||||
],
|
||||
], $controller->getPermissions());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Engelsystem\Test\Unit\Controllers\Stub;
|
||||
|
||||
use Engelsystem\Controllers\BaseController;
|
||||
|
||||
class ControllerImplementation extends BaseController
|
||||
{
|
||||
/** @var array */
|
||||
protected $permissions = [
|
||||
'foo',
|
||||
'lorem' => [
|
||||
'ipsum',
|
||||
'dolor',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param array $permissions
|
||||
*/
|
||||
public function setPermissions(array $permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
}
|
||||
}
|
|
@ -24,5 +24,6 @@ class AuthenticatorServiceProviderTest extends ServiceProviderTest
|
|||
|
||||
$this->assertInstanceOf(Authenticator::class, $app->get(Authenticator::class));
|
||||
$this->assertInstanceOf(Authenticator::class, $app->get('authenticator'));
|
||||
$this->assertInstanceOf(Authenticator::class, $app->get('auth'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Engelsystem\Test\Unit\Http\Exceptions;
|
||||
|
||||
use Engelsystem\Http\Exceptions\HttpForbidden;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class HttpForbiddenTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers \Engelsystem\Http\Exceptions\HttpForbidden::__construct
|
||||
*/
|
||||
public function testConstruct()
|
||||
{
|
||||
$exception = new HttpForbidden();
|
||||
$this->assertEquals(403, $exception->getStatusCode());
|
||||
$this->assertEquals('', $exception->getMessage());
|
||||
|
||||
$exception = new HttpForbidden('Go away!');
|
||||
$this->assertEquals('Go away!', $exception->getMessage());
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ use PHPUnit\Framework\TestCase;
|
|||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use ReflectionClass as Reflection;
|
||||
use stdClass;
|
||||
|
||||
class CallableHandlerTest extends TestCase
|
||||
|
@ -28,17 +27,14 @@ class CallableHandlerTest extends TestCase
|
|||
/**
|
||||
* @dataProvider provideCallable
|
||||
* @covers \Engelsystem\Middleware\CallableHandler::__construct
|
||||
* @covers \Engelsystem\Middleware\CallableHandler::getCallable
|
||||
* @param callable $callable
|
||||
*/
|
||||
public function testInit($callable)
|
||||
{
|
||||
$handler = new CallableHandler($callable);
|
||||
|
||||
$reflection = new Reflection(get_class($handler));
|
||||
$property = $reflection->getProperty('callable');
|
||||
$property->setAccessible(true);
|
||||
|
||||
$this->assertEquals($callable, $property->getValue($handler));
|
||||
$this->assertEquals($callable, $handler->getCallable());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3,7 +3,11 @@
|
|||
namespace Engelsystem\Test\Unit\Middleware;
|
||||
|
||||
use Engelsystem\Application;
|
||||
use Engelsystem\Helpers\Authenticator;
|
||||
use Engelsystem\Http\Exceptions\HttpForbidden;
|
||||
use Engelsystem\Middleware\CallableHandler;
|
||||
use Engelsystem\Middleware\RequestHandler;
|
||||
use Engelsystem\Test\Unit\Middleware\Stub\ControllerImplementation;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -131,6 +135,79 @@ class RequestHandlerTest extends TestCase
|
|||
$this->assertEquals($return, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Engelsystem\Middleware\RequestHandler::process
|
||||
* @covers \Engelsystem\Middleware\RequestHandler::checkPermissions
|
||||
*/
|
||||
public function testCheckPermissions()
|
||||
{
|
||||
/** @var Application|MockObject $container */
|
||||
/** @var ServerRequestInterface|MockObject $request */
|
||||
/** @var RequestHandlerInterface|MockObject $handler */
|
||||
/** @var ResponseInterface|MockObject $response */
|
||||
list($container, $request, $handler, $response) = $this->getMocks();
|
||||
|
||||
/** @var Authenticator|MockObject $auth */
|
||||
$auth = $this->createMock(Authenticator::class);
|
||||
|
||||
$class = new ControllerImplementation();
|
||||
/** @var CallableHandler|MockObject $callable */
|
||||
$callable = $this->getMockBuilder(CallableHandler::class)
|
||||
->setConstructorArgs([[$class, 'actionStub']])
|
||||
->getMock();
|
||||
|
||||
$callable->expects($this->exactly(2))
|
||||
->method('getCallable')
|
||||
->willReturn([$class, 'actionStub']);
|
||||
|
||||
$callable->expects($this->exactly(1))
|
||||
->method('process')
|
||||
->with($request, $handler)
|
||||
->willReturn($response);
|
||||
|
||||
$request->expects($this->exactly(2))
|
||||
->method('getAttribute')
|
||||
->with('route-request-handler')
|
||||
->willReturn($callable);
|
||||
|
||||
|
||||
/** @var RequestHandler|MockObject $middleware */
|
||||
$middleware = $this->getMockBuilder(RequestHandler::class)
|
||||
->setConstructorArgs([$container])
|
||||
->setMethods(['resolveRequestHandler'])
|
||||
->getMock();
|
||||
|
||||
$middleware->expects($this->exactly(2))
|
||||
->method('resolveRequestHandler')
|
||||
->with($callable)
|
||||
->willReturn($callable);
|
||||
|
||||
$container->expects($this->exactly(2))
|
||||
->method('get')
|
||||
->with('auth')
|
||||
->willReturn($auth);
|
||||
|
||||
$hasPermissions = [];
|
||||
$auth->expects($this->atLeastOnce())
|
||||
->method('can')
|
||||
->willReturnCallback(function ($permission) use (&$hasPermissions) {
|
||||
return in_array($permission, $hasPermissions);
|
||||
});
|
||||
|
||||
$hasPermissions = ['foo', 'test', 'user'];
|
||||
$class->setPermissions([
|
||||
'foo',
|
||||
'loremIpsumAction' => ['dolor', 'sit'],
|
||||
'actionStub' => ['test'],
|
||||
'user',
|
||||
]);
|
||||
$middleware->process($request, $handler);
|
||||
|
||||
$class->setPermissions(array_merge(['not.existing.permission'], $hasPermissions));
|
||||
$this->expectException(HttpForbidden::class);
|
||||
$middleware->process($request, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Engelsystem\Test\Unit\Middleware\Stub;
|
||||
|
||||
use Engelsystem\Controllers\BaseController;
|
||||
|
||||
class ControllerImplementation extends BaseController
|
||||
{
|
||||
/**
|
||||
* @param array $permissions
|
||||
*/
|
||||
public function setPermissions(array $permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function actionStub()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue