From 41d224b8319f74718476b37a968999ec26125bb2 Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Wed, 19 Aug 2020 19:17:38 +0200 Subject: [PATCH] Review --- app.php | 2 + config-dist.php | 10 +++-- middleware/ApiKeyAuthMiddleware.php | 39 +++++++++++------- middleware/AuthMiddleware.php | 49 ++++++++++++++++------- middleware/DefaultAuthMiddleware.php | 11 +++-- middleware/ProxyAuthMiddleware.php | 39 ------------------ middleware/ReverseProxyAuthMiddleware.php | 40 ++++++++++++++++++ middleware/SessionAuthMiddleware.php | 18 ++++++--- routes.php | 8 ++-- 9 files changed, 129 insertions(+), 87 deletions(-) delete mode 100644 middleware/ProxyAuthMiddleware.php create mode 100644 middleware/ReverseProxyAuthMiddleware.php diff --git a/app.php b/app.php index 112d84dd..c036c41b 100644 --- a/app.php +++ b/app.php @@ -19,6 +19,7 @@ require_once __DIR__ . '/config-dist.php'; // For not in own config defined valu if ((GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease') && !defined('GROCY_USER_ID')) { define('GROCY_USER_ID', 1); + define('GROCY_SHOW_AUTH_VIEWS', true); } // Definitions for disabled authentication mode @@ -28,6 +29,7 @@ if (GROCY_DISABLE_AUTH === true) { define('GROCY_USER_ID', 1); } + define('GROCY_SHOW_AUTH_VIEWS', false); } // Setup base application diff --git a/config-dist.php b/config-dist.php index 792a9604..a37600ad 100644 --- a/config-dist.php +++ b/config-dist.php @@ -66,11 +66,13 @@ 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'); +# Either "Grocy\Middleware\DefaultAuthMiddleware", "Grocy\Middleware\ReverseProxyAuthMiddleware" +# or any class that implements Grocy\Middleware\AuthMiddleware +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'); +# When using ReverseProxyAuthMiddleware, +# the name of the HTTP header which your reverse proxy uses to pass the username (on successful authentication) +Setting('REVERSE_PROXY_AUTH_HEADER', 'REMOTE_USER'); # 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); diff --git a/middleware/ApiKeyAuthMiddleware.php b/middleware/ApiKeyAuthMiddleware.php index b860c1ab..3f1679da 100644 --- a/middleware/ApiKeyAuthMiddleware.php +++ b/middleware/ApiKeyAuthMiddleware.php @@ -1,14 +1,13 @@ getRoute(); @@ -30,36 +32,45 @@ class ApiKeyAuthMiddleware extends AuthMiddleware $validApiKey = true; $usedApiKey = null; - $apiKeyService = new ApiKeyService(); // 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; - } else { + } + 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))) { + 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)) { + 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 ($validApiKey) { + if ($validApiKey) + { return $apiKeyService->GetUserByApiKey($usedApiKey); - } else { + } + else + { return null; } } -} \ No newline at end of file +} diff --git a/middleware/AuthMiddleware.php b/middleware/AuthMiddleware.php index d5f8bbc4..7c7e1afa 100644 --- a/middleware/AuthMiddleware.php +++ b/middleware/AuthMiddleware.php @@ -1,18 +1,15 @@ getRoute(); $routeName = $route->getName(); - if ($routeName === 'root') { + $isApiRoute = string_starts_with($request->getUri()->getPath(), '/api/'); + + if ($routeName === 'root') + { return $handler->handle($request); } - if ($routeName === 'login') { + else 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) { + if (GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease' || GROCY_IS_EMBEDDED_INSTALL || GROCY_DISABLE_AUTH) + { + $sessionService = SessionService::getInstance(); + $user = $sessionService->GetDefaultUser(); + define('GROCY_AUTHENTICATED', true); + define('GROCY_USER_USERNAME', $user->username); + return $handler->handle($request); - } else { + } + else + { $user = $this->authenticate($request); - if ($user === null) { + + if ($user === null) + { define('GROCY_AUTHENTICATED', false); + $response = $this->ResponseFactory->createResponse(); - return $response->withHeader('Location', $this->AppContainer->get('UrlManager')->ConstructUrl('/login')); - } else { + if ($isApiRoute) + { + return $response->withStatus(401); + } + else + { + 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); } } @@ -61,5 +81,4 @@ abstract class AuthMiddleware extends BaseMiddleware * @throws \Exception Throws an \Exception if config is invalid. */ protected abstract function authenticate(Request $request); - -} \ No newline at end of file +} diff --git a/middleware/DefaultAuthMiddleware.php b/middleware/DefaultAuthMiddleware.php index 8994b914..cb28e934 100644 --- a/middleware/DefaultAuthMiddleware.php +++ b/middleware/DefaultAuthMiddleware.php @@ -1,25 +1,24 @@ AppContainer, $this->ResponseFactory); $user = $auth->authenticate($request); if ($user !== null) + { return $user; + } + // Then by session cookie $auth = new SessionAuthMiddleware($this->AppContainer, $this->ResponseFactory); $user = $auth->authenticate($request); - return $user; - } -} \ No newline at end of file +} diff --git a/middleware/ProxyAuthMiddleware.php b/middleware/ProxyAuthMiddleware.php deleted file mode 100644 index cbe99042..00000000 --- a/middleware/ProxyAuthMiddleware.php +++ /dev/null @@ -1,39 +0,0 @@ -GetDbConnection(); - - $username = $request->getHeader(GROCY_PROXY_AUTH_HEADER); - error_log(var_dump($username)); - if (count($username) != 1) { - // Invalid configuration of Proxy - throw new \Exception("Invalid Username from Proxy " . var_dump($username)); - } - - $username = $username[0]; - - $user = $db->users()->where('username', $username)->fetch(); - if ($user == null) { - $user = UsersService::getInstance()->CreateUser( - $username, - '', - '', - '' - ); - } - return $user; - } -} \ No newline at end of file diff --git a/middleware/ReverseProxyAuthMiddleware.php b/middleware/ReverseProxyAuthMiddleware.php new file mode 100644 index 00000000..a0536ca4 --- /dev/null +++ b/middleware/ReverseProxyAuthMiddleware.php @@ -0,0 +1,40 @@ +GetDbConnection(); + + $username = $request->getHeader(GROCY_REVERSE_PROXY_AUTH_HEADER); + + if (count($username) !== 1) + { + // Invalid configuration of Proxy + throw new \Exception("ReverseProxyAuthMiddleware: Invalid username from proxy: " . var_dump($username)); + } + + $username = $username[0]; + + $user = $db->users()->where('username', $username)->fetch(); + + if ($user == null) + { + $user = UsersService::getInstance()->CreateUser($username, '', '', ''); + } + + return $user; + } +} diff --git a/middleware/SessionAuthMiddleware.php b/middleware/SessionAuthMiddleware.php index 878eed15..aa0b2de6 100644 --- a/middleware/SessionAuthMiddleware.php +++ b/middleware/SessionAuthMiddleware.php @@ -3,11 +3,11 @@ namespace Grocy\Middleware; - -use Grocy\Services\SessionService; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ServerRequestInterface as Request; +use Grocy\Services\SessionService; + class SessionAuthMiddleware extends AuthMiddleware { public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory) @@ -20,13 +20,19 @@ class SessionAuthMiddleware extends AuthMiddleware function authenticate(Request $request) { - define('GROCY_SHOW_AUTH_VIEWS', true); + if (!defined('GROCY_SHOW_AUTH_VIEWS')) + { + define('GROCY_SHOW_AUTH_VIEWS', true); + } $sessionService = SessionService::getInstance(); - if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) { + if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) + { return null; - } else { + } + else + { return $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]); } } -} \ No newline at end of file +} diff --git a/routes.php b/routes.php index 7ef190be..ff3d5d06 100644 --- a/routes.php +++ b/routes.php @@ -7,7 +7,9 @@ use Slim\Routing\RouteCollectorProxy; use Grocy\Middleware\JsonMiddleware; use Grocy\Middleware\CorsMiddleware; -$auth_middleware = GROCY_AUTH_CLASS; + +$authMiddlewareClass = GROCY_AUTH_CLASS; + $app->group('', function(RouteCollectorProxy $group) { // System routes @@ -132,7 +134,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 $auth_middleware($container, $app->getResponseFactory())); +})->add(new $authMiddlewareClass($container, $app->getResponseFactory())); $app->group('/api', function(RouteCollectorProxy $group) { @@ -252,7 +254,7 @@ $app->group('/api', function(RouteCollectorProxy $group) $group->get('/calendar/ical/sharing-link', '\Grocy\Controllers\CalendarApiController:IcalSharingLink'); } })->add(JsonMiddleware::class) -->add(new $auth_middleware($container, $app->getResponseFactory())); +->add(new $authMiddlewareClass($container, $app->getResponseFactory())); // Handle CORS preflight OPTIONS requests $app->options('/api/{routes:.+}', function(Request $request, Response $response): Response