Twig, Tests, AES,
This commit is contained in:
@@ -2,69 +2,64 @@
|
||||
|
||||
namespace Elements;
|
||||
|
||||
use Driver\SQL\SQL;
|
||||
use Objects\User;
|
||||
|
||||
abstract class Document {
|
||||
|
||||
protected Head $head;
|
||||
protected Body $body;
|
||||
protected User $user;
|
||||
protected bool $databaseRequired;
|
||||
private ?string $activeView;
|
||||
private bool $cspEnabled;
|
||||
private ?string $cspNonce;
|
||||
|
||||
public function __construct(User $user, $headClass, $bodyClass, ?string $view = NULL) {
|
||||
public function __construct(User $user) {
|
||||
$this->user = $user;
|
||||
$this->head = new $headClass($this);
|
||||
$this->body = new $bodyClass($this);
|
||||
$this->cspEnabled = false;
|
||||
$this->cspNonce = null;
|
||||
$this->databaseRequired = true;
|
||||
$this->activeView = $view;
|
||||
}
|
||||
|
||||
public function getHead(): Head { return $this->head; }
|
||||
public function getBody(): Body { return $this->body; }
|
||||
public function getSQL(): ?\Driver\SQL\SQL { return $this->user->getSQL(); }
|
||||
public function getUser(): User { return $this->user; }
|
||||
|
||||
public function getView() : ?View {
|
||||
|
||||
if ($this->activeView === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$view = parseClass($this->activeView);
|
||||
$file = getClassPath($view);
|
||||
if(!file_exists($file) || !is_subclass_of($view, View::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new $view($this);
|
||||
public function getSQL(): ?SQL {
|
||||
return $this->user->getSQL();
|
||||
}
|
||||
|
||||
public function getRequestedView(): string {
|
||||
return $this->activeView;
|
||||
public function getUser(): User {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
function getCode(): string {
|
||||
public function getCSPNonce(): ?string {
|
||||
return $this->cspNonce;
|
||||
}
|
||||
|
||||
public function isCSPEnabled(): bool {
|
||||
return $this->cspEnabled;
|
||||
}
|
||||
|
||||
public function enableCSP() {
|
||||
$this->cspEnabled = true;
|
||||
$this->cspNonce = generateRandomString(16, "base62");
|
||||
}
|
||||
|
||||
public function getCode(): string {
|
||||
if ($this->databaseRequired) {
|
||||
$sql = $this->user->getSQL();
|
||||
if (is_null($sql)) {
|
||||
die("Database is not configured yet.");
|
||||
} else if(!$sql->isConnected()) {
|
||||
} else if (!$sql->isConnected()) {
|
||||
die("Database is not connected: " . $sql->getLastError());
|
||||
}
|
||||
}
|
||||
|
||||
$body = $this->body->getCode();
|
||||
$head = $this->head->getCode();
|
||||
$lang = $this->user->getLanguage()->getShortCode();
|
||||
if ($this->cspEnabled) {
|
||||
$csp = ["default-src 'self'", "object-src 'none'", "base-uri 'self'", "style-src 'self' 'unsafe-inline'", "script-src 'nonce-$this->cspNonce'"];
|
||||
if ($this->user->getConfiguration()->getSettings()->isRecaptchaEnabled()) {
|
||||
$csp[] = "frame-src https://www.google.com/ 'self'";
|
||||
}
|
||||
|
||||
$html = "<!DOCTYPE html>";
|
||||
$html .= "<html lang=\"$lang\">";
|
||||
$html .= $head;
|
||||
$html .= $body;
|
||||
$html .= "</html>";
|
||||
return $html;
|
||||
$compiledCSP = implode(";", $csp);
|
||||
header("Content-Security-Policy: $compiledCSP;");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
79
core/Elements/HtmlDocument.class.php
Normal file
79
core/Elements/HtmlDocument.class.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Elements;
|
||||
|
||||
use Objects\User;
|
||||
|
||||
class HtmlDocument extends Document {
|
||||
|
||||
protected Head $head;
|
||||
protected Body $body;
|
||||
private ?string $activeView;
|
||||
|
||||
public function __construct(User $user, $headClass, $bodyClass, ?string $view = NULL) {
|
||||
parent::__construct($user);
|
||||
$this->head = $headClass ? new $headClass($this) : null;
|
||||
$this->body = $bodyClass ? new $bodyClass($this) : null;
|
||||
$this->activeView = $view;
|
||||
}
|
||||
|
||||
public function getHead(): Head { return $this->head; }
|
||||
public function getBody(): Body { return $this->body; }
|
||||
|
||||
public function getView() : ?View {
|
||||
|
||||
if ($this->activeView === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$view = parseClass($this->activeView);
|
||||
$file = getClassPath($view);
|
||||
if(!file_exists($file) || !is_subclass_of($view, View::class)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new $view($this);
|
||||
}
|
||||
|
||||
public function createScript($type, $src, $content = ""): Script {
|
||||
$script = new Script($type, $src, $content);
|
||||
|
||||
if ($this->isCSPEnabled()) {
|
||||
$script->setNonce($this->getCSPNonce());
|
||||
}
|
||||
|
||||
return $script;
|
||||
}
|
||||
|
||||
public function getRequestedView(): string {
|
||||
return $this->activeView;
|
||||
}
|
||||
|
||||
function getCode(): string {
|
||||
|
||||
parent::getCode();
|
||||
|
||||
// generate body first, so we can modify head
|
||||
$body = $this->body->getCode();
|
||||
|
||||
if ($this->isCSPEnabled()) {
|
||||
foreach ($this->head->getSources() as $element) {
|
||||
if ($element instanceof Script || $element instanceof Link) {
|
||||
$element->setNonce($this->getCSPNonce());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$head = $this->head->getCode();
|
||||
$lang = $this->user->getLanguage()->getShortCode();
|
||||
|
||||
$html = "<!DOCTYPE html>";
|
||||
$html .= "<html lang=\"$lang\">";
|
||||
$html .= $head;
|
||||
$html .= $body;
|
||||
$html .= "</html>";
|
||||
return $html;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -15,15 +15,30 @@ class Link extends StaticView {
|
||||
private string $type;
|
||||
private string $rel;
|
||||
private string $href;
|
||||
private ?string $nonce;
|
||||
|
||||
function __construct($rel, $href, $type = "") {
|
||||
$this->href = $href;
|
||||
$this->type = $type;
|
||||
$this->rel = $rel;
|
||||
$this->nonce = null;
|
||||
}
|
||||
|
||||
function getCode(): string {
|
||||
$type = (empty($this->type) ? "" : " type=\"$this->type\"");
|
||||
return "<link rel=\"$this->rel\" href=\"$this->href\"$type/>";
|
||||
$attributes = ["rel" => $this->rel, "href" => $this->href];
|
||||
|
||||
if (!empty($this->type)) {
|
||||
$attributes["type"] = $this->type;
|
||||
}
|
||||
if (!empty($this->nonce)) {
|
||||
$attributes["nonce"] = $this->nonce;
|
||||
}
|
||||
|
||||
$attributes = html_attributes($attributes);
|
||||
return "<link $attributes/>";
|
||||
}
|
||||
|
||||
public function setNonce(string $nonce) {
|
||||
$this->nonce = $nonce;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,35 @@ class Script extends StaticView {
|
||||
const INSTALL = "/js/install.js";
|
||||
const BOOTSTRAP = "/js/bootstrap.bundle.min.js";
|
||||
const ACCOUNT = "/js/account.js";
|
||||
const SECLAB = "/js/seclab.min.js";
|
||||
const FONTAWESOME = "/js/fontawesome-all.min.js";
|
||||
|
||||
private string $type;
|
||||
private string $content;
|
||||
private string $src;
|
||||
private ?string $nonce;
|
||||
|
||||
function __construct($type, $src, $content = "") {
|
||||
$this->src = $src;
|
||||
$this->type = $type;
|
||||
$this->content = $content;
|
||||
$this->nonce = null;
|
||||
}
|
||||
|
||||
function getCode(): string {
|
||||
$src = (empty($this->src) ? "" : " src=\"$this->src\"");
|
||||
return "<script type=\"$this->type\"$src>$this->content</script>";
|
||||
$attributes = ["type" => $this->type];
|
||||
if (!empty($this->src)) {
|
||||
$attributes["src"] = $this->src;
|
||||
}
|
||||
|
||||
if (!empty($this->nonce)) {
|
||||
$attributes["nonce"] = $this->nonce;
|
||||
}
|
||||
|
||||
$attributes = html_attributes($attributes);
|
||||
return "<script $attributes>$this->content</script>";
|
||||
}
|
||||
|
||||
public function setNonce(string $nonce) {
|
||||
$this->nonce = $nonce;
|
||||
}
|
||||
}
|
||||
71
core/Elements/TemplateDocument.class.php
Normal file
71
core/Elements/TemplateDocument.class.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Elements;
|
||||
|
||||
use Objects\User;
|
||||
use Twig\Environment;
|
||||
use Twig\Error\LoaderError;
|
||||
use Twig\Error\RuntimeError;
|
||||
use Twig\Error\SyntaxError;
|
||||
use Twig\Loader\FilesystemLoader;
|
||||
|
||||
class TemplateDocument extends Document {
|
||||
|
||||
private string $templateName;
|
||||
protected array $parameters;
|
||||
private Environment $twigEnvironment;
|
||||
private FilesystemLoader $twigLoader;
|
||||
|
||||
public function __construct(User $user, string $templateName, array $initialParameters = []) {
|
||||
parent::__construct($user);
|
||||
$this->templateName = $templateName;
|
||||
$this->parameters = $initialParameters;
|
||||
$this->twigLoader = new FilesystemLoader(WEBROOT . '/core/Templates');
|
||||
$this->twigEnvironment = new Environment($this->twigLoader, [
|
||||
'cache' => WEBROOT . '/core/TemplateCache',
|
||||
'auto_reload' => true
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getTemplateName(): string {
|
||||
return $this->templateName;
|
||||
}
|
||||
|
||||
protected function loadParameters() {
|
||||
|
||||
}
|
||||
|
||||
public function getCode(): string {
|
||||
parent::getCode();
|
||||
$this->loadParameters();
|
||||
return $this->renderTemplate($this->templateName, $this->parameters);
|
||||
}
|
||||
|
||||
public function renderTemplate(string $name, array $params = []): string {
|
||||
try {
|
||||
|
||||
$params["user"] = [
|
||||
"lang" => $this->user->getLanguage()->getShortCode(),
|
||||
"loggedIn" => $this->user->isLoggedIn(),
|
||||
];
|
||||
|
||||
$settings = $this->user->getConfiguration()->getSettings();
|
||||
$params["site"] = [
|
||||
"name" => $settings->getSiteName(),
|
||||
"baseUrl" => $settings->getBaseUrl(),
|
||||
"recaptcha" => [
|
||||
"key" => $settings->isRecaptchaEnabled() ? $settings->getRecaptchaSiteKey() : null,
|
||||
"enabled" => $settings->isRecaptchaEnabled(),
|
||||
],
|
||||
"csp" => [
|
||||
"nonce" => $this->getCSPNonce(),
|
||||
"enabled" => $this->isCSPEnabled()
|
||||
]
|
||||
];
|
||||
|
||||
return $this->twigEnvironment->render($name, $params);
|
||||
} catch (LoaderError | RuntimeError | SyntaxError $e) {
|
||||
return "<b>Error rendering twig template: " . htmlspecialchars($e->getMessage()) . "</b>";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user