This commit is contained in:
Bernd Bestel 2020-08-19 19:17:38 +02:00
parent 3a8654ffb6
commit 41d224b831
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
9 changed files with 129 additions and 87 deletions

View File

@ -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')) if ((GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease') && !defined('GROCY_USER_ID'))
{ {
define('GROCY_USER_ID', 1); define('GROCY_USER_ID', 1);
define('GROCY_SHOW_AUTH_VIEWS', true);
} }
// Definitions for disabled authentication mode // Definitions for disabled authentication mode
@ -28,6 +29,7 @@ if (GROCY_DISABLE_AUTH === true)
{ {
define('GROCY_USER_ID', 1); define('GROCY_USER_ID', 1);
} }
define('GROCY_SHOW_AUTH_VIEWS', false);
} }
// Setup base application // Setup base application

View File

@ -66,11 +66,13 @@ 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);
# # Either "Grocy\Middleware\DefaultAuthMiddleware", "Grocy\Middleware\ReverseProxyAuthMiddleware"
Setting('AUTH_CLASS', '\\Grocy\\Middleware\\DefaultAuthMiddleware'); # 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) # When using ReverseProxyAuthMiddleware,
Setting('PROXY_AUTH_HEADER', 'X-Username'); # 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) # 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,14 +1,13 @@
<?php <?php
namespace Grocy\Middleware; namespace Grocy\Middleware;
use Grocy\Services\ApiKeyService;
use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Routing\RouteContext; use Slim\Routing\RouteContext;
use Grocy\Services\ApiKeyService;
class ApiKeyAuthMiddleware extends AuthMiddleware class ApiKeyAuthMiddleware extends AuthMiddleware
{ {
public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory) public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
@ -21,7 +20,10 @@ class ApiKeyAuthMiddleware extends AuthMiddleware
function authenticate(Request $request) function authenticate(Request $request)
{ {
define('GROCY_SHOW_AUTH_VIEWS', true); if (!defined('GROCY_SHOW_AUTH_VIEWS'))
{
define('GROCY_SHOW_AUTH_VIEWS', true);
}
$routeContext = RouteContext::fromRequest($request); $routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute(); $route = $routeContext->getRoute();
@ -30,36 +32,45 @@ class ApiKeyAuthMiddleware extends AuthMiddleware
$validApiKey = true; $validApiKey = true;
$usedApiKey = null; $usedApiKey = null;
$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 { }
else
{
$usedApiKey = $request->getHeaderLine($this->ApiKeyHeaderName); $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 ($request->getQueryParam('secret') !== null && $apiKeyService->IsValidApiKey($request->getQueryParam('secret'), ApiKeyService::API_KEY_TYPE_SPECIAL_PURPOSE_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; $validApiKey = true;
} }
} }
} }
if ($validApiKey) { if ($validApiKey)
{
return $apiKeyService->GetUserByApiKey($usedApiKey); return $apiKeyService->GetUserByApiKey($usedApiKey);
} else { }
else
{
return null; return null;
} }
} }
} }

View File

@ -1,18 +1,15 @@
<?php <?php
namespace Grocy\Middleware; namespace Grocy\Middleware;
use Grocy\Auth\ApiKeyAuthProvider;
use Grocy\Auth\ProxyAuthProvider;
use Grocy\Auth\SessionAuthProvider;
use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler; use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Routing\RouteContext; use Slim\Routing\RouteContext;
use Grocy\Services\SessionService;
abstract class AuthMiddleware extends BaseMiddleware abstract class AuthMiddleware extends BaseMiddleware
{ {
public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory) public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
@ -28,28 +25,51 @@ abstract class AuthMiddleware extends BaseMiddleware
$routeContext = RouteContext::fromRequest($request); $routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute(); $route = $routeContext->getRoute();
$routeName = $route->getName(); $routeName = $route->getName();
if ($routeName === 'root') { $isApiRoute = string_starts_with($request->getUri()->getPath(), '/api/');
if ($routeName === 'root')
{
return $handler->handle($request); return $handler->handle($request);
} }
if ($routeName === 'login') { else if ($routeName === 'login')
{
define('GROCY_AUTHENTICATED', false); define('GROCY_AUTHENTICATED', false);
return $handler->handle($request); 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_AUTHENTICATED', true);
define('GROCY_USER_USERNAME', $user->username);
return $handler->handle($request); return $handler->handle($request);
} else { }
else
{
$user = $this->authenticate($request); $user = $this->authenticate($request);
if ($user === null) {
if ($user === null)
{
define('GROCY_AUTHENTICATED', false); define('GROCY_AUTHENTICATED', false);
$response = $this->ResponseFactory->createResponse(); $response = $this->ResponseFactory->createResponse();
return $response->withHeader('Location', $this->AppContainer->get('UrlManager')->ConstructUrl('/login')); if ($isApiRoute)
} else { {
return $response->withStatus(401);
}
else
{
return $response->withHeader('Location', $this->AppContainer->get('UrlManager')->ConstructUrl('/login'));
}
}
else
{
define('GROCY_AUTHENTICATED', true); define('GROCY_AUTHENTICATED', true);
define('GROCY_USER_ID', $user->id); define('GROCY_USER_ID', $user->id);
define('GROCY_USER_USERNAME', $user->username); define('GROCY_USER_USERNAME', $user->username);
return $response = $handler->handle($request); return $response = $handler->handle($request);
} }
} }
@ -61,5 +81,4 @@ abstract class AuthMiddleware extends BaseMiddleware
* @throws \Exception Throws an \Exception if config is invalid. * @throws \Exception Throws an \Exception if config is invalid.
*/ */
protected abstract function authenticate(Request $request); protected abstract function authenticate(Request $request);
}
}

View File

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

View File

@ -1,39 +0,0 @@
<?php
namespace Grocy\Middleware;
use Grocy\Services\DatabaseService;
use Grocy\Services\UsersService;
use Psr\Http\Message\ServerRequestInterface as Request;
class ProxyAuthMiddleware extends AuthMiddleware
{
function authenticate(Request $request)
{
define('GROCY_SHOW_AUTH_VIEWS', false);
$db = DatabaseService::getInstance()->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;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace Grocy\Middleware;
use Psr\Http\Message\ServerRequestInterface as Request;
use Grocy\Services\DatabaseService;
use Grocy\Services\UsersService;
class ReverseProxyAuthMiddleware extends AuthMiddleware
{
function authenticate(Request $request)
{
if (!defined('GROCY_SHOW_AUTH_VIEWS'))
{
define('GROCY_SHOW_AUTH_VIEWS', false);
}
$db = DatabaseService::getInstance()->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;
}
}

View File

@ -3,11 +3,11 @@
namespace Grocy\Middleware; namespace Grocy\Middleware;
use Grocy\Services\SessionService;
use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use Grocy\Services\SessionService;
class SessionAuthMiddleware extends AuthMiddleware class SessionAuthMiddleware extends AuthMiddleware
{ {
public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory) public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
@ -20,13 +20,19 @@ class SessionAuthMiddleware extends AuthMiddleware
function authenticate(Request $request) 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(); $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; return null;
} else { }
else
{
return $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]); return $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]);
} }
} }
} }

View File

@ -7,7 +7,9 @@ use Slim\Routing\RouteCollectorProxy;
use Grocy\Middleware\JsonMiddleware; use Grocy\Middleware\JsonMiddleware;
use Grocy\Middleware\CorsMiddleware; use Grocy\Middleware\CorsMiddleware;
$auth_middleware = GROCY_AUTH_CLASS;
$authMiddlewareClass = GROCY_AUTH_CLASS;
$app->group('', function(RouteCollectorProxy $group) $app->group('', function(RouteCollectorProxy $group)
{ {
// System routes // System routes
@ -132,7 +134,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 $auth_middleware($container, $app->getResponseFactory())); })->add(new $authMiddlewareClass($container, $app->getResponseFactory()));
$app->group('/api', function(RouteCollectorProxy $group) $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'); $group->get('/calendar/ical/sharing-link', '\Grocy\Controllers\CalendarApiController:IcalSharingLink');
} }
})->add(JsonMiddleware::class) })->add(JsonMiddleware::class)
->add(new $auth_middleware($container, $app->getResponseFactory())); ->add(new $authMiddlewareClass($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