mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-11-10 09:02:45 +08:00
Improved Content-Security-Policy management for Captcha issue #222
This commit is contained in:
parent
b257fac19b
commit
911f833ede
5 changed files with 113 additions and 19 deletions
|
@ -301,6 +301,10 @@ $Plugin->addHook('hook.name', 'functionName');
|
|||
\RainLoop\Model\Account $oAccount
|
||||
int $iLimit
|
||||
|
||||
### main.content-security-policy
|
||||
params:
|
||||
\SnappyMail\HTTP\CSP $oCSP
|
||||
|
||||
### main.default-response
|
||||
params:
|
||||
string $sActionName
|
||||
|
|
|
@ -6,9 +6,9 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
NAME = 'reCaptcha',
|
||||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '2.12',
|
||||
RELEASE = '2022-02-11',
|
||||
REQUIRED = '2.12.0',
|
||||
VERSION = '2.12.1',
|
||||
RELEASE = '2022-02-14',
|
||||
REQUIRED = '2.12.1',
|
||||
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';
|
||||
|
@ -24,6 +24,7 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
|
||||
$this->addHook('json.action-pre-call', 'AjaxActionPreCall');
|
||||
$this->addHook('filter.json-response', 'FilterAjaxResponse');
|
||||
$this->addHook('main.content-security-policy', 'ContentSecurityPolicy');
|
||||
}
|
||||
|
||||
protected function configMapping() : array
|
||||
|
@ -77,7 +78,7 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
public function FilterAppDataPluginSection(bool $bAdmin, bool $bAuth, array &$aConfig) : void
|
||||
{
|
||||
if (!$bAdmin && !$bAuth) {
|
||||
$aConfig['show_captcha_on_login'] = 1;
|
||||
$aConfig['show_captcha_on_login'] = 1 > $this->getLimit();;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,4 +141,13 @@ 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->frame[] = 'https://www.google.com/recaptcha/';
|
||||
$CSP->frame[] = 'https://recaptcha.google.com/recaptcha/';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -240,23 +240,20 @@ abstract class Service
|
|||
|
||||
private static function setCSP(string $sScriptNonce = null) : void
|
||||
{
|
||||
// "img-src https:" is allowed due to remote images in e-mails
|
||||
$sContentSecurityPolicy = \trim(Api::Config()->Get('security', 'content_security_policy', ''))
|
||||
?: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data: https: http:; style-src 'self' 'unsafe-inline'";
|
||||
if (Api::Config()->Get('security', 'use_local_proxy_for_external_images', '')) {
|
||||
$sContentSecurityPolicy = \preg_replace('/(img-src[^;]+)\\shttps:(\\s|;|$)/D', '$1$2', $sContentSecurityPolicy);
|
||||
$sContentSecurityPolicy = \preg_replace('/(img-src[^;]+)\\shttp:(\\s|;|$)/D', '$1$2', $sContentSecurityPolicy);
|
||||
$CSP = new \SnappyMail\HTTP\CSP(\trim(Api::Config()->Get('security', 'content_security_policy', '')));
|
||||
$CSP->report_only = Api::Config()->Get('debug', 'enable', false); // '0.0.0' === APP_VERSION
|
||||
// Allow https: due to remote images in e-mails or use proxy
|
||||
if (!Api::Config()->Get('security', 'use_local_proxy_for_external_images', '')) {
|
||||
$CSP->img[] = 'https:';
|
||||
$CSP->img[] = 'http:';
|
||||
}
|
||||
// Internet Explorer does not support 'nonce'
|
||||
if (!$_SERVER['HTTP_USER_AGENT'] || (!\strpos($_SERVER['HTTP_USER_AGENT'], 'Trident/') && !\strpos($_SERVER['HTTP_USER_AGENT'], 'Edge/1'))) {
|
||||
if ($sScriptNonce) {
|
||||
$sContentSecurityPolicy = \str_replace('script-src', "script-src 'nonce-{$sScriptNonce}'", $sContentSecurityPolicy);
|
||||
}
|
||||
// Knockout.js requires unsafe-inline?
|
||||
$sContentSecurityPolicy = \preg_replace("/(script-src[^;]+)'unsafe-inline'/", '$1', $sContentSecurityPolicy);
|
||||
// Knockout.js requires eval() for observable binding purposes
|
||||
//$sContentSecurityPolicy = \preg_replace("/(script-src[^;]+)'unsafe-eval'/", '$1', $sContentSecurityPolicy);
|
||||
if ($sScriptNonce && !$_SERVER['HTTP_USER_AGENT'] || (!\strpos($_SERVER['HTTP_USER_AGENT'], 'Trident/') && !\strpos($_SERVER['HTTP_USER_AGENT'], 'Edge/1'))) {
|
||||
$CSP->script[] = "'nonce-{$sScriptNonce}'";
|
||||
}
|
||||
\header('Content-Security-Policy: '.$sContentSecurityPolicy);
|
||||
|
||||
Api::Actions()->Plugins()->RunHook('main.content-security-policy', array($CSP));
|
||||
|
||||
$CSP->setHeaders();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -371,6 +371,11 @@ class ServiceActions
|
|||
return '';
|
||||
}
|
||||
|
||||
public function ServiceCspReport() : void
|
||||
{
|
||||
\SnappyMail\HTTP\CSP::logReport();
|
||||
}
|
||||
|
||||
public function ServiceRaw() : string
|
||||
{
|
||||
$sResult = '';
|
||||
|
|
78
snappymail/v/0.0.0/app/libraries/snappymail/http/csp.php
Normal file
78
snappymail/v/0.0.0/app/libraries/snappymail/http/csp.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
/**
|
||||
* Controls the content_security_policy
|
||||
*/
|
||||
|
||||
namespace SnappyMail\HTTP;
|
||||
|
||||
class CSP
|
||||
{
|
||||
public
|
||||
$default = ["'self'"],
|
||||
// Knockout.js requires unsafe-inline?
|
||||
// Knockout.js requires eval() for observable binding purposes
|
||||
$script = ["'self'", "'unsafe-eval'"/*, "'unsafe-inline'"*/],
|
||||
$img = ["'self'", 'data:'],
|
||||
$style = ["'self'", "'unsafe-inline'"],
|
||||
$frame = [],
|
||||
$report_to = [],
|
||||
$report_only = false;
|
||||
|
||||
function __construct(string $default = '')
|
||||
{
|
||||
if ($default) {
|
||||
foreach (\explode(';', $default) as $directive) {
|
||||
$values = \explode(' ', $directive);
|
||||
$name = \preg_replace('/-.+/', '', \trim(\array_shift($values)));
|
||||
$this->$name = $values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __toString() : string
|
||||
{
|
||||
$params = [
|
||||
'default-src ' . \implode(' ', $this->default),
|
||||
'script-src ' . \implode(' ', $this->script),
|
||||
'img-src ' . \implode(' ', $this->img),
|
||||
'style-src ' . \implode(' ', $this->style),
|
||||
];
|
||||
if ($this->script) {
|
||||
$params[] = 'script-src ' . \implode(' ', $this->script);
|
||||
}
|
||||
if ($this->img) {
|
||||
$params[] = 'img-src ' . \implode(' ', $this->img);
|
||||
}
|
||||
if ($this->style) {
|
||||
$params[] = 'style-src ' . \implode(' ', $this->style);
|
||||
}
|
||||
if ($this->frame) {
|
||||
$params[] = 'frame-src ' . \implode(' ', $this->frame);
|
||||
}
|
||||
// Deprecated
|
||||
$params[] = 'report-uri ./?/CspReport';
|
||||
|
||||
return \implode('; ', $params);
|
||||
}
|
||||
|
||||
public function setHeaders() : void
|
||||
{
|
||||
if ($this->report_only) {
|
||||
\header('Content-Security-Policy-Report-Only: ' . $this);
|
||||
} else {
|
||||
\header('Content-Security-Policy: ' . $this);
|
||||
}
|
||||
}
|
||||
|
||||
public static function logReport() : void
|
||||
{
|
||||
\http_response_code(204);
|
||||
$json = \file_get_contents('php://input');
|
||||
$report = \json_decode($json, true);
|
||||
// Useless to log 'moz-extension' as there's no clue which extension violates
|
||||
if ($json && $report && 'moz-extension' !== $report['csp-report']['source-file']) {
|
||||
\SnappyMail\Log::error('CSP', $json);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue