Commit f326e004 authored by Tino Goratsch's avatar Tino Goratsch

first part at refactoring the authentication mechanism

parent e5377c02
......@@ -11,19 +11,19 @@ use ACP3\Modules\ACP3\Permissions;
class ACL
{
/**
* @var Auth
* @var \ACP3\Core\Auth
*/
protected $auth;
/**
* @var Modules
* @var \ACP3\Core\Modules
*/
protected $modules;
/**
* @var Permissions\Cache
* @var \ACP3\Modules\ACP3\Permissions\Cache
*/
protected $permissionsCache;
/**
* @var Permissions\Model
* @var \ACP3\Modules\ACP3\Permissions\Model
*/
protected $permissionsModel;
/**
......
......@@ -140,47 +140,11 @@ class Application extends AbstractApplication
$containerConfigCache = new ConfigCache($file, (defined('DEBUG') && DEBUG === true));
if (!$containerConfigCache->isFresh()) {
$containerBuilder = new ContainerBuilder();
$loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__));
$loader->load(CLASSES_DIR . 'config/services.yml');
$loader->load(CLASSES_DIR . 'View/Renderer/Smarty/config/services.yml');
// Try to get all available services
/** @var Modules $modules */
$modules = $containerBuilder->get('core.modules');
$activeModules = $modules->getActiveModules();
$vendors = $containerBuilder->get('core.modules.vendors')->getVendors();
foreach ($activeModules as $module) {
foreach ($vendors as $vendor) {
$path = MODULES_DIR . $vendor . '/' . $module['dir'] . '/config/services.yml';
if (is_file($path)) {
$loader->load($path);
}
}
}
$containerBuilder->compile();
$dumper = new PhpDumper($containerBuilder);
$containerConfigCache->write(
$dumper->dump(['class' => 'ACP3ServiceContainer']),
$containerBuilder->getResources()
);
$this->dumpContainer($containerConfigCache);
}
require_once $file;
$this->container = new \ACP3ServiceContainer();
// Load system settings
$this->systemSettings = $this->container->get('core.config')->getSettings('system');
$this->container->get('core.auth')->authenticate();
$this->_setThemeConstants();
$this->container->get('core.view')->setRenderer('smarty');
}
/**
......@@ -198,6 +162,12 @@ class Application extends AbstractApplication
*/
public function outputPage()
{
// Load system settings
$this->systemSettings = $this->container->get('core.config')->getSettings('system');
$this->_setThemeConstants();
$this->container->get('core.auth')->authenticate();
$this->container->get('core.view')->setRenderer('smarty');
/** @var \ACP3\Core\Http\Request $request */
$request = $this->container->get('core.request');
$request->processQuery();
......@@ -273,4 +243,39 @@ class Application extends AbstractApplication
$redirect->temporary($path);
}
}
/**
* @param $containerConfigCache
*/
protected function dumpContainer($containerConfigCache)
{
$containerBuilder = new ContainerBuilder();
$loader = new YamlFileLoader($containerBuilder, new FileLocator(__DIR__));
$loader->load(CLASSES_DIR . 'config/services.yml');
$loader->load(CLASSES_DIR . 'View/Renderer/Smarty/config/services.yml');
// Try to get all available services
/** @var Modules $modules */
$modules = $containerBuilder->get('core.modules');
$activeModules = $modules->getActiveModules();
$vendors = $containerBuilder->get('core.modules.vendors')->getVendors();
foreach ($activeModules as $module) {
foreach ($vendors as $vendor) {
$path = MODULES_DIR . $vendor . '/' . $module['dir'] . '/config/services.yml';
if (is_file($path)) {
$loader->load($path);
}
}
}
$containerBuilder->compile();
$dumper = new PhpDumper($containerBuilder);
$containerConfigCache->write(
$dumper->dump(['class' => 'ACP3ServiceContainer']),
$containerBuilder->getResources()
);
}
}
......@@ -3,6 +3,7 @@ namespace ACP3\Core;
use ACP3\Core\Helpers\Country;
use ACP3\Core\Helpers\Secure;
use ACP3\Core\Http\RequestInterface;
use ACP3\Modules\ACP3\Users;
/**
......@@ -14,7 +15,7 @@ class Auth
/**
* Name des Authentifizierungscookies
*/
const COOKIE_NAME = 'ACP3_AUTH';
const AUTH_NAME = 'ACP3_AUTH';
/**
* Anzuzeigende Datensätze pro Seite
*
......@@ -49,6 +50,10 @@ class Auth
* @var array
*/
protected $userInfo = [];
/**
* @var \ACP3\Core\Http\RequestInterface
*/
protected $request;
/**
* @var \ACP3\Core\SessionHandler
*/
......@@ -67,18 +72,21 @@ class Auth
protected $secureHelper;
/**
* @param \ACP3\Core\SessionHandler $sessionHandler
* @param \ACP3\Core\Helpers\Secure $secureHelper
* @param \ACP3\Core\Config $config
* @param \ACP3\Modules\ACP3\Users\Model $usersModel
* @param \ACP3\Core\Http\RequestInterface $request
* @param \ACP3\Core\SessionHandler $sessionHandler
* @param \ACP3\Core\Helpers\Secure $secureHelper
* @param \ACP3\Core\Config $config
* @param \ACP3\Modules\ACP3\Users\Model $usersModel
*/
public function __construct(
RequestInterface $request,
SessionHandler $sessionHandler,
Secure $secureHelper,
Config $config,
Users\Model $usersModel
)
{
$this->request = $request;
$this->sessionHandler = $sessionHandler;
$this->secureHelper = $secureHelper;
$this->config = $config;
......@@ -86,8 +94,7 @@ class Auth
}
/**
* Findet heraus, falls der ACP3_AUTH Cookie gesetzt ist, ob der
* Seitenbesucher auch wirklich ein registrierter Benutzer des ACP3 ist
* Authenticates the user
*/
public function authenticate()
{
......@@ -96,31 +103,50 @@ class Auth
$this->entries = $settings['entries'];
$this->language = $settings['lang'];
if (isset($_COOKIE[self::COOKIE_NAME])) {
$cookie = base64_decode($_COOKIE[self::COOKIE_NAME]);
$cookieData = explode('|', $cookie);
$user = $this->usersModel->getOneActiveUserByNickname($cookieData[0]);
if (!empty($user)) {
$dbPassword = substr($user['pwd'], 0, 40);
if ($dbPassword === $cookieData[1]) {
$this->isUser = true;
$this->userId = (int)$user['id'];
$this->superUser = (bool)$user['super_user'];
$settings = $this->config->getSettings('users');
if ($settings['entries_override'] == 1 && $user['entries'] > 0) {
$this->entries = (int)$user['entries'];
}
if ($settings['language_override'] == 1) {
$this->language = $user['language'];
}
}
} else {
if ($this->sessionHandler->has(self::AUTH_NAME)) {
$authData = $this->sessionHandler->get(self::AUTH_NAME, []);
if (!$this->validate($authData['username'], $authData['password'])) {
$this->logout();
}
} elseif ($this->request->getCookie()->has(self::AUTH_NAME)) {
$cookie = base64_decode($this->request->getCookie()->get(self::AUTH_NAME, ''));
$authData = explode('|', $cookie);
if (!$this->validate($authData[0], $authData[1])) {
$this->logout();
}
}
}
/**
* @param string $username
* @param string $passwordHash
*
* @return bool
*/
protected function validate($username, $passwordHash)
{
$user = $this->usersModel->getOneActiveUserByNickname($username);
if (!empty($user)) {
$dbPassword = substr($user['pwd'], 0, 40);
if ($dbPassword === $passwordHash) {
$this->successfulAuthentication($user);
$settings = $this->config->getSettings('users');
if ($settings['entries_override'] == 1 && $user['entries'] > 0) {
$this->entries = (int)$user['entries'];
}
if ($settings['language_override'] == 1) {
$this->language = $user['language'];
}
return true;
}
}
return false;
}
/**
......@@ -151,7 +177,7 @@ class Auth
$value = base64_encode($nickname . '|' . $password);
$expiry = time() + $expiry;
$domain = strpos($_SERVER['HTTP_HOST'], '.') !== false ? $_SERVER['HTTP_HOST'] : '';
return setcookie(self::COOKIE_NAME, $value, $expiry, ROOT_DIR, $domain);
return setcookie(self::AUTH_NAME, $value, $expiry, ROOT_DIR, $domain);
}
/**
......@@ -225,15 +251,12 @@ class Auth
* Loggt einen User ein
*
* @param string $username
* Der zu verwendente Username
* @param string $password
* Das zu verwendente Passwort
* @param integer $expiry
* Gibt die Zeit in Sekunden an, wie lange der User eingeloggt bleiben soll
* @param bool $rememberMe
*
* @return integer
*/
public function login($username, $password, $expiry)
public function login($username, $password, $rememberMe)
{
$user = $this->usersModel->getOneByNickname($username);
......@@ -243,38 +266,69 @@ class Auth
return -1;
}
// Passwort aus Datenbank
// The hashed password (without the salt) from the database
$dbHash = substr($user['pwd'], 0, 40);
// Hash für eingegebenes Passwort generieren
// Get the salt of the password
$salt = substr($user['pwd'], 41, 53);
// Generate the hash for the typed in password
$formPasswordHash = $this->secureHelper->generateSaltedPassword($salt, $password);
// Wenn beide Hashwerte gleich sind, Benutzer authentifizieren
if ($dbHash === $formPasswordHash) {
// Login-Fehler zurücksetzen
if ($user['login_errors'] > 0) {
$this->usersModel->update(['login_errors' => 0], (int)$user['id']);
}
$this->setCookie($username, $dbHash, $expiry);
if ($rememberMe === true) {
$this->setCookie($username, $dbHash, 31104000);
}
// Neue Session-ID generieren
$this->sessionHandler->secureSession(true);
$this->sessionHandler->secureSession();
$this->isUser = true;
$this->userId = (int)$user['id'];
$this->setSessionValues($username, $dbHash);
$this->successfulAuthentication($user);
return 1;
} else { // Beim dritten falschen Login den Account sperren
$loginErrors = $user['login_errors'] + 1;
$this->usersModel->update(['login_errors' => $loginErrors], (int)$user['id']);
if ($loginErrors === 3) {
} else {
if ($this->saveFailedLoginAttempts($user) === 3) {
return -1;
}
}
}
return 0;
}
/**
* @param string $username
* @param string $dbHash
*/
private function setSessionValues($username, $dbHash)
{
$this->sessionHandler->set(self::AUTH_NAME, [
'username' => $username,
'password' => $dbHash
]);
}
/**
* @param array $user
*
* @return int
*/
protected function saveFailedLoginAttempts(array $user)
{
$loginErrors = $user['login_errors'] + 1;
$this->usersModel->update(['login_errors' => $loginErrors], (int)$user['id']);
return $loginErrors;
}
/**
* @param array $user
*/
protected function successfulAuthentication(array $user)
{
$this->isUser = true;
$this->userId = (int)$user['id'];
$this->superUser = (bool)$user['super_user'];
}
}
......@@ -10,9 +10,9 @@ use ACP3\Core;
class RewriteInternalUri
{
/**
* @var \ACP3\Core\Modules
* @var \ACP3\Core\Modules\Helper\ControllerActionExists
*/
protected $modules;
protected $controllerActionExists;
/**
* @var \ACP3\Core\Router
*/
......@@ -23,17 +23,17 @@ class RewriteInternalUri
protected $aliasesValidator;
/**
* @param \ACP3\Core\Modules $modules
* @param \ACP3\Core\Router $router
* @param \ACP3\Core\Validator\Rules\Router\Aliases $aliasValidator
* @param \ACP3\Core\Modules\Helper\ControllerActionExists $controllerActionExists
* @param \ACP3\Core\Router $router
* @param \ACP3\Core\Validator\Rules\Router\Aliases $aliasValidator
*/
public function __construct(
Core\Modules $modules,
Core\Modules\Helper\ControllerActionExists $controllerActionExists,
Core\Router $router,
Core\Validator\Rules\Router\Aliases $aliasValidator
)
{
$this->modules = $modules;
$this->controllerActionExists = $controllerActionExists;
$this->router = $router;
$this->aliasesValidator = $aliasValidator;
}
......@@ -76,7 +76,7 @@ class RewriteInternalUri
$path .= '/' . $uriArray[3];
}
if ($this->modules->controllerActionExists($path) === true) {
if ($this->controllerActionExists->controllerActionExists($path) === true) {
return '<a' . $matches[1] . 'href="' . $this->router->route($matches[7]) . '"';
} else {
return $matches[0];
......
......@@ -15,9 +15,9 @@ class Request extends AbstractRequest
const ADMIN_PANEL_PATTERN = '=^acp/=';
/**
* @var \ACP3\Core\Modules
* @var \ACP3\Core\Modules\Helper\ControllerActionExists
*/
protected $modules;
protected $controllerActionExists;
/**
* @var \ACP3\Core\Config
*/
......@@ -61,17 +61,17 @@ class Request extends AbstractRequest
protected $isHomepage;
/**
* @param \ACP3\Core\Modules $modules
* @param \ACP3\Core\Config $config
* @param \ACP3\Modules\ACP3\Seo\Model $seoModel
* @param \ACP3\Core\Modules\Helper\ControllerActionExists $controllerActionExists
* @param \ACP3\Core\Config $config
* @param \ACP3\Modules\ACP3\Seo\Model $seoModel
*/
public function __construct(
Modules $modules,
Modules\Helper\ControllerActionExists $controllerActionExists,
Config $config,
Seo\Model $seoModel
)
{
$this->modules = $modules;
$this->controllerActionExists = $controllerActionExists;
$this->config = $config;
$this->seoModel = $seoModel;
......@@ -341,7 +341,7 @@ class Request extends AbstractRequest
}
// Keine entsprechende Module-Action gefunden -> muss Alias sein
if ($this->modules->controllerActionExists($this->area . '/' . $query[0] . '/' . $query[1] . '/' . $query[2]) === false) {
if ($this->controllerActionExists->controllerActionExists($this->area . '/' . $query[0] . '/' . $query[1] . '/' . $query[2]) === false) {
$length = 0;
foreach ($query as $row) {
if (strpos($row, '_') !== false) {
......
<?php
namespace ACP3\Core;
use ACP3\Core\Modules\Helper\ControllerActionExists;
use ACP3\Core\Modules\ModuleInfoCache;
use ACP3\Core\Modules\Vendors;
use ACP3\Modules\ACP3\System;
......@@ -16,6 +17,10 @@ class Modules
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* @var \ACP3\Core\Modules\Helper\ControllerActionExists
*/
protected $controllerActionExists;
/**
* @var \ACP3\Core\Modules\ModuleInfoCache
*/
......@@ -35,16 +40,19 @@ class Modules
/**
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* @param \ACP3\Core\Modules\Helper\ControllerActionExists $controllerActionExists
* @param \ACP3\Core\Modules\ModuleInfoCache $moduleInfoCache
* @param \ACP3\Core\Modules\Vendors $vendors
*/
public function __construct(
ContainerInterface $container,
ControllerActionExists $controllerActionExists,
ModuleInfoCache $moduleInfoCache,
Vendors $vendors
)
{
$this->container = $container;
$this->controllerActionExists = $controllerActionExists;
$this->moduleInfoCache = $moduleInfoCache;
$this->vendors = $vendors;
}
......@@ -58,22 +66,7 @@ class Modules
*/
public function controllerActionExists($path)
{
$pathArray = explode('/', strtolower(str_replace('_', '', $path)));
if (empty($pathArray[2]) === true) {
$pathArray[2] = 'index';
}
if (empty($pathArray[3]) === true) {
$pathArray[3] = 'index';
}
$serviceId = $pathArray[1] . '.controller.' . $pathArray[0] . '.' . $pathArray[2];
if ($this->container->has($serviceId)) {
return method_exists($this->container->get($serviceId), 'action' . $pathArray[3]);
}
return false;
return $this->controllerActionExists->controllerActionExists($path);
}
/**
......
<?php
namespace ACP3\Core\Modules\Helper;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class ControllerActionExists
* @package ACP3\Core\Modules\Helper
*/
class ControllerActionExists
{
/**
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Returns, whether the given module controller action exists
*
* @param string $path
*
* @return boolean
*/
public function controllerActionExists($path)
{
$pathArray = explode('/', strtolower(str_replace('_', '', $path)));
if (empty($pathArray[2]) === true) {
$pathArray[2] = 'index';
}
if (empty($pathArray[3]) === true) {
$pathArray[3] = 'index';
}
$serviceId = $pathArray[1] . '.controller.' . $pathArray[0] . '.' . $pathArray[2];
if ($this->container->has($serviceId)) {
return method_exists($this->container->get($serviceId), 'action' . $pathArray[3]);
}
return false;
}
}
\ No newline at end of file
......@@ -44,6 +44,7 @@ class Redirect
$response = new RedirectResponse($url);
$response->send();
exit;
}
/**
......
<?php
namespace ACP3\Core;
use ACP3\Core\Http\RequestInterface;
/**
* @package ACP3\Core
......@@ -26,13 +27,22 @@ class SessionHandler implements \SessionHandlerInterface
* @var \ACP3\Core\DB
*/
protected $db;
/**
* @var \ACP3\Core\Http\RequestInterface
*/
protected $request;
/**
* @param \ACP3\Core\DB $db
* @param \ACP3\Core\DB $db
* @param \ACP3\Core\Http\RequestInterface $request
*/
public function __construct(DB $db)
public function __construct(
DB $db,
RequestInterface $request
)
{
$this->db = $db;
$this->request = $request;
$this->configureSession();
}
......@@ -58,12 +68,14 @@ class SessionHandler implements \SessionHandlerInterface
// Start the session and secure it
$this->startSession();
$this->secureSession();
register_shutdown_function('session_write_close');
}
}
public function __destruct()
{
session_write_close();
}
/**
* Session starten
*/
......@@ -72,23 +84,20 @@ class SessionHandler implements \SessionHandlerInterface
// Set the session cookie parameters
session_set_cookie_params(0, ROOT_DIR);
session_write_close();
// Start the session
session_start();
}
/**
* Secures the current session
*
* @param boolean $force
*/
public function secureSession($force = false)
public function secureSession()
{
// Prevent from session fixations
if ($this->get('acp3_init', false) === false || $force === true) {
session_regenerate_id(true);
$this->resetSessionData();
$this->set('acp3_init', true);
}
session_regenerate_id(true);
$this->resetSessionData();
}
/**
......@@ -129,7 +138,7 @@ class SessionHandler implements \SessionHandlerInterface
}
/**
* @param $key
* @param string $key
*
* @return bool
*/
......@@ -139,8 +148,8 @@ class SessionHandler implements \SessionHandlerInterface
}
/**
* @param $key
* @param $value
* @param string $key
* @param mixed $value
*
* @return $this
*/
......@@ -151,6 +160,11 @@ class SessionHandler implements \SessionHandlerInterface
return $this;
}
/**
* @param string $key
*
* @return $this
*/
public function remove($key)
{
if (isset($_SESSION[$key])) {
......@@ -196,7 +210,7 @@ class SessionHandler implements \SessionHandlerInterface
$this->resetSessionData();
// Session-Cookie löschen
if (isset($_COOKIE[self::SESSION_NAME])) {
if ($this->request->getCookie()->has(self::SESSION_NAME)) {
setcookie(self::SESSION_NAME, '', time() - 3600, ROOT_DIR);
}
......
......@@ -54,4 +54,4 @@ services:
core.helpers.formatter.rewriteInternalUri:
class: ACP3\Core\Helpers\Formatter\RewriteInternalUri
arguments: ['@core.modules', '@core.router', '@core.validator.rules.router.aliases']
arguments: [@core.modules.controllerActionsExists, '@core.router', '@core.validator.rules.router.aliases']
......@@ -16,7 +16,7 @@ services:
core.modules:
class: ACP3\Core\Modules
arguments: ['@service_container', @core.modules.moduleInfoCache, @core.modules.vendors]
arguments: ['@service_container', @core.modules.controllerActionsExists, @core.modules.moduleInfoCache, @core.modules.vendors]
core.modules.aclInstaller:
class: ACP3\Core\Modules\AclInstaller
......@@ -34,6 +34,10 @@ services:
abstract: true
arguments: ['@core.context.admin']
core.modules.controllerActionsExists:
class: ACP3\Core\Modules\Helper\ControllerActionExists
arguments: [@service_container]
core.modules.moduleInfoCache:
class: ACP3\Core\Modules\ModuleInfoCache
arguments: [@core.cache.system, @core.lang, @core.modules.vendors, @core.xml, @system.model]
......
......@@ -19,7 +19,7 @@ services:
core.auth:
class: ACP3\Core\Auth
arguments: ['@core.session', '@core.helpers.secure', '@core.config', '@users.model']
arguments: [@core.request, '@core.session', '@core.helpers.secure', '@core.config', '@users.model']
core.breadcrumb:
class: ACP3\Core\Breadcrumb
......@@ -66,7 +66,7 @@ services:
core.request:
class: ACP3\Core\Http\Request
arguments: ['@core.modules', '@core.config', '@seo.model']
arguments: ['@core.modules.controllerActionsExists', '@core.config', '@seo.model']