From 94214b867ab05c507c70a010e994745a6630de7c Mon Sep 17 00:00:00 2001 From: Bernd Bestel Date: Mon, 19 Oct 2020 18:38:12 +0200 Subject: [PATCH] More authentication refactoring to also provide "plugable" credentials handling (references #921, needed for #305) --- app.php | 7 +--- controllers/LoginController.php | 46 ++++------------------- middleware/ApiKeyAuthMiddleware.php | 5 +++ middleware/AuthMiddleware.php | 13 +++++++ middleware/DefaultAuthMiddleware.php | 37 ++++++++++++++++++ middleware/ReverseProxyAuthMiddleware.php | 5 +++ middleware/SessionAuthMiddleware.php | 12 +++--- routes.php | 6 +-- services/SessionService.php | 2 + 9 files changed, 81 insertions(+), 52 deletions(-) diff --git a/app.php b/app.php index 0b2004b6..8176ebec 100644 --- a/app.php +++ b/app.php @@ -11,9 +11,7 @@ require_once __DIR__ . '/vendor/autoload.php'; // Load config files require_once GROCY_DATAPATH . '/config.php'; -require_once __DIR__ . '/config-dist.php'; - -// For not in own config defined values we use the default ones +require_once __DIR__ . '/config-dist.php'; // For not in own config defined values we use the default ones // Definitions for dev/demo/prerelease mode if ((GROCY_MODE === 'dev' || GROCY_MODE === 'demo' || GROCY_MODE === 'prerelease') && !defined('GROCY_USER_ID')) @@ -41,9 +39,6 @@ $container = $app->getContainer(); $container->set('view', function (Container $container) { return new Slim\Views\Blade(__DIR__ . '/views', GROCY_DATAPATH . '/viewcache'); }); -$container->set('LoginControllerInstance', function (Container $container) { - return new LoginController($container, 'grocy_session'); -}); $container->set('UrlManager', function (Container $container) { return new UrlManager(GROCY_BASE_URL); }); diff --git a/controllers/LoginController.php b/controllers/LoginController.php index 5e61535e..1182e2cc 100644 --- a/controllers/LoginController.php +++ b/controllers/LoginController.php @@ -2,13 +2,13 @@ namespace Grocy\Controllers; +use Grocy\Services\SessionService; + class LoginController extends BaseController { - protected $SessionCookieName; - - public function GetSessionCookieName() + public function __construct(\DI\Container $container) { - return $this->SessionCookieName; + parent::__construct($container); } public function LoginPage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) @@ -18,50 +18,20 @@ class LoginController extends BaseController public function Logout(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - $this->getSessionService()->RemoveSession($_COOKIE[$this->SessionCookieName]); + $this->getSessionService()->RemoveSession($_COOKIE[SessionService::SESSION_COOKIE_NAME]); return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/')); } public function ProcessLogin(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) { - $postParams = $this->GetParsedAndFilteredRequestBody($request); - - if (isset($postParams['username']) && isset($postParams['password'])) + $authMiddlewareClass = GROCY_AUTH_CLASS; + if ($authMiddlewareClass::ProcessLogin($this->GetParsedAndFilteredRequestBody($request))) { - $user = $this->getDatabase()->users()->where('username', $postParams['username'])->fetch(); - $inputPassword = $postParams['password']; - $stayLoggedInPermanently = $postParams['stay_logged_in'] == 'on'; - - if ($user !== null && password_verify($inputPassword, $user->password)) - { - $sessionKey = $this->getSessionService()->CreateSession($user->id, $stayLoggedInPermanently); - setcookie($this->SessionCookieName, $sessionKey, PHP_INT_SIZE == 4 ? PHP_INT_MAX : PHP_INT_MAX >> 32); - - // Cookie expires never, but session validity is up to SessionService - - if (password_needs_rehash($user->password, PASSWORD_DEFAULT)) - { - $user->update([ - 'password' => password_hash($inputPassword, PASSWORD_DEFAULT) - ]); - } - - return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/')); - } - else - { - return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/login?invalid=true')); - } + return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/')); } else { return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/login?invalid=true')); } } - - public function __construct(\DI\Container $container, string $sessionCookieName) - { - parent::__construct($container); - $this->SessionCookieName = $sessionCookieName; - } } diff --git a/middleware/ApiKeyAuthMiddleware.php b/middleware/ApiKeyAuthMiddleware.php index a6e1771f..d61044ad 100644 --- a/middleware/ApiKeyAuthMiddleware.php +++ b/middleware/ApiKeyAuthMiddleware.php @@ -72,4 +72,9 @@ class ApiKeyAuthMiddleware extends AuthMiddleware return null; } } + + public static function ProcessLogin(array $postParams) + { + throw new \Exception('Not implemented'); + } } diff --git a/middleware/AuthMiddleware.php b/middleware/AuthMiddleware.php index 5634b51f..922a629b 100644 --- a/middleware/AuthMiddleware.php +++ b/middleware/AuthMiddleware.php @@ -76,6 +76,19 @@ abstract class AuthMiddleware extends BaseMiddleware } } + protected static function SetSessionCookie($sessionKey) + { + // Cookie never expires, session validity is up to SessionService + setcookie(SessionService::SESSION_COOKIE_NAME, $sessionKey, PHP_INT_SIZE == 4 ? PHP_INT_MAX : PHP_INT_MAX >> 32); + } + + /** + * @param array $postParams + * @return bool True/False if the provided credentials were valid + * @throws \Exception Throws an \Exception if an error happended during credentials processing or if this AuthMiddleware doesn't provide credentials processing (e. g. handles this externally) + */ + abstract public static function ProcessLogin(array $postParams); + /** * @param Request $request * @return mixed|null the user row or null if the request is not authenticated diff --git a/middleware/DefaultAuthMiddleware.php b/middleware/DefaultAuthMiddleware.php index 1de86965..03a4a87f 100644 --- a/middleware/DefaultAuthMiddleware.php +++ b/middleware/DefaultAuthMiddleware.php @@ -2,6 +2,8 @@ namespace Grocy\Middleware; +use Grocy\Services\DatabaseService; +use Grocy\Services\SessionService; use Psr\Http\Message\ServerRequestInterface as Request; class DefaultAuthMiddleware extends AuthMiddleware @@ -22,4 +24,39 @@ class DefaultAuthMiddleware extends AuthMiddleware $user = $auth->authenticate($request); return $user; } + + public static function ProcessLogin(array $postParams) + { + if (isset($postParams['username']) && isset($postParams['password'])) + { + $db = DatabaseService::getInstance()->GetDbConnection(); + + $user = $db->users()->where('username', $postParams['username'])->fetch(); + $inputPassword = $postParams['password']; + $stayLoggedInPermanently = $postParams['stay_logged_in'] == 'on'; + + if ($user !== null && password_verify($inputPassword, $user->password)) + { + $sessionKey = SessionService::getInstance()->CreateSession($user->id, $stayLoggedInPermanently); + parent::SetSessionCookie($sessionKey); + + if (password_needs_rehash($user->password, PASSWORD_DEFAULT)) + { + $user->update([ + 'password' => password_hash($inputPassword, PASSWORD_DEFAULT) + ]); + } + + return true; + } + else + { + return false; + } + } + else + { + return false; + } + } } diff --git a/middleware/ReverseProxyAuthMiddleware.php b/middleware/ReverseProxyAuthMiddleware.php index 0de90b31..190da80c 100644 --- a/middleware/ReverseProxyAuthMiddleware.php +++ b/middleware/ReverseProxyAuthMiddleware.php @@ -36,4 +36,9 @@ class ReverseProxyAuthMiddleware extends AuthMiddleware return $user; } + + public static function ProcessLogin(array $postParams) + { + throw new \Exception('Not implemented'); + } } diff --git a/middleware/SessionAuthMiddleware.php b/middleware/SessionAuthMiddleware.php index a0d3b104..1b4eca5c 100644 --- a/middleware/SessionAuthMiddleware.php +++ b/middleware/SessionAuthMiddleware.php @@ -8,12 +8,9 @@ use Psr\Http\Message\ServerRequestInterface as Request; class SessionAuthMiddleware extends AuthMiddleware { - protected $SessionCookieName; - public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory) { parent::__construct($container, $responseFactory); - $this->SessionCookieName = $this->AppContainer->get('LoginControllerInstance')->GetSessionCookieName(); } public function authenticate(Request $request) @@ -25,13 +22,18 @@ class SessionAuthMiddleware extends AuthMiddleware $sessionService = SessionService::getInstance(); - if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) + if (!isset($_COOKIE[SessionService::SESSION_COOKIE_NAME]) || !$sessionService->IsValidSession($_COOKIE[SessionService::SESSION_COOKIE_NAME])) { return null; } else { - return $sessionService->GetUserBySessionKey($_COOKIE[$this->SessionCookieName]); + return $sessionService->GetUserBySessionKey($_COOKIE[SessionService::SESSION_COOKIE_NAME]); } } + + public static function ProcessLogin(array $postParams) + { + throw new \Exception('Not implemented'); + } } diff --git a/routes.php b/routes.php index 2bf90ac3..6334a857 100644 --- a/routes.php +++ b/routes.php @@ -13,9 +13,9 @@ $app->group('', function (RouteCollectorProxy $group) { $group->get('/barcodescannertesting', '\Grocy\Controllers\SystemController:BarcodeScannerTesting'); // Login routes - $group->get('/login', 'LoginControllerInstance:LoginPage')->setName('login'); - $group->post('/login', 'LoginControllerInstance:ProcessLogin')->setName('login'); - $group->get('/logout', 'LoginControllerInstance:Logout'); + $group->get('/login', '\Grocy\Controllers\LoginController:LoginPage')->setName('login'); + $group->post('/login', '\Grocy\Controllers\LoginController:ProcessLogin')->setName('login'); + $group->get('/logout', '\Grocy\Controllers\LoginController:Logout'); // Generic entity interaction $group->get('/userfields', '\Grocy\Controllers\GenericEntityController:UserfieldsList'); diff --git a/services/SessionService.php b/services/SessionService.php index d1deaf26..d7033b7c 100644 --- a/services/SessionService.php +++ b/services/SessionService.php @@ -4,6 +4,8 @@ namespace Grocy\Services; class SessionService extends BaseService { + const SESSION_COOKIE_NAME = 'grocy_session'; + /** * @return string */