Compare commits

...

4 commits
master ... dev

Author SHA1 Message Date
Maurice Grönwoldt
c7984873c0 fixed login
added example login
2020-09-25 22:33:35 +02:00
Maurice Grönwoldt
f00bdc99ec fixed security login 2020-09-25 21:51:39 +02:00
Maurice Grönwoldt
2db5aa8693 cherry-picked changes Meta-Generator from engineer-trooper 2020-09-25 21:39:05 +02:00
Maurice Grönwoldt
f7fa124535 WIP 2020-09-25 21:33:54 +02:00
22 changed files with 471 additions and 58 deletions

View file

@ -9,7 +9,9 @@
}
],
"require": {
"ext-pdo": "*"
"ext-pdo": "*",
"ext-http": "*",
"ext-json": "*"
},
"autoload": {
"psr-4": {

View file

@ -7,6 +7,14 @@ create table if not exists seoData
)
comment 'seo url mapping';
create table if not exists metaTagData
(
id int(255) auto_increment not null unique primary key,
content JSON not null,
isActive tinyint(1) default 1 null
)
comment 'Meta Tag File';
create table if not exists language
(
id int(255) auto_increment not null unique primary key,
@ -27,3 +35,16 @@ create table if not exists data
datatype enum ('content', 'form')
)
comment 'DataLoader File';
create table if not exists users
(
id int(255) auto_increment not null unique primary key,
username varchar(255) not null unique,
email varchar(255) not null,
password varchar(255) not null,
token varchar(255) not null,
salt varchar(255) not null,
roles text default 'ROLE_GUEST' not null,
isActive tinyint(1) default 1 null
)
comment 'User File';

View file

@ -1,7 +0,0 @@
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (/[^.]*|\.)$ [NC]
RewriteRule .* index.php [L]

View file

@ -1,24 +0,0 @@
<?php
use Venom\Core\Config;
use Venom\Core\Setup;
use Venom\Venom;
require_once '../../vendor/autoload.php';
Setup::loadConfig(true);
Setup::loadLanguage();
$config = Config::getInstance();
if ($config->isMaintenance()) {
echo 'Currently not available';
exit;
}
//if devMode is on show all errors!
if ($config->isDevMode()) {
error_reporting(E_ALL);
ini_set('error_reporting', E_ALL);
}
$venom = new Venom();
Setup::loadRouters($venom);
Setup::loadModules($venom);
$venom->run();

View file

@ -2,10 +2,12 @@
use Venom\Core\Config;
use Venom\Core\Setup;
use Venom\Helper\URLHelper;
use Venom\Venom;
require_once '../vendor/autoload.php';
Setup::loadConfig(false);
session_start();
Setup::loadConfig(URLHelper::getInstance()->isAdminUrl());
Setup::loadLanguage();
$config = Config::getInstance();
@ -21,4 +23,4 @@ if ($config->isDevMode()) {
$venom = new Venom();
Setup::loadRouters($venom);
Setup::loadModules($venom);
$venom->run();
$venom->inject();

View file

@ -0,0 +1,22 @@
* {
box-sizing: border-box;
}
html, body {
margin: 0;
font-size: 16px;
color: #fff;
background-color: #333;
font-family: sans-serif;
height: 100vh;
width: 100vw;
}
header {
font-size: 5vw;
cursor: pointer;
}
header:hover {
color: #ff0323;
}

View file

View file

@ -0,0 +1,35 @@
<?php
namespace Venom\Admin;
use Venom\Helper\URLHelper;
use Venom\Views\RenderController;
use Venom\Views\VenomRenderer;
class AdminController implements RenderController
{
private string $tpl = 'default';
public function register(): bool
{
return true;
}
public function render(VenomRenderer $renderer): bool
{
if (URLHelper::getInstance()->getUrl() !== '/admin/') {
http_response_code(404);
$this->tpl = 'async';
}
return true;
}
public function getTemplate(): string
{
return $this->tpl;
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Venom\Admin;
use Venom\Admin\Routes\LoginRoute;
use Venom\Routing\Router;
use Venom\Venom;
class AdminRouterInit
{
public static function registerAdminRouters(Venom $venom): void
{
$router = new Router(Router::ADMIN_ROUTER, 1.0, '/admin/api');
$router->addRoutes(self::getRoutes());
$venom->addRouter(Router::ADMIN_ROUTER, $router);
}
public static function getRoutes(): array
{
return [
'/login' => [
'cl' => LoginRoute::class,
'roles' => ['ROLE_GUEST'],
'routes' => [
'*' => [
"POST" => 'login'
],
'1' => [
"GET" => 'handle'
]
]
]
];
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Venom\Admin\Routes;
use Venom\Core\ArgumentHandler;
use Venom\Routing\Route;
use Venom\Security\Security;
class LoginRoute implements Route
{
public function login(): bool
{
Security::get()->login();
return true;
}
public function handle($fnc): bool
{
if ($fnc === 'logout') {
Security::get()->logout();
$url = ArgumentHandler::get()->getPostItem('REDIRECT_TO', '/admin/');
header('Location: ' . $url);
die();
}
return true;
}
}

View file

@ -8,6 +8,7 @@ class ArgumentHandler
{
public static ?ArgumentHandler $instance = null;
private array $arguments = [];
private array $post = [];
public function __construct()
{
@ -16,6 +17,7 @@ class ArgumentHandler
}
foreach ($_POST as $key => $item) {
$this->arguments[htmlspecialchars($key)] = htmlspecialchars($item);
$this->post[htmlspecialchars($key)] = htmlspecialchars($item);
}
}
@ -41,4 +43,14 @@ class ArgumentHandler
{
return isset($this->arguments[$key]);
}
public function getPostItem(string $key, $default = null)
{
return $this->post[$key] ?? $default;
}
public function hasPostItem(string $key): bool
{
return isset($this->post[$key]);
}
}

View file

@ -4,11 +4,40 @@
namespace Venom\Helper;
use Venom\Core\ArgumentHandler;
use Venom\Core\DatabaseHandler;
/**
* Class MetaGenerator
* @package Venom\Helper
*/
class MetaGenerator
{
private array $container = [];
private string $id;
public function __construct()
{
$this->id = (string)ArgumentHandler::get()->getItem('metaId', '-1');
}
public function loadById(): void
{
if ($this->id === '-1') {
return;
}
$db = DatabaseHandler::get();
$data = $db->getOne('select content from metaTagData where id = :id', [':id' => $this->id]);
if ($data !== null) {
$this->container = json_decode($data->content ?? '', true);
$this->container = array_merge([], $this->container);
}
}
public function render(): void
{
foreach($this->container as $key => $value) {
echo '<meta name="'.$key.'" content="'.$value.'">';
}
}
}

View file

@ -42,4 +42,9 @@ class URLHelper
{
return $url;
}
public function isAdminUrl(): bool
{
return strpos($this->parsedUrl, '/admin/') === 0;
}
}

View file

@ -4,11 +4,124 @@
namespace Venom\Models;
use Venom\Core\DatabaseHandler;
class User
{
private string $username;
private string $password;
private string $salt;
private string $token;
private array $roles;
public const ADMIN_ROLE = 'ROLE_ADMIN';
public const GUEST_ROLE = 'ROLE_GUEST';
private string $username = 'GUEST';
private string $email = 'GUEST';
private string $password = '---';
private string $salt = '---';
private string $token = '---';
private string $id = '-1';
private array $roles = [];
private bool $isLoaded = false;
public function hasRole(string $role): bool
{
return in_array($role, $this->roles, true);
}
public function loadUser(): bool
{
if (isset($_SESSION['userID']) || $this->username !== 'GUEST') {
// try to load user from id!
$user = DatabaseHandler::get()->getOne("SELECT * FROM users WHERE id = :id OR username = :name AND isActive = 1", [
':id' => $_SESSION['userID'],
':name' => $this->username
]);
if ($user !== null) {
$this->username = $user->username ?? '';
$this->email = $user->email ?? '';
$this->password = $user->password ?? '';
$this->token = $user->token ?? '';
$this->salt = $user->salt ?? '';
$this->id = $user->id ?? '-1';
$this->roles = explode(',', $user->roles ?? '');
$this->isLoaded = true;
return true;
}
}
return false;
}
public function getUsername(): string
{
return $this->username;
}
public function setUsername(string $username): void
{
$this->username = $username;
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): void
{
$this->email = $email;
}
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): void
{
$this->password = $password;
}
public function getSalt(): string
{
return $this->salt;
}
public function setSalt(string $salt): void
{
$this->salt = $salt;
}
public function getToken(): string
{
return $this->token;
}
public function setToken(string $token): void
{
$this->token = $token;
}
public function getRoles(): array
{
return $this->roles;
}
public function setRoles(array $roles): void
{
$this->roles = $roles;
}
public function addRole($value): void
{
if (!in_array($value, $this->roles, true)) {
$this->roles[] = $value;
}
}
public function isLoaded(): bool
{
return $this->isLoaded;
}
public function getId(): string
{
return $this->id;
}
}

View file

@ -5,9 +5,14 @@ namespace Venom\Routing;
use Exception;
use Venom\Core\Config;
use Venom\Models\User;
use Venom\Security\Security;
class Router
{
public const DEFAULT_ROUTER = 'defaultRouter';
public const ADMIN_ROUTER = 'adminRouter';
protected string $id = 'defaultRouter';
protected int $version;
protected string $prefix = '';
@ -66,6 +71,12 @@ class Router
$subRouteFound = isset($this->routes[$url]['routes'][$subRoute]);
$methodFound = isset($this->routes[$url]['routes'][$subRoute][$method]);
if ($routeAvailable && $subRouteFound && $methodFound) {
if ((Config::getInstance()->getSecurity()->useSecurity || $this->id === self::ADMIN_ROUTER) && isset($this->routes[$url]['roles'])) {
$roles = $this->routes[$url]['roles'];
if (!in_array(User::GUEST_ROLE, $roles, true) && !Security::get()->hasRoles($roles)) {
return null;
}
}
return [
'cl' => $this->routes[$url]['cl'],
'fnc' => $this->routes[$url]['routes'][$subRoute][$method],

View file

@ -4,10 +4,50 @@
namespace Venom\Security;
use Venom\Core\ArgumentHandler;
use Venom\Core\Config;
use Venom\Helper\URLHelper;
use Venom\Models\User;
/**
* Class that Login stupid via Password, Username
*/
class BaseLogin
class BaseLogin implements Login
{
private User $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function checkCredentials(): bool
{
$handler = ArgumentHandler::get();
return $handler->hasPostItem('USERNAME') && $handler->hasPostItem('PASSWORD');
}
public function redirect(): void
{
$url = ArgumentHandler::get()->getPostItem('REDIRECT_TO', URLHelper::getInstance()->getUrl());
header('Location: ' . $url);
die();
}
public function login(): bool
{
$sec = Config::getInstance()->getSecurity();
$this->user->setUsername(ArgumentHandler::get()->getPostItem('USERNAME'));
if (!$this->user->loadUser()) {
return false;
}
$secret = $sec->secret ?? 'venom';
$hashed = hash($sec->algo ?? 'SHA256', ArgumentHandler::get()->getPostItem('PASSWORD') . $secret . $this->user->getSalt());
if ($this->user->getPassword() === $hashed) {
$_SESSION['userID'] = $this->user->getId();
return true;
}
return false;
}
}

View file

@ -10,10 +10,6 @@ interface Login
{
public function __construct(User $user);
public function checkUsername(): bool;
public function checkPassword(): bool;
public function checkCredentials(): bool;
public function login(): bool;

View file

@ -3,10 +3,20 @@
namespace Venom\Security;
use \RuntimeException;
use Venom\Core\Config;
use Venom\Models\User;
class Security
{
private static ?Security $instance = null;
private ?User $user;
public function __construct()
{
$this->user = new User();
$this->user->loadUser();
}
public static function get(): Security
{
@ -16,15 +26,39 @@ class Security
return self::$instance;
}
/* @todo implement logic */
public function hasRole(string $role): bool
{
return $this->user->hasRole($role);
}
public function hasRoles(array $roles): bool
{
foreach ($roles as $role) {
if (!$this->user->hasRole($role)) {
return false;
}
}
return true;
}
/* @todo implement logic */
public function hasRoles(array $roles): bool
public function login(): void
{
return true;
if ($this->user->isLoaded()) {
throw new RuntimeException('Try to re-login!');
}
$sec = Config::getInstance()->getSecurity();
$login = new $sec->securityClass($this->user);
if ($login instanceof Login) {
if (!$login->checkCredentials() || !$login->login()) {
http_response_code(401);
}
$login->redirect();
}
}
public function logout(): void
{
unset($_SESSION['userID']);
$this->user = new User();
}
}

View file

@ -4,6 +4,8 @@
namespace Venom;
use Venom\Admin\AdminController;
use Venom\Admin\AdminRouterInit;
use Venom\Core\ArgumentHandler;
use Venom\Core\Config;
use Venom\Core\Module;
@ -30,13 +32,22 @@ class Venom
Asset::get()->setRenderer($this->renderer);
}
public function inject(): void
{
$this->run();
}
public function run(): void
{
$arguments = ArgumentHandler::get();
$arguments->setItem(ErrorHandler::ERROR_KEY, false);
$config = Config::getInstance();
if ($config->isAdmin()) {
$this->initAdmin();
}
// we need to load the current controller and the current start template.
// after this we can start the renderer
if (Config::getInstance()->isRouterEnabled()) {
if ($config->isRouterEnabled() || $config->isAdmin()) {
$status = $this->useRouter();
if ($status['found']) {
if ($status['status']) {
@ -48,18 +59,32 @@ class Venom
$registry = Registry::getInstance();
$registry->getLang()->initLang();
// if site is errored then dont load via SEO
if (!$arguments->getItem(ErrorHandler::ERROR_KEY)) {
if (!$config->isAdmin() && !$arguments->getItem(ErrorHandler::ERROR_KEY)) {
$registry->getSeo()->loadSite();
}
$this->renderer->init($this->findController());
$this->renderer->render();
}
public function initAdmin(): void
{
$this->controllers['adminCtrl'] = AdminController::class;
AdminRouterInit::registerAdminRouters($this);
ArgumentHandler::get()->setItem('cl', 'adminCtrl');
}
private function useRouter(): array
{
$url = URLHelper::getInstance()->getUrl();
$isAdmin = Config::getInstance()->isAdmin();
/** @var Router $router */
foreach ($this->routers as $router) {
foreach ($this->routers as $key => $router) {
if ($isAdmin && $key !== Router::ADMIN_ROUTER) {
continue;
}
if (!$isAdmin && $key === Router::ADMIN_ROUTER) {
continue;
}
$route = $router->findRoute($url, $_SERVER['REQUEST_METHOD']);
$status = $router->tryFunctionCall($route);
if ($route !== null) {

View file

@ -3,7 +3,7 @@
namespace Venom\Views;
use RuntimeException;
use \RuntimeException;
use Venom\Core\DatabaseHandler;
use Venom\Models\DataModel;
@ -87,8 +87,8 @@ class DataLoader
private function generateID(string $id = ''): string
{
if ($id === '') {
$id = bin2hex(random_bytes(16));
$id = bin2hex(random_bytes(32));
}
return $id;
return hash('SHA256', $id);
}
}
}

View file

@ -6,12 +6,14 @@ namespace Venom\Views;
use Venom\Core\ArgumentHandler;
use Venom\Core\Config;
use Venom\Helper\MetaGenerator;
use Venom\Venom;
class VenomRenderer
{
private Venom $venom;
private ?RenderController $controller;
private ?MetaGenerator $metaGenerator;
private string $templateData = '';
private array $vars = [];
private string $baseTemplate = '';
@ -84,7 +86,7 @@ class VenomRenderer
return $this->vars[$name];
}
public function deleteVar($name)
public function deleteVar($name): void
{
unset($this->vars[$name]);
}
@ -93,7 +95,16 @@ class VenomRenderer
{
$this->controller = $controller;
$data = Config::getInstance()->getRenderer();
$this->baseTemplate = $data->baseFile . '.php' ?? 'base.php';
$this->templateDir = __DIR__ . '/../../../tpl/' . $data->theme . '/';
$theme = $data->theme;
$base = $data->baseFile ?? 'base';
$this->metaGenerator = new MetaGenerator();
if (Config::getInstance()->isAdmin()) {
$base = 'base';
$theme = 'admin';
} else {
$this->metaGenerator->loadById();
}
$this->baseTemplate = $base . '.php';
$this->templateDir = __DIR__ . '/../../../tpl/' . $theme . '/';
}
}

19
tpl/admin/base.php Normal file
View file

@ -0,0 +1,19 @@
<?php
use Venom\Models\User;
use \Venom\Security\Security;
if (!Security::get()->hasRole(User::ADMIN_ROLE)) {
?>
<form method="post" action="/admin/api/login">
<input type="text" name="USERNAME" placeholder="Username">
<input type="password" name="PASSWORD" placeholder="Password">
<input type="hidden" name="REDIRECT_TO" value="/admin/">
<input type="submit" value="Login">
</form>
<?php
echo 'Login!';
} else {
echo 'Admin Interface!';
echo '<a href="/admin/api/login/logout">Ausloggen</a>';
}