More authentication refactoring to also provide "plugable" credentials handling (references #921, needed for #305)

This commit is contained in:
Bernd Bestel 2020-10-19 18:38:12 +02:00
parent 9f88dd3af3
commit 94214b867a
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
9 changed files with 81 additions and 52 deletions

View File

@ -11,9 +11,7 @@ require_once __DIR__ . '/vendor/autoload.php';
// Load config files // Load config files
require_once GROCY_DATAPATH . '/config.php'; require_once GROCY_DATAPATH . '/config.php';
require_once __DIR__ . '/config-dist.php'; require_once __DIR__ . '/config-dist.php'; // For not in own config defined values we use the default ones
// For not in own config defined values we use the default ones
// Definitions for dev/demo/prerelease mode // Definitions for dev/demo/prerelease mode
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'))
@ -41,9 +39,6 @@ $container = $app->getContainer();
$container->set('view', function (Container $container) { $container->set('view', function (Container $container) {
return new Slim\Views\Blade(__DIR__ . '/views', GROCY_DATAPATH . '/viewcache'); 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) { $container->set('UrlManager', function (Container $container) {
return new UrlManager(GROCY_BASE_URL); return new UrlManager(GROCY_BASE_URL);
}); });

View File

@ -2,13 +2,13 @@
namespace Grocy\Controllers; namespace Grocy\Controllers;
use Grocy\Services\SessionService;
class LoginController extends BaseController class LoginController extends BaseController
{ {
protected $SessionCookieName; public function __construct(\DI\Container $container)
public function GetSessionCookieName()
{ {
return $this->SessionCookieName; parent::__construct($container);
} }
public function LoginPage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) 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) 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('/')); return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/'));
} }
public function ProcessLogin(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) public function ProcessLogin(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{ {
$postParams = $this->GetParsedAndFilteredRequestBody($request); $authMiddlewareClass = GROCY_AUTH_CLASS;
if ($authMiddlewareClass::ProcessLogin($this->GetParsedAndFilteredRequestBody($request)))
if (isset($postParams['username']) && isset($postParams['password']))
{ {
$user = $this->getDatabase()->users()->where('username', $postParams['username'])->fetch(); return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/'));
$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'));
}
} }
else else
{ {
return $response->withRedirect($this->AppContainer->get('UrlManager')->ConstructUrl('/login?invalid=true')); 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;
}
} }

View File

@ -72,4 +72,9 @@ class ApiKeyAuthMiddleware extends AuthMiddleware
return null; return null;
} }
} }
public static function ProcessLogin(array $postParams)
{
throw new \Exception('Not implemented');
}
} }

View File

@ -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 * @param Request $request
* @return mixed|null the user row or null if the request is not authenticated * @return mixed|null the user row or null if the request is not authenticated

View File

@ -2,6 +2,8 @@
namespace Grocy\Middleware; namespace Grocy\Middleware;
use Grocy\Services\DatabaseService;
use Grocy\Services\SessionService;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
class DefaultAuthMiddleware extends AuthMiddleware class DefaultAuthMiddleware extends AuthMiddleware
@ -22,4 +24,39 @@ class DefaultAuthMiddleware extends AuthMiddleware
$user = $auth->authenticate($request); $user = $auth->authenticate($request);
return $user; 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;
}
}
} }

View File

@ -36,4 +36,9 @@ class ReverseProxyAuthMiddleware extends AuthMiddleware
return $user; return $user;
} }
public static function ProcessLogin(array $postParams)
{
throw new \Exception('Not implemented');
}
} }

View File

@ -8,12 +8,9 @@ use Psr\Http\Message\ServerRequestInterface as Request;
class SessionAuthMiddleware extends AuthMiddleware class SessionAuthMiddleware extends AuthMiddleware
{ {
protected $SessionCookieName;
public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory) public function __construct(\DI\Container $container, ResponseFactoryInterface $responseFactory)
{ {
parent::__construct($container, $responseFactory); parent::__construct($container, $responseFactory);
$this->SessionCookieName = $this->AppContainer->get('LoginControllerInstance')->GetSessionCookieName();
} }
public function authenticate(Request $request) public function authenticate(Request $request)
@ -25,13 +22,18 @@ class SessionAuthMiddleware extends AuthMiddleware
$sessionService = SessionService::getInstance(); $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; return null;
} }
else 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');
}
} }

View File

@ -13,9 +13,9 @@ $app->group('', function (RouteCollectorProxy $group) {
$group->get('/barcodescannertesting', '\Grocy\Controllers\SystemController:BarcodeScannerTesting'); $group->get('/barcodescannertesting', '\Grocy\Controllers\SystemController:BarcodeScannerTesting');
// Login routes // Login routes
$group->get('/login', 'LoginControllerInstance:LoginPage')->setName('login'); $group->get('/login', '\Grocy\Controllers\LoginController:LoginPage')->setName('login');
$group->post('/login', 'LoginControllerInstance:ProcessLogin')->setName('login'); $group->post('/login', '\Grocy\Controllers\LoginController:ProcessLogin')->setName('login');
$group->get('/logout', 'LoginControllerInstance:Logout'); $group->get('/logout', '\Grocy\Controllers\LoginController:Logout');
// Generic entity interaction // Generic entity interaction
$group->get('/userfields', '\Grocy\Controllers\GenericEntityController:UserfieldsList'); $group->get('/userfields', '\Grocy\Controllers\GenericEntityController:UserfieldsList');

View File

@ -4,6 +4,8 @@ namespace Grocy\Services;
class SessionService extends BaseService class SessionService extends BaseService
{ {
const SESSION_COOKIE_NAME = 'grocy_session';
/** /**
* @return string * @return string
*/ */