Improved settings of CSPs.

Added manifest.json.
Added further security-related HTTP headers.
This commit is contained in:
Joshua Ramon Enslin 2018-06-18 13:57:35 +02:00 committed by Stefan Rohde-Enslin
parent 1acdc7ba2b
commit 067beedf29
12 changed files with 165 additions and 34 deletions

View File

@ -59,7 +59,7 @@ if (isset($task) and $task == "update") {
if (!isset($public)) $public = false; if (!isset($public)) $public = false;
echo printBackendHead($pageTitle); echo printBackendHead($settings, $pageTitle, $pageTitle, $settings['logo']);
echo printBackendHeader($pageTitle, $translations["help$id"]); echo printBackendHeader($pageTitle, $translations["help$id"]);
echo ' echo '

View File

@ -1,26 +1,37 @@
<?PHP <?PHP
/** /**
* Functions for forming the HTML output. * Functions for forming the HTML output.
*
* @file
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/ */
/** /**
* Prints the head element of an HTML page * Prints the head element of an HTML page
* *
* @param array $settings Settings variable.
* @param string $page Name / ID of the current page. * @param string $page Name / ID of the current page.
* @param string $title Title of the page. * @param string $title Title of the page.
* @param string $icon The icon of the website. * @param string $icon The icon of the website.
* *
* @return string * @return string
*/ */
function printBackendHead(string $page = "home", string $title = "Home", string $icon = ""):string { function printBackendHead(array $settings, string $page = "home", string $title = "Home", string $icon = ""):string {
$output = '<!DOCTYPE html> $output = '<!DOCTYPE html>
<html lang="en" id="' . $page . '"> <html lang="en" id="' . $page . '">
<head> <head>
<!-- Content Security policies --> <!-- Content Security policies -->
<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; script-src \'self\'; connect-src \'self\'; img-src \'self\' data: blob:; style-src \'self\' \'unsafe-inline\'; font-src \'self\';" /> <meta http-equiv="Content-Security-Policy" content="default-src \'none\'; script-src \'self\'; connect-src \'self\'; img-src \'self\' data: blob: ' . $settings['mdVersion'];
if ($settings['CSPimageSources']) $output .= " " . $settings['CSPimageSources']; // Allow embedding of whitelisted images.
$output .= '; style-src \'self\' \'unsafe-inline\'; frame-src \'self\'';
if ($settings['CSPobjectSources']) $output .= " " . $settings['CSPobjectSources']; // Allow embedding of whitelisted frame contents / objects.
$output .= '; object-src \'self\'';
if ($settings['CSPobjectSources']) $output .= " " . $settings['CSPobjectSources']; // Allow embedding of whitelisted frame contents / objects.
$output .= '; frame-ancestors \'self\';font-src \'self\';" />
<title>' . $title . '</title> <title>' . $title . '</title>
<link rel="stylesheet" type="text/css" href="themes/imports.css"> <link rel="stylesheet" type="text/css" href="themes/imports.css">
@ -29,7 +40,7 @@ function printBackendHead(string $page = "home", string $title = "Home", string
if ($icon) { if ($icon) {
$output .= ' $output .= '
<link rel="shortcut icon" sizes="16x16 32x32" href="' . $icon . '" /> <link rel="shortcut icon" href="' . $icon . '" />
'; ';
} }

View File

@ -25,7 +25,7 @@ $pages = loadPages(); // Load overview of pages.
* Output * Output
*/ */
echo printBackendHead($translations['start'], $translations['start'], $settings['logo']); echo printBackendHead($settings, $translations['start'], $translations['start'], $settings['logo']);
echo printBackendHeader($translations['start'], $translations['helpStart']); echo printBackendHeader($translations['start'], $translations['helpStart']);
echo ' echo '

View File

@ -75,7 +75,7 @@ if (isset($task)) {
if (!isset($public)) $public = false; if (!isset($public)) $public = false;
echo printBackendHead($pageTitle, $pageTitle, $settings['logo']); echo printBackendHead($settings, $pageTitle, $pageTitle, $settings['logo']);
echo printBackendHeader($pageTitle, $translations['helpSinglePage']); echo printBackendHeader($pageTitle, $translations['helpSinglePage']);
echo ' echo '

View File

@ -25,7 +25,7 @@ $pages = loadPages(); // Load overview of pages.
* Output * Output
*/ */
echo printBackendHead($translations['pagesOverview'], $translations['pagesOverview'], $settings['logo']); echo printBackendHead($settings, $translations['pagesOverview'], $translations['pagesOverview'], $settings['logo']);
echo printBackendHeader($translations['pagesOverview'], $translations['helpPagesOverview']); echo printBackendHeader($translations['pagesOverview'], $translations['helpPagesOverview']);
echo ' echo '

View File

@ -22,7 +22,7 @@ $pages = loadPages(); // Load overview of pages.
*/ */
// Check for vars. // Check for vars.
loadHttpToGlobals(["task", "startPage", "pageTitle", "logo", "url", "css", "hideInstitution", "mdVersion", "mdImgFolder", "cacheRefreshInterval", "limitToInstitutions", "maxFileSize", "defaultLang"]); loadHttpToGlobals(["task", "startPage", "pageTitle", "logo", "url", "css", "hideInstitution", "mdVersion", "mdImgFolder", "cacheRefreshInterval", "limitToInstitutions", "maxFileSize", "sendHTTPHeaders", "CSPimageSources", "CSPobjectSources", "defaultLang"]);
if (isset($task) and $task == "update") { // Adding new users. if (isset($task) and $task == "update") { // Adding new users.
@ -36,7 +36,7 @@ if (isset($task) and $task == "update") { // Adding new users.
if (isset($mdImgFolder)) $mdImgFolder = rtrim($mdImgFolder, "/") . "/"; if (isset($mdImgFolder)) $mdImgFolder = rtrim($mdImgFolder, "/") . "/";
if (isset($limitToInstitutions)) $settings['limitToInstitutions'] = array_diff(explode(',', $limitToInstitutions), ['']); if (isset($limitToInstitutions)) $settings['limitToInstitutions'] = array_diff(explode(',', $limitToInstitutions), ['']);
foreach (["startPage", "pageTitle", "logo", "url", "css", "hideInstitution", "mdVersion", "mdImgFolder", "cacheRefreshInterval", "maxFileSize", "defaultLang"] as $var) { foreach (["startPage", "pageTitle", "logo", "url", "css", "hideInstitution", "mdVersion", "mdImgFolder", "cacheRefreshInterval", "maxFileSize", "sendHTTPHeaders", "CSPimageSources", "CSPobjectSources", "defaultLang"] as $var) {
if (isset($$var)) $settings[$var] = $$var; if (isset($$var)) $settings[$var] = $$var;
} }
@ -53,7 +53,7 @@ if (isset($task) and $task == "update") { // Adding new users.
* Output * Output
*/ */
echo printBackendHead($translations['settings'], $translations['settings'], $settings['logo']); echo printBackendHead($settings, $translations['settings'], $translations['settings'], $settings['logo']);
echo printBackendHeader($translations['settings'], $translations['helpSettings']); echo printBackendHeader($translations['settings'], $translations['helpSettings']);
echo ' echo '
@ -70,6 +70,10 @@ echo '
<form action="" method="POST"> <form action="" method="POST">
<table class="obj_cha_maintable"> <table class="obj_cha_maintable">
<tr>
<th colspan="3" class="sectionTH">' . $translations['general'] . '</th>
</tr>
<!-- Start page --> <!-- Start page -->
<tr> <tr>
<th><label for="startPage">' . $translations['startPage'] . '</label></th> <th><label for="startPage">' . $translations['startPage'] . '</label></th>
@ -117,6 +121,7 @@ echo '
<select name="css" id="settingsUsedCSS"> <select name="css" id="settingsUsedCSS">
'; ';
foreach (scanDirConts(__DIR__ . "/../themes") as $cssOption) { foreach (scanDirConts(__DIR__ . "/../themes") as $cssOption) {
if (!is_dir(__DIR__ . "/../themes/$cssOption")) continue;
echo '<option value="' . $cssOption . '"'; echo '<option value="' . $cssOption . '"';
if ($settings['css'] == $cssOption) echo ' selected'; if ($settings['css'] == $cssOption) echo ' selected';
echo '>' . $cssOption . '</option>'; echo '>' . $cssOption . '</option>';
@ -127,13 +132,25 @@ echo '
<td>' . generateHelpToolTip("helpSettingsUsedCSS", $translations['settingsUsedCSS'], $translations['helpSettingsUsedCSS']) . '</td> <td>' . generateHelpToolTip("helpSettingsUsedCSS", $translations['settingsUsedCSS'], $translations['helpSettingsUsedCSS']) . '</td>
</tr> </tr>
<!-- Hiding attribution (if the page is for only one museum) or not -->
<tr> <tr>
<th><label for="hideInstitution">' . $translations['hideInstitution'] . '</label></th> <th><label for="language">' . $translations['language'] . '</label></th>
<td> <td>
<input name="hideInstitution" id="hideInstitution" type="range" min="0" max="1" value="' . (string)$settings['hideInstitution'] . '" /> <select name="defaultLang" id="language">
';
foreach (scanDirConts(__DIR__ . "/translations") as $lang) {
$lang = pathinfo($lang)['filename'];
echo '<option value="' . $lang . '"';
if ($settings['defaultLang'] == $lang) echo ' selected';
echo '>' . $lang . '</option>';
}
echo '
</select>
</td> </td>
<td>' . generateHelpToolTip("helpHideInstitution", $translations['hideInstitution'], $translations['helpHideInstitution']) . '</td> <td>' . generateHelpToolTip("helpLanguage", $translations['language'], $translations['helpLanguage']) . '</td>
</tr>
<tr>
<th colspan="3" class="sectionTH">' . $translations['integrationWithMD'] . '</th>
</tr> </tr>
<!-- MD Version --> <!-- MD Version -->
@ -164,6 +181,19 @@ echo '
<td>' . generateHelpToolTip("helpLimitToInstitutions", $translations['limitToInstitutions'], $translations['helpLimitToInstitutions']) . '</td> <td>' . generateHelpToolTip("helpLimitToInstitutions", $translations['limitToInstitutions'], $translations['helpLimitToInstitutions']) . '</td>
</tr> </tr>
<!-- Hiding attribution (if the page is for only one museum) or not -->
<tr>
<th><label for="hideInstitution">' . $translations['hideInstitution'] . '</label></th>
<td>
<input name="hideInstitution" id="hideInstitution" type="range" min="0" max="1" value="' . (string)$settings['hideInstitution'] . '" />
</td>
<td>' . generateHelpToolTip("helpHideInstitution", $translations['hideInstitution'], $translations['helpHideInstitution']) . '</td>
</tr>
<tr>
<th colspan="3" class="sectionTH">' . $translations['security'] . '</th>
</tr>
<!-- Max Upload Size --> <!-- Max Upload Size -->
<tr> <tr>
<th><label for="maxFileSize">' . $translations['maxFileSize'] . '</label></th> <th><label for="maxFileSize">' . $translations['maxFileSize'] . '</label></th>
@ -171,21 +201,27 @@ echo '
<td>' . generateHelpToolTip("helpMaxFileSize", $translations['maxFileSize'], $translations['helpMaxFileSize']) . '</td> <td>' . generateHelpToolTip("helpMaxFileSize", $translations['maxFileSize'], $translations['helpMaxFileSize']) . '</td>
</tr> </tr>
<!-- Whether or not to send security-related headers -->
<tr> <tr>
<th><label for="language">' . $translations['language'] . '</label></th> <th><label for="sendHTTPHeaders">' . $translations['sendHTTPHeaders'] . '</label></th>
<td> <td>
<select name="defaultLang" id="language"> <input name="sendHTTPHeaders" id="sendHTTPHeaders" type="range" min="0" max="1" value="' . (string)$settings['sendHTTPHeaders'] . '" />
';
foreach (scanDirConts(__DIR__ . "/translations") as $lang) {
$lang = pathinfo($lang)['filename'];
echo '<option value="' . $lang . '"';
if ($settings['defaultLang'] == $lang) echo ' selected';
echo '>' . $lang . '</option>';
}
echo '
</select>
</td> </td>
<td>' . generateHelpToolTip("helpLanguage", $translations['language'], $translations['helpLanguage']) . '</td> <td>' . generateHelpToolTip("helpSendHTTPHeaders", $translations['sendHTTPHeaders'], $translations['helpSendHTTPHeaders']) . '</td>
</tr>
<!-- Image sources whitelisted for CSPs -->
<tr>
<th><label for="CSPimageSources">' . $translations['CSPimageSources'] . '</label></th>
<td><input type="text" id="CSPimageSources" name="CSPimageSources" placeholder="' . $translations['CSPimageSources']. '" value="'.$settings['CSPimageSources'].'" /></td>
<td>' . generateHelpToolTip("helpCSPimageSources", $translations['CSPimageSources'], $translations['helpCSPimageSources']) . '</td>
</tr>
<!-- Object and frame sources whitelisted for CSPs -->
<tr>
<th><label for="CSPobjectSources">' . $translations['CSPobjectSources'] . '</label></th>
<td><input type="text" id="CSPobjectSources" name="CSPobjectSources" placeholder="' . $translations['CSPobjectSources']. '" value="'.$settings['CSPobjectSources'].'" /></td>
<td>' . generateHelpToolTip("helpCSPobjectSources", $translations['CSPobjectSources'], $translations['helpCSPobjectSources']) . '</td>
</tr> </tr>
<tr> <tr>

View File

@ -109,6 +109,8 @@ table.obj_cha_maintable button:only-child,
table.obj_cha_maintable input:only-child { width: 100%; } table.obj_cha_maintable input:only-child { width: 100%; }
table.obj_cha_maintable textarea:only-child { width: 100%; height: 14em; } table.obj_cha_maintable textarea:only-child { width: 100%; height: 14em; }
table.obj_cha_maintable th.sectionTH { padding: 1em 0; font-size: 1.1em; font-weight: bold; }
table.obj_cha_maintable > tbody > tr:first-child th.sectionTH { padding: 0 0 1em 0; }
table.obj_cha_maintable input[type="range"]:only-child { margin: .5em -1em 0 0; } table.obj_cha_maintable input[type="range"]:only-child { margin: .5em -1em 0 0; }
@media screen and (min-width:65em) { @media screen and (min-width:65em) {

View File

@ -16,6 +16,9 @@ $translations = [
"preview" => "Preview", "preview" => "Preview",
"banner" => "Banner", "banner" => "Banner",
"delete" => "Delete", "delete" => "Delete",
"general" => "General Settings",
"security" => "Security",
"integrationWithMD" => "Integration with Museum-Digital",
"languageUnavailable" => "This language is not available.", "languageUnavailable" => "This language is not available.",
"settingsUpdated" => "Updated settings.", "settingsUpdated" => "Updated settings.",
"staticPageTitle" => "Page title", "staticPageTitle" => "Page title",
@ -41,6 +44,29 @@ $translations = [
"helpAdmin" => "<p>Is the user an administrator?</p>", "helpAdmin" => "<p>Is the user an administrator?</p>",
"language" => "Language", "language" => "Language",
"helpLanguage" => "<p>The default language of this instance of md:cms.</p>", "helpLanguage" => "<p>The default language of this instance of md:cms.</p>",
"sendHTTPHeaders" => "Send additional HTTP Headers",
"helpSendHTTPHeaders" => "<p>md:cms can send additional directives to the browser to increase security. Your server administrator can set these server wide, and if they have done so already, you should disable this option (this is by far the prefered way). In most cases, server administrators have not opted to do so yet, and keeping this option enabled makes sense. If you want to inquire into this further, the <a href='https://observatory.mozilla.org/'>Mozilla Observatory</a> is a useful resource.</p>
<p>
The default headers sent are the following:
<pre><code>
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; preload
Referrer-Policy: strict-origin
</code></pre>
</p>",
"CSPimageSources" => "Image sources (whitelist)",
"helpCSPimageSources" => "
<p>To increase security, md:cms directs browsers to refrain from loading images from any but the whitelisted sources. By default, only the current domain and the linked instance of museum-digital are whitelisted.</p>
<p>If you, for example, want to embed images from flickr.com, you either need to download the images and upload them here, or you whitelist flickr. To whitelist domains, please enter them one by one into the field, separated by whitespaces. E.g.<br />
<code>https://www.flickr.com https://www.google.com</code>
</p>",
"CSPobjectSources" => "Object sources (whitelist)",
"helpCSPobjectSources" => "
<p>To increase security, md:cms directs browsers to refrain from loading frames and objects from any but the whitelisted sources. By default, only the current domain and the linked instance of museum-digital are whitelisted.</p>
<p>If you, for example, want to embed videos from youtube.com, you need to whitelist youtube. To whitelist domains, please enter them one by one into the field, separated by whitespaces. E.g.<br />
<code>https://www.youtube.com https://www.vimeo.com</code>
</p>",
"maxFileSize" => "Maximum upload size", "maxFileSize" => "Maximum upload size",
"helpMaxFileSize" => "<p>The maximum file size of file uploads.</p>", "helpMaxFileSize" => "<p>The maximum file size of file uploads.</p>",
"cacheRefreshInterval" => "Cache Refresh Interval", "cacheRefreshInterval" => "Cache Refresh Interval",

View File

@ -84,7 +84,7 @@ if (isset($task) and $task == "insert") { // Adding new users.
* Output * Output
*/ */
echo printBackendHead($translations['start'], $translations['start'], $settings['logo']); echo printBackendHead($settings, $translations['start'], $translations['start'], $settings['logo']);
echo printBackendHeader($translations['usersOverview'], $translations['helpUsers']); echo printBackendHeader($translations['usersOverview'], $translations['helpUsers']);
echo ' echo '

View File

@ -74,19 +74,28 @@ function ensureEnvironment() {
"logo" => "", "logo" => "",
"url" => "", "url" => "",
"css" => "default", "css" => "default",
"hideInstitution" => 0, "defaultLang" => "en",
"cacheRefreshInterval" => 0, "cacheRefreshInterval" => 0,
"mdVersion" => "https://rlp.museum-digital.de/", "mdVersion" => "https://rlp.museum-digital.de/",
"mdImgFolder" => "https://rlp.museum-digital.de/data/rlp/", "mdImgFolder" => "https://rlp.museum-digital.de/data/rlp/",
"hideInstitution" => 0,
"limitToInstitutions" => [], "limitToInstitutions" => [],
"sendHTTPHeaders" => 1,
"CSPimageSources" => "",
"CSPobjectSources" => "",
"maxFileSize" => 300000, "maxFileSize" => 300000,
"defaultLang" => "en"
], ],
json_decode(file_get_contents(__DIR__ . "/../data/settings.json"), true) json_decode(file_get_contents(__DIR__ . "/../data/settings.json"), true)
); );
$GLOBALS['settings'] = $settings; $GLOBALS['settings'] = $settings;
if ($settings['sendHTTPHeaders']) {
header('X-Content-Type-Options: nosniff');
header('X-XSS-Protection: 1; mode=block');
header('Strict-Transport-Security: max-age=31536000; preload');
header('Referrer-Policy: strict-origin');
}
} }

View File

@ -25,11 +25,18 @@ function printPublicHead(array $settings, string $page = "home", string $title =
<head> <head>
<!-- Content Security policies --> <!-- Content Security policies -->
<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; script-src \'self\'; connect-src \'self\' ' . $settings['mdVersion'] . '; img-src \'self\' ' . $settings['mdVersion'] . '; style-src \'self\' \'unsafe-inline\'; font-src \'self\';" /> <meta http-equiv="Content-Security-Policy" content="default-src \'none\'; script-src \'self\'; connect-src \'self\' ' . $settings['mdVersion'] . '; img-src \'self\' ' . $settings['mdVersion'];
if ($settings['CSPimageSources']) $output .= " " . $settings['CSPimageSources']; // Allow embedding of whitelisted images.
$output .= '; style-src \'self\' \'unsafe-inline\'; font-src \'self\'; frame-src \'self\'';
if ($settings['CSPobjectSources']) $output .= " " . $settings['CSPobjectSources']; // Allow embedding of whitelisted frame contents / objects.
$output .= '; object-src \'self\'';
if ($settings['CSPobjectSources']) $output .= " " . $settings['CSPobjectSources']; // Allow embedding of whitelisted frame contents / objects.
$output .= '; frame-ancestors \'self\'; base-uri \'none\'; form-action \'self\';" />
<title>' . $title . '</title> <title>' . $title . '</title>
<link rel="stylesheet" type="text/css" href="themes/' . $settings['css'] . '/theme.css" />
<link rel="stylesheet" type="text/css" href="themes/imports.css" /> <link rel="stylesheet" type="text/css" href="themes/imports.css" />
<link rel="stylesheet" type="text/css" href="themes/' . $settings['css'] . '/theme.css" />
<link rel="manifest" href="./manifest.php">
<meta http-equiv="content-type" content="text/html;charset=utf-8" />'; <meta http-equiv="content-type" content="text/html;charset=utf-8" />';
$output .= $additional; $output .= $additional;

40
manifest.php Normal file
View File

@ -0,0 +1,40 @@
<?PHP
/**
* This file generates a web manifest based on the settings.
*
* @author Joshua Ramon Enslin <joshua@jrenslin.de>
*/
// Include functions and settings.
require_once __DIR__ . "/inc/functions.php";
// Ensure working environment for frontend.
ensureEnvironment();
// Fill output array
$data = [];
$data["name"] = $data['short_name'] = $settings['pageTitle'];
$data["start_url"] = "/";
$data["display"] = "standalone";
$data["background_color"] = "#000";
$data["theme_color"] = "#AFB42B";
$data["description"] = "Website of " . $settings['pageTitle'];
/*
$data['icons'] = [
"src" => $settings['logo'],
"type" => mime_content_type(__DIR__ . $settings['logo'])
];
*/
// Return JSON-encoded data.
header('Content-Type: application/json');
echo json_encode($data, JSON_PRETTY_PRINT);
?>