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'))
{
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

View File

@ -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);

View File

@ -1,14 +1,13 @@
<?php
namespace Grocy\Middleware;
use Grocy\Services\ApiKeyService;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Routing\RouteContext;
use Grocy\Services\ApiKeyService;
class ApiKeyAuthMiddleware extends AuthMiddleware
{
public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
@ -21,7 +20,10 @@ class ApiKeyAuthMiddleware extends AuthMiddleware
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);
$route = $routeContext->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;
}
}
}
}

View File

@ -1,18 +1,15 @@
<?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;
use Grocy\Services\SessionService;
abstract class AuthMiddleware extends BaseMiddleware
{
public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
@ -28,28 +25,51 @@ abstract class AuthMiddleware extends BaseMiddleware
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->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);
}
}

View File

@ -1,25 +1,24 @@
<?php
namespace Grocy\Middleware;
use Psr\Http\Message\ServerRequestInterface as Request;
class DefaultAuthMiddleware extends AuthMiddleware
{
protected function authenticate(Request $request)
{
// First try to authenticate by API key
$auth = new ApiKeyAuthMiddleware($this->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;
}
}
}

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;
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]);
}
}
}
}

View File

@ -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