Refactor Authentication-Middlewares

This commit is contained in:
fipwmaqzufheoxq92ebc 2020-07-21 15:12:09 +02:00
parent 98f214e9f1
commit cfcb922e83
6 changed files with 165 additions and 155 deletions

View File

@ -66,6 +66,12 @@ Setting('ENTRY_PAGE', 'stock');
# places where user context is needed will then use the default (first existing) user
Setting('DISABLE_AUTH', false);
#
Setting('AUTH_CLASS', '\\Grocy\\Middleware\\DefaultAuthMiddleware');
# The Header-name with the username set by the reverse-proxy (case-insensitive)
Setting('PROXY_AUTH_HEADER', 'X-Username');
# Set this to true if you want to disable the ability to scan a barcode via the device camera (Browser API)
Setting('DISABLE_BROWSER_BARCODE_CAMERA_SCANNING', false);

View File

@ -1,105 +1,63 @@
<?php
namespace Grocy\Middleware;
use Grocy\Services\ApiKeyService;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteContext;
use Grocy\Services\SessionService;
use Grocy\Services\ApiKeyService;
class ApiKeyAuthMiddleware extends BaseMiddleware
class ApiKeyAuthMiddleware extends AuthMiddleware
{
public function __construct(\DI\Container $container, string $sessionCookieName, string $apiKeyHeaderName)
{
parent::__construct($container);
$this->SessionCookieName = $sessionCookieName;
$this->ApiKeyHeaderName = $apiKeyHeaderName;
}
public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
{
parent::__construct($container, $responseFactory);
$this->ApiKeyHeaderName = $this->AppContainer->get('ApiKeyHeaderName');
}
protected $SessionCookieName;
protected $ApiKeyHeaderName;
protected $ApiKeyHeaderName;
public function __invoke(Request $request, RequestHandler $handler): Response
{
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute();
$routeName = $route->getName();
function authenticate(Request $request)
{
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute();
$routeName = $route->getName();
if (GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease' || GROCY_IS_EMBEDDED_INSTALL || GROCY_DISABLE_AUTH)
{
define('GROCY_AUTHENTICATED', true);
$response = $handler->handle($request);
}
else
{
$validSession = true;
$validApiKey = true;
$usedApiKey = null;
$validApiKey = true;
$usedApiKey = null;
$sessionService = SessionService::getInstance();
if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName]))
{
$validSession = false;
}
$apiKeyService = new ApiKeyService();
$apiKeyService = new ApiKeyService();
// First check of the API key in the configured header
if (!$request->hasHeader($this->ApiKeyHeaderName) || !$apiKeyService->IsValidApiKey($request->getHeaderLine($this->ApiKeyHeaderName)))
{
$validApiKey = false;
}
else
{
$usedApiKey = $request->getHeaderLine($this->ApiKeyHeaderName);
}
// First check of the API key in the configured header
if (!$request->hasHeader($this->ApiKeyHeaderName) || !$apiKeyService->IsValidApiKey($request->getHeaderLine($this->ApiKeyHeaderName))) {
$validApiKey = false;
} else {
$usedApiKey = $request->getHeaderLine($this->ApiKeyHeaderName);
}
// Not recommended, but it's also possible to provide the API key via a query parameter (same name as the configured header)
if (!$validApiKey && !empty($request->getQueryParam($this->ApiKeyHeaderName)) && $apiKeyService->IsValidApiKey($request->getQueryParam($this->ApiKeyHeaderName)))
{
$validApiKey = true;
$usedApiKey = $request->getQueryParam($this->ApiKeyHeaderName);
}
// Not recommended, but it's also possible to provide the API key via a query parameter (same name as the configured header)
if (!$validApiKey && !empty($request->getQueryParam($this->ApiKeyHeaderName)) && $apiKeyService->IsValidApiKey($request->getQueryParam($this->ApiKeyHeaderName))) {
$validApiKey = true;
$usedApiKey = $request->getQueryParam($this->ApiKeyHeaderName);
}
// Handling of special purpose API keys
if (!$validApiKey)
{
if ($routeName === 'calendar-ical')
{
if ($request->getQueryParam('secret') !== null && $apiKeyService->IsValidApiKey($request->getQueryParam('secret'), ApiKeyService::API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL))
{
$validApiKey = true;
}
}
}
// Handling of special purpose API keys
if (!$validApiKey) {
if ($routeName === 'calendar-ical') {
if ($request->getQueryParam('secret') !== null && $apiKeyService->IsValidApiKey($request->getQueryParam('secret'), ApiKeyService::API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL)) {
$validApiKey = true;
}
}
}
if (!$validSession && !$validApiKey)
{
define('GROCY_AUTHENTICATED', false);
$response = new \Slim\Psr7\Response(); // No content when unauthorized
$response = $response->withStatus(401);
}
elseif ($validApiKey)
{
$user = $apiKeyService->GetUserByApiKey($usedApiKey);
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_ID', $user->id);
if ($validApiKey) {
return $apiKeyService->GetUserByApiKey($usedApiKey);
$response = $handler->handle($request);
}
elseif ($validSession)
{
$user = $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_ID', $user->id);
$response = $handler->handle($request);
}
}
return $response;
}
}
} else {
return null;
}
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace Grocy\Middleware;
use Grocy\Auth\ApiKeyAuthProvider;
use Grocy\Auth\ProxyAuthProvider;
use Grocy\Auth\SessionAuthProvider;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Routing\RouteContext;
abstract class AuthMiddleware extends BaseMiddleware
{
public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
{
parent::__construct($container);
$this->ResponseFactory = $responseFactory;
}
protected $ResponseFactory;
public function __invoke(Request $request, RequestHandler $handler): Response
{
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute();
$routeName = $route->getName();
if ($routeName === 'root') {
return $handler->handle($request);
}
if ($routeName === 'login') {
define('GROCY_AUTHENTICATED', false);
return $handler->handle($request);
}
if (GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease' || GROCY_IS_EMBEDDED_INSTALL || GROCY_DISABLE_AUTH) {
define('GROCY_AUTHENTICATED', true);
return $handler->handle($request);
} else {
$user = $this->authenticate($request);
if ($user === null) {
define('GROCY_AUTHENTICATED', false);
$response = $this->ResponseFactory->createResponse();
return $response->withHeader('Location', $this->AppContainer->get('UrlManager')->ConstructUrl('/login'));
} else {
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_ID', $user->id);
define('GROCY_USER_USERNAME', $user->username);
return $response = $handler->handle($request);
}
}
}
/**
* @param Request $request
* @return mixed|null the user row or null if the request is not authenticated
* @throws \Exception Throws an \Exception if config is invalid.
*/
protected abstract function authenticate(Request $request);
}

View File

@ -0,0 +1,25 @@
<?php
namespace Grocy\Middleware;
use Psr\Http\Message\ServerRequestInterface as Request;
class DefaultAuthMiddleware extends AuthMiddleware
{
protected function authenticate(Request $request)
{
$auth = new ApiKeyAuthMiddleware($this->AppContainer, $this->ResponseFactory);
$user = $auth->authenticate($request);
if ($user !== null)
return $user;
$auth = new SessionAuthMiddleware($this->AppContainer, $this->ResponseFactory);
$user = $auth->authenticate($request);
return $user;
}
}

View File

@ -1,73 +1,30 @@
<?php
namespace Grocy\Middleware;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteContext;
use Grocy\Services\SessionService;
use Grocy\Services\LocalizationService;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
class SessionAuthMiddleware extends BaseMiddleware
class SessionAuthMiddleware extends AuthMiddleware
{
public function __construct(\DI\Container $container, string $sessionCookieName, ResponseFactoryInterface $responseFactory)
{
parent::__construct($container);
$this->SessionCookieName = $sessionCookieName;
$this->ResponseFactory = $responseFactory;
}
public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
{
parent::__construct($container, $responseFactory);
$this->SessionCookieName = $this->AppContainer->get('LoginControllerInstance')->GetSessionCookieName();
}
protected $SessionCookieName;
protected $ResponseFactory;
protected $SessionCookieName;
public function __invoke(Request $request, RequestHandler $handler): Response
{
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute();
$routeName = $route->getName();
$sessionService = SessionService::getInstance();
if ($routeName === 'root')
{
$response = $handler->handle($request);
}
elseif (GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease' || GROCY_IS_EMBEDDED_INSTALL || GROCY_DISABLE_AUTH)
{
$user = $sessionService->GetDefaultUser();
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_USERNAME', $user->username);
$response = $handler->handle($request);
}
else
{
if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login')
{
define('GROCY_AUTHENTICATED', false);
$response = $this->ResponseFactory->createResponse();
return $response->withHeader('Location', $this->AppContainer->get('UrlManager')->ConstructUrl('/login'));
}
else
{
if ($routeName !== 'login')
{
$user = $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_USERNAME', $user->username);
define('GROCY_USER_ID', $user->id);
}
else
{
define('GROCY_AUTHENTICATED', false);
}
$response = $handler->handle($request);
}
}
return $response;
}
}
function authenticate(Request $request)
{
$sessionService = SessionService::getInstance();
if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) {
return null;
} else {
return $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
}
}
}

View File

@ -1,14 +1,13 @@
<?php
use Grocy\Middleware\AuthMiddleware;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteCollectorProxy;
use Grocy\Middleware\JsonMiddleware;
use Grocy\Middleware\CorsMiddleware;
use Grocy\Middleware\SessionAuthMiddleware;
use Grocy\Middleware\ApiKeyAuthMiddleware;
$auth_middleware = GROCY_AUTH_CLASS;
$app->group('', function(RouteCollectorProxy $group)
{
// System routes
@ -133,7 +132,7 @@ $app->group('', function(RouteCollectorProxy $group)
$group->get('/api', '\Grocy\Controllers\OpenApiController:DocumentationUi');
$group->get('/manageapikeys', '\Grocy\Controllers\OpenApiController:ApiKeysList');
$group->get('/manageapikeys/new', '\Grocy\Controllers\OpenApiController:CreateNewApiKey');
})->add(new SessionAuthMiddleware($container, $container->get('LoginControllerInstance')->GetSessionCookieName(), $app->getResponseFactory()));
})->add(new $auth_middleware($container, $app->getResponseFactory()));
$app->group('/api', function(RouteCollectorProxy $group)
{
@ -253,7 +252,7 @@ $app->group('/api', function(RouteCollectorProxy $group)
$group->get('/calendar/ical/sharing-link', '\Grocy\Controllers\CalendarApiController:IcalSharingLink');
}
})->add(JsonMiddleware::class)
->add(new ApiKeyAuthMiddleware($container, $container->get('LoginControllerInstance')->GetSessionCookieName(), $container->get('ApiKeyHeaderName')));
->add(new $auth_middleware($container, $app->getResponseFactory()));
// Handle CORS preflight OPTIONS requests
$app->options('/api/{routes:.+}', function(Request $request, Response $response): Response