Implemented controller permissions
This commit is contained in:
parent
55beca95cd
commit
c9d7e88cc7
|
@ -4,5 +4,16 @@ namespace Engelsystem\Controllers;
|
||||||
|
|
||||||
abstract class BaseController
|
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::class, $authenticator);
|
||||||
$this->app->instance('authenticator', $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) {
|
foreach ($uploadedFiles as $file) {
|
||||||
/** @var UploadedFileInterface $file */
|
/** @var UploadedFileInterface $file */
|
||||||
$filename = tempnam(sys_get_temp_dir(), 'upload');
|
$filename = tempnam(sys_get_temp_dir(), 'upload');
|
||||||
$handle = fopen($filename, "w");
|
$handle = fopen($filename, 'w');
|
||||||
fwrite($handle, $file->getStream()->getContents());
|
fwrite($handle, $file->getStream()->getContents());
|
||||||
fclose($handle);
|
fclose($handle);
|
||||||
|
|
||||||
|
|
|
@ -74,4 +74,12 @@ class CallableHandler implements MiddlewareInterface, RequestHandlerInterface
|
||||||
$response = $this->container->get('response');
|
$response = $this->container->get('response');
|
||||||
return $response->withContent($return);
|
return $response->withContent($return);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return callable
|
||||||
|
*/
|
||||||
|
public function getCallable()
|
||||||
|
{
|
||||||
|
return $this->callable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@
|
||||||
namespace Engelsystem\Middleware;
|
namespace Engelsystem\Middleware;
|
||||||
|
|
||||||
use Engelsystem\Application;
|
use Engelsystem\Application;
|
||||||
|
use Engelsystem\Controllers\BaseController;
|
||||||
|
use Engelsystem\Helpers\Authenticator;
|
||||||
|
use Engelsystem\Http\Exceptions\HttpForbidden;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
@ -37,6 +40,14 @@ class RequestHandler implements MiddlewareInterface
|
||||||
$requestHandler = $request->getAttribute('route-request-handler');
|
$requestHandler = $request->getAttribute('route-request-handler');
|
||||||
$requestHandler = $this->resolveRequestHandler($requestHandler);
|
$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) {
|
if ($requestHandler instanceof MiddlewareInterface) {
|
||||||
return $requestHandler->process($request, $handler);
|
return $requestHandler->process($request, $handler);
|
||||||
}
|
}
|
||||||
|
@ -49,6 +60,8 @@ class RequestHandler implements MiddlewareInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Resolve the given class
|
||||||
|
*
|
||||||
* @param string|callable|MiddlewareInterface|RequestHandlerInterface $handler
|
* @param string|callable|MiddlewareInterface|RequestHandlerInterface $handler
|
||||||
* @return MiddlewareInterface|RequestHandlerInterface
|
* @return MiddlewareInterface|RequestHandlerInterface
|
||||||
*/
|
*/
|
||||||
|
@ -76,4 +89,36 @@ class RequestHandler implements MiddlewareInterface
|
||||||
|
|
||||||
return $this->resolveMiddleware($handler);
|
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::class));
|
||||||
$this->assertInstanceOf(Authenticator::class, $app->get('authenticator'));
|
$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\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use ReflectionClass as Reflection;
|
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
class CallableHandlerTest extends TestCase
|
class CallableHandlerTest extends TestCase
|
||||||
|
@ -28,17 +27,14 @@ class CallableHandlerTest extends TestCase
|
||||||
/**
|
/**
|
||||||
* @dataProvider provideCallable
|
* @dataProvider provideCallable
|
||||||
* @covers \Engelsystem\Middleware\CallableHandler::__construct
|
* @covers \Engelsystem\Middleware\CallableHandler::__construct
|
||||||
|
* @covers \Engelsystem\Middleware\CallableHandler::getCallable
|
||||||
* @param callable $callable
|
* @param callable $callable
|
||||||
*/
|
*/
|
||||||
public function testInit($callable)
|
public function testInit($callable)
|
||||||
{
|
{
|
||||||
$handler = new CallableHandler($callable);
|
$handler = new CallableHandler($callable);
|
||||||
|
|
||||||
$reflection = new Reflection(get_class($handler));
|
$this->assertEquals($callable, $handler->getCallable());
|
||||||
$property = $reflection->getProperty('callable');
|
|
||||||
$property->setAccessible(true);
|
|
||||||
|
|
||||||
$this->assertEquals($callable, $property->getValue($handler));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,7 +3,11 @@
|
||||||
namespace Engelsystem\Test\Unit\Middleware;
|
namespace Engelsystem\Test\Unit\Middleware;
|
||||||
|
|
||||||
use Engelsystem\Application;
|
use Engelsystem\Application;
|
||||||
|
use Engelsystem\Helpers\Authenticator;
|
||||||
|
use Engelsystem\Http\Exceptions\HttpForbidden;
|
||||||
|
use Engelsystem\Middleware\CallableHandler;
|
||||||
use Engelsystem\Middleware\RequestHandler;
|
use Engelsystem\Middleware\RequestHandler;
|
||||||
|
use Engelsystem\Test\Unit\Middleware\Stub\ControllerImplementation;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use PHPUnit\Framework\MockObject\MockObject;
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
@ -131,6 +135,79 @@ class RequestHandlerTest extends TestCase
|
||||||
$this->assertEquals($return, $response);
|
$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
|
* @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