Files
Froxlor/lib/Froxlor/UI/Panel/UI.php
Michael Kaufmann 77bcd10729 removed deprecated/old x-xss-protection http-header
Signed-off-by: Michael Kaufmann <d00p@froxlor.org>
2023-10-25 15:03:57 +02:00

377 lines
11 KiB
PHP

<?php
/**
* This file is part of the Froxlor project.
* Copyright (c) 2010 the Froxlor Team (see authors).
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can also view it online at
* https://files.froxlor.org/misc/COPYING.txt
*
* @copyright the authors
* @author Froxlor team <team@froxlor.org>
* @license https://files.froxlor.org/misc/COPYING.txt GPLv2
*/
declare(strict_types=1);
namespace Froxlor\UI\Panel;
use DirectoryIterator;
use Exception;
use Froxlor\CurrentUser;
use Froxlor\FileDir;
use Froxlor\Froxlor;
use Froxlor\PhpHelper;
use Froxlor\Settings;
use Froxlor\UI\Linker;
use Twig\Environment;
use Twig\Extension\DebugExtension;
use Twig\Loader\FilesystemLoader;
class UI
{
/**
* twig object
*
* @var Environment
*/
private static $twig = null;
/**
* twig buffer
*
* @var array
*/
private static $twigbuf = [];
/**
* linker class object
*/
private static $linker = null;
/**
* current logged in user
*
* @var array
*/
private static $userinfo = [];
/**
* default fallback theme
*
* @var string
*/
private static $default_theme = 'Froxlor';
private static $install_mode = false;
public static function requestIsHttps(): bool
{
$isHttps =
$_SERVER['HTTPS']
?? $_SERVER['REQUEST_SCHEME']
?? $_SERVER['HTTP_X_FORWARDED_PROTO']
?? null;
return $isHttps && (strcasecmp('on', $isHttps) == 0 || strcasecmp('https', $isHttps) == 0);
}
/**
* Extract the cookie host from HTTP_HOST, stripping the port.
*/
public static function getCookieHost(): ?string
{
if (empty($_SERVER['HTTP_HOST']))
return null;
$colonPosition = strrpos($_SERVER['HTTP_HOST'], ':');
// There's no port in the host
if ($colonPosition === false)
return $_SERVER['HTTP_HOST'];
$closingSquareBracketPosition = strrpos($_SERVER['HTTP_HOST'], ']');
// The host is an IPv4 address or hostname with port
if ($closingSquareBracketPosition === false)
return substr($_SERVER['HTTP_HOST'], 0, $colonPosition);
// The host is an IPv6 address with port
return substr($_SERVER['HTTP_HOST'], 0, $closingSquareBracketPosition + 1);
}
/**
* send various security related headers
*/
public static function sendHeaders()
{
session_set_cookie_params([
'lifetime' => self::$install_mode ? 7200 : 600, // will be renewed based on settings in lib/init.php
'path' => '/',
'domain' => self::getCookieHost(),
'secure' => self::requestIsHttps(),
'httponly' => true,
'samesite' => 'Strict'
]);
session_start();
header("Content-Type: text/html; charset=UTF-8");
// prevent Froxlor pages from being cached
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Pragma: no-cache");
header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T', time()));
header('Expires: ' . gmdate('D, d M Y H:i:s \G\M\T', time()));
// Prevent inline - JS to be executed (i.e. XSS) in browsers which support this,
// Inline-JS is no longer allowed and used
// See: http://people.mozilla.org/~bsterne/content-security-policy/index.html
// New stuff see: https://www.owasp.org/index.php/List_of_useful_HTTP_headers and https://www.owasp.org/index.php/Content_Security_Policy
$csp_content = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; object-src 'self'; frame-src 'self'; frame-ancestors 'self';";
header("Content-Security-Policy: " . $csp_content);
header("X-Content-Security-Policy: " . $csp_content);
header("X-WebKit-CSP: " . $csp_content);
// Don't allow to load Froxlor in an iframe to prevent i.e. clickjacking
header("X-Frame-Options: DENY");
// Internet Explorer shall not guess the Content-Type, see:
// http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx
header("X-Content-Type-Options: nosniff");
// ensure that default timezone is set
if (function_exists("date_default_timezone_set") && function_exists("date_default_timezone_get")) {
@date_default_timezone_set(@date_default_timezone_get());
}
}
public static function sendSslHeaders()
{
/**
* If Froxlor was called via HTTPS -> enforce it for the next time by settings HSTS header according to settings
*/
if (isset($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off')) {
$maxage = Settings::Get('system.hsts_maxage');
if (empty($maxage)) {
$maxage = 0;
}
$hsts_header = "Strict-Transport-Security: max-age=" . $maxage;
if (Settings::Get('system.hsts_incsub') == '1') {
$hsts_header .= "; includeSubDomains";
}
if (Settings::Get('system.hsts_preload') == '1') {
$hsts_header .= "; preload";
}
header($hsts_header);
}
}
/**
* initialize Twig template engine
*/
public static function initTwig(bool $install_mode = false)
{
self::$install_mode = $install_mode;
// init twig template engine
$loader = new FilesystemLoader(Froxlor::getInstallDir() . '/templates/');
$twig_params = [
'auto_reload' => true,
'debug' => false,
];
if (is_writable(Froxlor::getInstallDir() . '/cache')) {
$twig_params['cache'] = Froxlor::getInstallDir() . '/cache';
}
self::$twig = new Environment($loader, $twig_params);
self::$twig->addExtension(new DebugExtension());
self::$twig->addExtension(new CustomReflection());
self::$twig->addExtension(new FroxlorTwig());
// empty buffer
self::$twigbuf = [];
}
/**
* twig wrapper
*
* @return Environment
*/
public static function twig(): ?Environment
{
return self::$twig;
}
public static function getLinker(): Linker
{
return self::$linker;
}
public static function setLinker($linker = null)
{
self::$linker = $linker;
}
public static function setCurrentUser($userinfo = null)
{
self::$userinfo = $userinfo;
}
public static function getCurrentUser(): array
{
return self::$userinfo;
}
/**
* returns an array of available themes
*
* @return array
* @throws Exception
*/
public static function getThemes(): array
{
$themespath = FileDir::makeCorrectDir(Froxlor::getInstallDir() . '/templates/');
$themes_available = [];
if (is_dir($themespath)) {
$its = new DirectoryIterator($themespath);
foreach ($its as $it) {
if ($it->isDir() && $it->getFilename() != '.' && $it->getFilename() != '..' && $it->getFilename() != 'misc') {
$theme = $themespath . $it->getFilename();
if (file_exists($theme . '/config.json')) {
$themeconfig = json_decode(file_get_contents($theme . '/config.json'), true);
if (array_key_exists('variants', $themeconfig) && is_array($themeconfig['variants'])) {
foreach ($themeconfig['variants'] as $variant => $data) {
if ($variant == "default") {
$themes_available[$it->getFilename()] = $it->getFilename();
} elseif (array_key_exists('description', $data)) {
$themes_available[$it->getFilename() . '_' . $variant] = $data['description'];
} else {
$themes_available[$it->getFilename() . '_' . $variant] = $it->getFilename() . ' (' . $variant . ')';
}
}
} else {
$themes_available[$it->getFilename()] = $it->getFilename();
}
}
}
}
}
return $themes_available;
}
public static function view($name, array $context = [])
{
self::twigBuffer($name, $context);
self::twigOutputBuffer();
}
/**
* wrapper for twig's "render" function to buffer the output
*
* @see \Twig\Environment::render()
*/
public static function twigBuffer($name, array $context = [])
{
$template_file = self::validateThemeTemplate($name);
self::$twigbuf[] = [
$template_file => $context
];
}
public static function validateThemeTemplate(string $name, string $theme = "")
{
if (empty(trim($theme))) {
$theme = self::getTheme();
}
$template_file = $theme . '/' . $name;
if (!file_exists(Froxlor::getInstallDir() . '/templates/' . $template_file)) {
PhpHelper::phpErrHandler(E_USER_WARNING, "Template '" . $template_file . "' could not be found, trying fallback theme", __FILE__, __LINE__);
$template_file = self::$default_theme . '/'. $name;
if (!file_exists(Froxlor::getInstallDir() . '/templates/' . $template_file)) {
PhpHelper::phpErrHandler(E_USER_ERROR, "Unknown template '" . $template_file . "'", __FILE__, __LINE__);
}
}
return $template_file;
}
public static function getTheme()
{
// fallback
$theme = self::$default_theme;
if (!self::$install_mode) {
// system default
if (Froxlor::versionCompare2(Settings::Get('panel.version'), '2.0.0-beta1') == -1) {
// pre 2.0
Settings::Set('panel.default_theme', 'Froxlor');
} else {
$theme = (Settings::Get('panel.default_theme') !== null) ? Settings::Get('panel.default_theme') : $theme;
// customer theme
if (CurrentUser::hasSession() && CurrentUser::getField('theme') != $theme) {
$theme = CurrentUser::getField('theme');
}
}
}
if (!file_exists(Froxlor::getInstallDir() . '/templates/' . $theme)) {
PhpHelper::phpErrHandler(E_USER_WARNING, "Theme '" . $theme . "' could not be found.", __FILE__, __LINE__);
$theme = self::$default_theme;
}
return $theme;
}
/**
* echo output buffer and empty buffer-content
*/
public static function twigOutputBuffer()
{
$output = "";
foreach (self::$twigbuf as $buf) {
foreach ($buf as $name => $context) {
try {
$output .= self::$twig->render($name, $context);
} catch (Exception $e) {
// whoops, template error
$errtpl = 'alert_nosession.html.twig';
if (self::activeUserSession()) {
$errtpl = 'alert.html.twig';
}
$edata = [
'type' => "danger",
'heading' => "Template error",
'alert_msg' => $e->getMessage(),
'alert_info' => $e->getTraceAsString()
];
try {
// try with user theme if set
$output .= self::$twig->render(self::getTheme() . '/misc/' . $errtpl, $edata);
} catch (Exception $e) {
// try with default theme if different from user theme
if (self::getTheme() != self::$default_theme) {
$output .= self::$twig->render(self::$default_theme . '/misc/' . $errtpl, $edata);
} else {
throw $e;
}
}
}
}
}
echo $output;
// empty buffer
self::$twigbuf = [];
}
public static function activeUserSession(): bool
{
return !empty(self::$userinfo);
}
}