Redesign \SnappyMail\HTTP\CSP for better control

This commit is contained in:
the-djmaze 2023-02-22 13:57:37 +01:00
parent 915a142315
commit bb77d0ae02
3 changed files with 44 additions and 53 deletions

View file

@ -6,9 +6,9 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin
NAME = 'reCaptcha',
AUTHOR = 'SnappyMail',
URL = 'https://snappymail.eu/',
VERSION = '2.14',
RELEASE = '2023-01-11',
REQUIRED = '2.23',
VERSION = '2.15',
RELEASE = '2023-02-22',
REQUIRED = '2.26.4',
CATEGORY = 'General',
LICENSE = 'MIT',
DESCRIPTION = 'A CAPTCHA (v2) is a program that can generate and grade tests that humans can pass but current computer programs cannot. For example, humans can read distorted text as the one shown below, but current computer programs can\'t. More info at https://developers.google.com/recaptcha';
@ -141,12 +141,9 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin
public function ContentSecurityPolicy(\SnappyMail\HTTP\CSP $CSP)
{
// $CSP->script[] = 'https://www.google.com/recaptcha/';
$CSP->script[] = 'https://www.gstatic.com/recaptcha/';
$CSP->script[] = 'https://www.recaptcha.net/recaptcha/';
// $CSP->frame[] = 'https://www.google.com/recaptcha/';
// $CSP->frame[] = 'https://recaptcha.google.com/recaptcha/';
$CSP->frame[] = 'https://www.recaptcha.net/recaptcha/';
$CSP->add('script-src', 'https://www.gstatic.com/recaptcha/');
$CSP->add('script-src', 'https://www.recaptcha.net/recaptcha/');
$CSP->add('frame-src', 'https://www.recaptcha.net/recaptcha/');
}
}

View file

@ -41,15 +41,14 @@ abstract class Api
$CSP = new \SnappyMail\HTTP\CSP(\trim($oConfig->Get('security', 'content_security_policy', '')));
$CSP->report = $oConfig->Get('security', 'csp_report', false);
$CSP->report_only = $oConfig->Get('debug', 'enable', false); // || SNAPPYMAIL_DEV
// $CSP->frame = \explode(' ', $oConfig->Get('security', 'csp_frame', ''));
// Allow https: due to remote images in e-mails or use proxy
if (!$oConfig->Get('security', 'use_local_proxy_for_external_images', '')) {
$CSP->img[] = 'https:';
$CSP->img[] = 'http:';
$CSP->add('img-src', 'https:');
$CSP->add('img-src', 'http:');
}
if ($sScriptNonce) {
$CSP->script[] = "'nonce-{$sScriptNonce}'";
$CSP->add('script-src', "'nonce-{$sScriptNonce}'");
}
static::Actions()->Plugins()->RunHook('main.content-security-policy', array($CSP));

View file

@ -8,64 +8,59 @@ namespace SnappyMail\HTTP;
class CSP
{
public
$base = ["'self'"],
$default = ["'self'", 'data:'],
// Knockout.js requires eval() for observable binding purposes
// Safari < 15.4 does not support strict-dynamic
// $script = ["'strict-dynamic'", "'unsafe-eval'"],
$script = ["'self'", "'unsafe-eval'"],
// Knockout.js requires unsafe-inline?
// $script = ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
$img = ["'self'", 'data:'],
$style = ["'self'", "'unsafe-inline'"],
$frame = [],
$frame_ancestors = [],
$report = false,
$report_to = [],
$report_only = false;
private $directives = [
'base-uri' => ["'self'"],
'default-src' => ["'self'", 'data:'],
// Knockout.js requires eval() for observable binding purposes
// Safari < 15.4 does not support strict-dynamic
// 'script-src' => ["'strict-dynamic'", "'unsafe-eval'"],
'script-src' => ["'self'", "'unsafe-eval'"],
// Knockout.js requires unsafe-inline?
// 'script-src' => ["'self'", "'unsafe-inline'", "'unsafe-eval'"],
'img-src' => ["'self'", 'data:'],
'style-src' => ["'self'", "'unsafe-inline'"],
];
function __construct(string $default = '')
{
if ($default) {
foreach (\explode(';', $default) as $directive) {
$values = \preg_split('/\\s+/', $directive);
$name = \str_replace('-', '_', \preg_replace('/-(src|uri)$/D', '', \trim(\array_shift($values))));
$this->$name = \array_unique(\array_merge($this->$name, $values));
$sources = \preg_split('/\\s+/', \trim($directive));
$directive = \array_shift($sources);
if (!isset($this->directives[$directive])) {
$this->directives[$directive] = [];
}
$this->directives[$directive] = \array_merge($this->directives[$directive], $sources);
}
}
}
function __toString() : string
{
$params = [
'base-uri ' . \implode(' ', \array_unique($this->base)),
'default-src ' . \implode(' ', \array_unique($this->default))
];
if ($this->script) {
$params[] = 'script-src ' . \implode(' ', \array_unique($this->script));
}
if ($this->img) {
$params[] = 'img-src ' . \implode(' ', \array_unique($this->img));
}
if ($this->style) {
$params[] = 'style-src ' . \implode(' ', \array_unique($this->style));
}
if ($this->frame) {
$params[] = 'frame-src ' . \implode(' ', \array_unique($this->frame));
}
if ($this->frame_ancestors) {
$params[] = 'frame-ancestors ' . \implode(' ', \array_unique($this->frame_ancestors));
}
// Deprecated
// report-uri deprecated
unset($this->directives['report-uri']);
if ($this->report) {
$params[] = 'report-uri ./?/CspReport';
$this->directives['report-uri'] = [\RainLoop\Utils::WebPath() . '?/CspReport'];
}
$params = [];
foreach ($this->directives as $directive => $sources) {
$params[] = $directive . ' ' . \implode(' ', \array_unique($sources));
}
return \implode('; ', $params);
}
public function add(string $directive, string $source) : void
{
if (!isset($this->directives[$directive])) {
$this->directives[$directive] = [];
}
$this->directives[$directive][] = $source;
}
public function setHeaders() : void
{
if ($this->report_only) {
@ -73,7 +68,7 @@ class CSP
} else {
\header('Content-Security-Policy: ' . $this);
}
if (!$this->frame_ancestors) {
if (empty($this->directives['frame-ancestors'])) {
\header('X-Frame-Options: DENY');
} else {
// \header('X-Frame-Options: SAMEORIGIN');