Revise session handling to prepare API authentication via token

This commit is contained in:
Bernd Bestel 2018-04-19 20:44:49 +02:00
parent 0c85342404
commit eae5b8bad9
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
9 changed files with 101 additions and 70 deletions

50
app.php
View File

@ -3,46 +3,38 @@
use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response; use \Psr\Http\Message\ResponseInterface as Response;
use \Grocy\Middleware\SessionAuthMiddleware;
use \Grocy\Helpers\UrlManager; use \Grocy\Helpers\UrlManager;
use \Grocy\Services\ApplicationService; use \Grocy\Controllers\LoginController;
require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/data/config.php'; require_once __DIR__ . '/data/config.php';
// Setup base application // Setup base application
if (PHP_SAPI !== 'cli') $appContainer = new \Slim\Container([
{ 'settings' => [
$appContainer = new \Slim\Container([ 'displayErrorDetails' => true,
'settings' => [ 'determineRouteBeforeAppMiddleware' => true
'displayErrorDetails' => true, ],
'determineRouteBeforeAppMiddleware' => true 'view' => function($container)
], {
'view' => function($container) return new \Slim\Views\Blade(__DIR__ . '/views', __DIR__ . '/data/viewcache');
{ },
return new \Slim\Views\Blade(__DIR__ . '/views', __DIR__ . '/data/viewcache'); 'LoginControllerInstance' => function($container)
}, {
'UrlManager' => function($container) return new LoginController($container, 'grocy_session');
{ },
return new UrlManager(BASE_URL); 'UrlManager' => function($container)
} {
]); return new UrlManager(BASE_URL);
}
]);
$app = new \Slim\App($appContainer);
$app = new \Slim\App($appContainer); if (PHP_SAPI === 'cli')
}
else
{ {
$app = new \Slim\App();
$app->add(\pavlakis\cli\CliRequest::class); $app->add(\pavlakis\cli\CliRequest::class);
} }
// Add session handling if this is not a demo installation
$applicationService = new ApplicationService();
if (!$applicationService->IsDemoInstallation())
{
$app->add(SessionAuthMiddleware::class);
}
require_once __DIR__ . '/routes.php'; require_once __DIR__ . '/routes.php';
$app->run(); $app->run();

View File

@ -9,13 +9,15 @@ use \Grocy\Services\DemoDataGeneratorService;
class LoginController extends BaseController class LoginController extends BaseController
{ {
public function __construct(\Slim\Container $container) public function __construct(\Slim\Container $container, string $sessionCookieName)
{ {
parent::__construct($container); parent::__construct($container);
$this->SessionService = new SessionService(); $this->SessionService = new SessionService();
$this->SessionCookieName = $sessionCookieName;
} }
protected $SessionService; protected $SessionService;
protected $SessionCookieName;
public function ProcessLogin(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) public function ProcessLogin(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{ {
@ -25,7 +27,7 @@ class LoginController extends BaseController
if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD) if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD)
{ {
$sessionKey = $this->SessionService->CreateSession(); $sessionKey = $this->SessionService->CreateSession();
setcookie('grocy_session', $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService setcookie($this->SessionCookieName, $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/')); return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
} }
@ -47,7 +49,7 @@ class LoginController extends BaseController
public function Logout(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args) public function Logout(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
{ {
$this->SessionService->RemoveSession($_COOKIE['grocy_session']); $this->SessionService->RemoveSession($_COOKIE[$this->SessionCookieName]);
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/')); return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
} }
@ -66,4 +68,9 @@ class LoginController extends BaseController
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/stockoverview')); return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/stockoverview'));
} }
public function GetSessionCookieName()
{
return $this->SessionCookieName;
}
} }

View File

@ -61,3 +61,14 @@ function GetClassConstants($className)
$r = new ReflectionClass($className); $r = new ReflectionClass($className);
return $r->getConstants(); return $r->getConstants();
} }
function RandomString($length, $allowedChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
{
$randomString = '';
for ($i = 0; $i < $length; $i++)
{
$randomString .= $allowedChars[rand(0, strlen($allowedChars) - 1)];
}
return $randomString;
}

View File

@ -2,11 +2,16 @@
namespace Grocy\Middleware; namespace Grocy\Middleware;
use \Grocy\Services\ApplicationService;
class BaseMiddleware class BaseMiddleware
{ {
public function __construct(\Slim\Container $container) { public function __construct(\Slim\Container $container)
{
$this->AppContainer = $container; $this->AppContainer = $container;
$this->ApplicationService = new ApplicationService();
} }
protected $AppContainer; protected $AppContainer;
protected $ApplicationService;
} }

View File

@ -13,7 +13,7 @@ class CliMiddleware extends BaseMiddleware
} }
else else
{ {
$response = $next($request, $response, $next); $response = $next($request, $response);
return $response->withHeader('Content-Type', 'text/plain'); return $response->withHeader('Content-Type', 'text/plain');
} }
} }

View File

@ -6,7 +6,7 @@ class JsonMiddleware extends BaseMiddleware
{ {
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next) public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
{ {
$response = $next($request, $response, $next); $response = $next($request, $response);
return $response->withHeader('Content-Type', 'application/json'); return $response->withHeader('Content-Type', 'application/json');
} }
} }

View File

@ -6,19 +6,27 @@ use \Grocy\Services\SessionService;
class SessionAuthMiddleware extends BaseMiddleware class SessionAuthMiddleware extends BaseMiddleware
{ {
public function __construct(\Slim\Container $container, string $sessionCookieName)
{
parent::__construct($container);
$this->SessionCookieName = $sessionCookieName;
}
protected $SessionCookieName;
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next) public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
{ {
$route = $request->getAttribute('route'); $route = $request->getAttribute('route');
$routeName = $route->getName(); $routeName = $route->getName();
if ($routeName === 'root') if ($routeName === 'root' || $this->ApplicationService->IsDemoInstallation())
{ {
$response = $next($request, $response); $response = $next($request, $response);
} }
else else
{ {
$sessionService = new SessionService(); $sessionService = new SessionService();
if ((!isset($_COOKIE['grocy_session']) || !$sessionService->IsValidSession($_COOKIE['grocy_session'])) && $routeName !== 'login') if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login')
{ {
$response = $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login')); $response = $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login'));
} }

View File

@ -2,48 +2,51 @@
use \Grocy\Middleware\JsonMiddleware; use \Grocy\Middleware\JsonMiddleware;
use \Grocy\Middleware\CliMiddleware; use \Grocy\Middleware\CliMiddleware;
use \Grocy\Middleware\SessionAuthMiddleware;
// Base route $app->group('', function()
$app->get('/', 'Grocy\Controllers\LoginController:Root')->setName('root'); {
// Base route
$this->get('/', 'LoginControllerInstance:Root')->setName('root');
// Login routes // Login routes
$app->get('/login', 'Grocy\Controllers\LoginController:LoginPage')->setName('login'); $this->get('/login', 'LoginControllerInstance:LoginPage')->setName('login');
$app->post('/login', 'Grocy\Controllers\LoginController:ProcessLogin')->setName('login'); $this->post('/login', 'LoginControllerInstance:ProcessLogin')->setName('login');
$app->get('/logout', 'Grocy\Controllers\LoginController:Logout'); $this->get('/logout', 'LoginControllerInstance:Logout');
// Stock routes // Stock routes
$app->get('/stockoverview', 'Grocy\Controllers\StockController:Overview'); $this->get('/stockoverview', 'Grocy\Controllers\StockController:Overview');
$app->get('/purchase', 'Grocy\Controllers\StockController:Purchase'); $this->get('/purchase', 'Grocy\Controllers\StockController:Purchase');
$app->get('/consume', 'Grocy\Controllers\StockController:Consume'); $this->get('/consume', 'Grocy\Controllers\StockController:Consume');
$app->get('/inventory', 'Grocy\Controllers\StockController:Inventory'); $this->get('/inventory', 'Grocy\Controllers\StockController:Inventory');
$app->get('/products', 'Grocy\Controllers\StockController:ProductsList'); $this->get('/products', 'Grocy\Controllers\StockController:ProductsList');
$app->get('/product/{productId}', 'Grocy\Controllers\StockController:ProductEditForm'); $this->get('/product/{productId}', 'Grocy\Controllers\StockController:ProductEditForm');
$app->get('/locations', 'Grocy\Controllers\StockController:LocationsList'); $this->get('/locations', 'Grocy\Controllers\StockController:LocationsList');
$app->get('/location/{locationId}', 'Grocy\Controllers\StockController:LocationEditForm'); $this->get('/location/{locationId}', 'Grocy\Controllers\StockController:LocationEditForm');
$app->get('/quantityunits', 'Grocy\Controllers\StockController:QuantityUnitsList'); $this->get('/quantityunits', 'Grocy\Controllers\StockController:QuantityUnitsList');
$app->get('/quantityunit/{quantityunitId}', 'Grocy\Controllers\StockController:QuantityUnitEditForm'); $this->get('/quantityunit/{quantityunitId}', 'Grocy\Controllers\StockController:QuantityUnitEditForm');
$app->get('/shoppinglist', 'Grocy\Controllers\StockController:ShoppingList'); $this->get('/shoppinglist', 'Grocy\Controllers\StockController:ShoppingList');
$app->get('/shoppinglistitem/{itemId}', 'Grocy\Controllers\StockController:ShoppingListItemEditForm'); $this->get('/shoppinglistitem/{itemId}', 'Grocy\Controllers\StockController:ShoppingListItemEditForm');
// Habit routes // Habit routes
$app->get('/habitsoverview', 'Grocy\Controllers\HabitsController:Overview'); $this->get('/habitsoverview', 'Grocy\Controllers\HabitsController:Overview');
$app->get('/habittracking', 'Grocy\Controllers\HabitsController:TrackHabitExecution'); $this->get('/habittracking', 'Grocy\Controllers\HabitsController:TrackHabitExecution');
$app->get('/habits', 'Grocy\Controllers\HabitsController:HabitsList'); $this->get('/habits', 'Grocy\Controllers\HabitsController:HabitsList');
$app->get('/habit/{habitId}', 'Grocy\Controllers\HabitsController:HabitEditForm'); $this->get('/habit/{habitId}', 'Grocy\Controllers\HabitsController:HabitEditForm');
// Batterry routes // Batterry routes
$app->get('/batteriesoverview', 'Grocy\Controllers\BatteriesController:Overview'); $this->get('/batteriesoverview', 'Grocy\Controllers\BatteriesController:Overview');
$app->get('/batterytracking', 'Grocy\Controllers\BatteriesController:TrackChargeCycle'); $this->get('/batterytracking', 'Grocy\Controllers\BatteriesController:TrackChargeCycle');
$app->get('/batteries', 'Grocy\Controllers\BatteriesController:BatteriesList');
$app->get('/battery/{batteryId}', 'Grocy\Controllers\BatteriesController:BatteryEditForm');
$this->get('/batteries', 'Grocy\Controllers\BatteriesController:BatteriesList');
$this->get('/battery/{batteryId}', 'Grocy\Controllers\BatteriesController:BatteryEditForm');
})->add(new SessionAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName()));
$app->group('/api', function() $app->group('/api', function()
{ {
@ -65,7 +68,7 @@ $app->group('/api', function()
$this->get('/batteries/track-charge-cycle/{batteryId}', 'Grocy\Controllers\BatteriesApiController:TrackChargeCycle'); $this->get('/batteries/track-charge-cycle/{batteryId}', 'Grocy\Controllers\BatteriesApiController:TrackChargeCycle');
$this->get('/batteries/get-battery-details/{batteryId}', 'Grocy\Controllers\BatteriesApiController:BatteryDetails'); $this->get('/batteries/get-battery-details/{batteryId}', 'Grocy\Controllers\BatteriesApiController:BatteryDetails');
})->add(JsonMiddleware::class); })->add(new SessionAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName()))->add(JsonMiddleware::class);
$app->group('/cli', function() $app->group('/cli', function()
{ {

View File

@ -24,11 +24,11 @@ class SessionService extends BaseService
*/ */
public function CreateSession() public function CreateSession()
{ {
$newSessionKey = uniqid() . uniqid() . uniqid(); $newSessionKey = $this->GenerateSessionKey();
$sessionRow = $this->Database->sessions()->createRow(array( $sessionRow = $this->Database->sessions()->createRow(array(
'session_key' => $newSessionKey, 'session_key' => $newSessionKey,
'expires' => time() + 2592000 //30 days 'expires' => time() + 2592000 // 30 days
)); ));
$sessionRow->save(); $sessionRow->save();
@ -39,4 +39,9 @@ class SessionService extends BaseService
{ {
$this->Database->sessions()->where('session_key', $sessionKey)->delete(); $this->Database->sessions()->where('session_key', $sessionKey)->delete();
} }
private function GenerateSessionKey()
{
return RandomString(50);
}
} }