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 # places where user context is needed will then use the default (first existing) user
Setting('DISABLE_AUTH', false); 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) # 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); Setting('DISABLE_BROWSER_BARCODE_CAMERA_SCANNING', false);

View File

@ -1,105 +1,63 @@
<?php <?php
namespace Grocy\Middleware; namespace Grocy\Middleware;
use Grocy\Services\ApiKeyService;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestInterface as Request; 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 Slim\Routing\RouteContext;
use Grocy\Services\SessionService; class ApiKeyAuthMiddleware extends AuthMiddleware
use Grocy\Services\ApiKeyService;
class ApiKeyAuthMiddleware extends BaseMiddleware
{ {
public function __construct(\DI\Container $container, string $sessionCookieName, string $apiKeyHeaderName) public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
{ {
parent::__construct($container); parent::__construct($container, $responseFactory);
$this->SessionCookieName = $sessionCookieName; $this->ApiKeyHeaderName = $this->AppContainer->get('ApiKeyHeaderName');
$this->ApiKeyHeaderName = $apiKeyHeaderName; }
}
protected $SessionCookieName; protected $ApiKeyHeaderName;
protected $ApiKeyHeaderName;
public function __invoke(Request $request, RequestHandler $handler): Response function authenticate(Request $request)
{ {
$routeContext = RouteContext::fromRequest($request); $routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute(); $route = $routeContext->getRoute();
$routeName = $route->getName(); $routeName = $route->getName();
if (GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease' || GROCY_IS_EMBEDDED_INSTALL || GROCY_DISABLE_AUTH) $validApiKey = true;
{ $usedApiKey = null;
define('GROCY_AUTHENTICATED', true);
$response = $handler->handle($request);
}
else
{
$validSession = true;
$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 // First check of the API key in the configured header
if (!$request->hasHeader($this->ApiKeyHeaderName) || !$apiKeyService->IsValidApiKey($request->getHeaderLine($this->ApiKeyHeaderName))) if (!$request->hasHeader($this->ApiKeyHeaderName) || !$apiKeyService->IsValidApiKey($request->getHeaderLine($this->ApiKeyHeaderName))) {
{ $validApiKey = false;
$validApiKey = false; } else {
} $usedApiKey = $request->getHeaderLine($this->ApiKeyHeaderName);
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) // 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))) if (!$validApiKey && !empty($request->getQueryParam($this->ApiKeyHeaderName)) && $apiKeyService->IsValidApiKey($request->getQueryParam($this->ApiKeyHeaderName))) {
{ $validApiKey = true;
$validApiKey = true; $usedApiKey = $request->getQueryParam($this->ApiKeyHeaderName);
$usedApiKey = $request->getQueryParam($this->ApiKeyHeaderName); }
}
// Handling of special purpose API keys // Handling of special purpose API keys
if (!$validApiKey) if (!$validApiKey) {
{ if ($routeName === 'calendar-ical') {
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 ($request->getQueryParam('secret') !== null && $apiKeyService->IsValidApiKey($request->getQueryParam('secret'), ApiKeyService::API_KEY_TYPE_SPECIAL_PURPOSE_CALENDAR_ICAL)) }
{ }
$validApiKey = true; }
}
}
}
if (!$validSession && !$validApiKey) if ($validApiKey) {
{ return $apiKeyService->GetUserByApiKey($usedApiKey);
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);
$response = $handler->handle($request); } else {
} return null;
elseif ($validSession) }
{ }
$user = $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_ID', $user->id);
$response = $handler->handle($request);
}
}
return $response;
}
} }

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 <?php
namespace Grocy\Middleware; 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\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) public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
{ {
parent::__construct($container); parent::__construct($container, $responseFactory);
$this->SessionCookieName = $sessionCookieName; $this->SessionCookieName = $this->AppContainer->get('LoginControllerInstance')->GetSessionCookieName();
$this->ResponseFactory = $responseFactory; }
}
protected $SessionCookieName; protected $SessionCookieName;
protected $ResponseFactory;
public function __invoke(Request $request, RequestHandler $handler): Response function authenticate(Request $request)
{ {
$routeContext = RouteContext::fromRequest($request); $sessionService = SessionService::getInstance();
$route = $routeContext->getRoute(); if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) {
$routeName = $route->getName(); return null;
$sessionService = SessionService::getInstance(); } else {
return $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
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;
}
} }

View File

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