Skip to content

Commit f8b5487

Browse files
committed
OWN-154 Configuration options for CSP
1 parent 3eff376 commit f8b5487

File tree

11 files changed

+312
-60
lines changed

11 files changed

+312
-60
lines changed

Cli/Analyze.php

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,21 @@ class Analyze
1717
{
1818
private const DESC = 'Analyze CSP violation reports and compose policy rules.';
1919
private const NAME = 'fl32:csp:analyze';
20-
20+
/** @var \Flancer32\Csp\Helper\Config */
21+
private $hlpCfg;
2122
/** @var \Magento\Framework\App\ResourceConnection */
2223
private $resource;
2324
/** @var \Flancer32\Csp\Service\Report\Analyze */
2425
private $srvAnalyze;
2526

2627
public function __construct(
2728
\Magento\Framework\App\ResourceConnection $resource,
29+
\Flancer32\Csp\Helper\Config $hlpCfg,
2830
\Flancer32\Csp\Service\Report\Analyze $srvAnalyze
2931
) {
3032
parent::__construct(self::NAME, self::DESC);
3133
$this->resource = $resource;
34+
$this->hlpCfg = $hlpCfg;
3235
$this->srvAnalyze = $srvAnalyze;
3336
}
3437

@@ -38,16 +41,21 @@ protected function execute(InputInterface $input, OutputInterface $output)
3841

3942
$output->writeln("<info>Command '" . $this->getName() . "': " . $this->getDescription() . "<info>");
4043
$this->checkAreaCode();
41-
$conn = $this->resource->getConnection();
42-
$conn->beginTransaction();
43-
$req = new \Flancer32\Csp\Service\Report\Analyze\Request();
44-
$resp = $this->srvAnalyze->execute($req);
45-
$conn->commit();
46-
$added = $resp->getRulesAdded();
47-
$deleted = $resp->getReportsDeleted();
48-
$min = $resp->getReportsIdMin();
49-
$max = $resp->getReportsIdMax();
50-
$output->writeln("<info>$added rules were added. $deleted reports were deleted (id: $min-$max).<info>");
44+
if ($this->hlpCfg->getEnabled()) {
45+
$conn = $this->resource->getConnection();
46+
$conn->beginTransaction();
47+
$req = new \Flancer32\Csp\Service\Report\Analyze\Request();
48+
$resp = $this->srvAnalyze->execute($req);
49+
$conn->commit();
50+
$added = $resp->getRulesAdded();
51+
$deleted = $resp->getReportsDeleted();
52+
$min = $resp->getReportsIdMin();
53+
$max = $resp->getReportsIdMax();
54+
$output->writeln("<info>$added rules were added. $deleted reports were deleted (id: $min-$max).<info>");
55+
} else {
56+
$output->writeln("<info>CSP reports processing is disabled. "
57+
. "Please, enable it at 'Configuration / Security / CSP / General / Enable'.<info>");
58+
}
5159
$output->writeln('<info>Command \'' . $this->getName() . '\' is completed.<info>');
5260
}
5361

Config.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ class Config
3737
'worker-src'
3838
];
3939

40+
const HTTP_HEAD_CSP = 'Content-Security-Policy';
41+
const HTTP_HEAD_CSP_REPORT_ONLY = 'Content-Security-Policy-Report-Only';
42+
4043
/** This module name. */
4144
const MODULE = self::MODULE_VENDOR . '_' . self::MODULE_PACKAGE;
4245
const MODULE_PACKAGE = 'Csp';

Controller/Adminhtml/Report/Index.php

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@ class Index
1717
const HTTP_NO_CONTENT = 204;
1818
/** @var \Magento\Framework\App\Response\HttpFactory */
1919
private $factHttpResponse;
20+
/** @var \Flancer32\Csp\Helper\Config */
21+
private $hlpCfg;
2022
/** @var \Flancer32\Csp\Service\Report\Save */
2123
private $srvReportSave;
2224

2325
public function __construct(
2426
\Magento\Backend\App\Action\Context $context,
2527
\Magento\Framework\App\Response\HttpFactory $factHttpResponse,
28+
\Flancer32\Csp\Helper\Config $hlpCfg,
2629
\Flancer32\Csp\Service\Report\Save $srvReportSave
2730
) {
2831
parent::__construct($context);
2932
$this->factHttpResponse = $factHttpResponse;
33+
$this->hlpCfg = $hlpCfg;
3034
$this->srvReportSave = $srvReportSave;
3135
}
3236

@@ -51,16 +55,18 @@ public function createCsrfValidationException(RequestInterface $request): ?Inval
5155

5256
public function execute()
5357
{
54-
// Read POSTed data and convert it into PHP object.
55-
$rawBody = file_get_contents('php://input');
56-
$data = json_decode($rawBody);
57-
if (isset($data->{'csp-report'})) {
58-
$req = new \Flancer32\Csp\Service\Report\Save\Request();
59-
$req->setIsAdmin(true);
60-
$req->setReport($data->{'csp-report'});
61-
$this->srvReportSave->execute($req);
58+
if ($this->hlpCfg->getEnabled()) {
59+
// Read POSTed data and convert it into PHP object.
60+
$rawBody = file_get_contents('php://input');
61+
$data = json_decode($rawBody);
62+
if (isset($data->{'csp-report'})) {
63+
$req = new \Flancer32\Csp\Service\Report\Save\Request();
64+
$req->setIsAdmin(true);
65+
$req->setReport($data->{'csp-report'});
66+
$this->srvReportSave->execute($req);
67+
}
6268
}
63-
// ... then create response
69+
// ... then create NO_CONTENT response
6470
/** @var \Magento\Framework\App\Response\Http $result */
6571
$result = $this->factHttpResponse->create();
6672
$result->setHttpResponseCode(self::HTTP_NO_CONTENT);

Controller/Report/Index.php

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,21 @@ class Index
1818

1919
/** @var \Magento\Framework\App\Response\HttpFactory */
2020
private $factHttpResponse;
21+
/** @var \Flancer32\Csp\Helper\Config */
22+
private $hlpCfg;
2123
/** @var \Flancer32\Csp\Service\Report\Save */
2224
private $srvReportSave;
2325

2426
public function __construct(
2527
\Magento\Framework\App\Action\Context $context,
2628
\Psr\Log\LoggerInterface $logger,
2729
\Magento\Framework\App\Response\HttpFactory $factHttpResponse,
30+
\Flancer32\Csp\Helper\Config $hlpCfg,
2831
\Flancer32\Csp\Service\Report\Save $srvReportSave
2932
) {
3033
parent::__construct($context);
3134
$this->factHttpResponse = $factHttpResponse;
35+
$this->hlpCfg = $hlpCfg;
3236
$this->srvReportSave = $srvReportSave;
3337
}
3438

@@ -39,16 +43,18 @@ public function createCsrfValidationException(RequestInterface $request): ?Inval
3943

4044
public function execute()
4145
{
42-
// Read POSTed data and convert it into PHP object.
43-
$rawBody = file_get_contents('php://input');
44-
$data = json_decode($rawBody);
45-
if (isset($data->{'csp-report'})) {
46-
$req = new \Flancer32\Csp\Service\Report\Save\Request();
47-
$req->setIsAdmin(false);
48-
$req->setReport($data->{'csp-report'});
49-
$this->srvReportSave->execute($req);
46+
if ($this->hlpCfg->getEnabled()) {
47+
// Read POSTed data and convert it into PHP object.
48+
$rawBody = file_get_contents('php://input');
49+
$data = json_decode($rawBody);
50+
if (isset($data->{'csp-report'})) {
51+
$req = new \Flancer32\Csp\Service\Report\Save\Request();
52+
$req->setIsAdmin(false);
53+
$req->setReport($data->{'csp-report'});
54+
$this->srvReportSave->execute($req);
55+
}
5056
}
51-
// ... then create response
57+
// ... then create NO_CONTENT response
5258
/** @var \Magento\Framework\App\Response\Http $result */
5359
$result = $this->factHttpResponse->create();
5460
$result->setHttpResponseCode(self::HTTP_NO_CONTENT);

Helper/Config.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
/**
3+
* Authors: Alex Gusev <[email protected]>
4+
* Since: 2020
5+
*/
6+
7+
namespace Flancer32\Csp\Helper;
8+
9+
use Magento\Framework\App\Config\ScopeConfigInterface as AScopeCfg;
10+
11+
/**
12+
* Helper to get store configuration parameters related to the module.
13+
*
14+
* @SuppressWarnings(PHPMD.BooleanGetMethodName)
15+
*/
16+
class Config
17+
{
18+
/** @var \Magento\Framework\App\State */
19+
private $appState;
20+
/** @var \Magento\Framework\App\Config\ScopeConfigInterface */
21+
private $scopeConfig;
22+
23+
public function __construct(
24+
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
25+
\Magento\Framework\App\State $appState
26+
) {
27+
$this->scopeConfig = $scopeConfig;
28+
$this->appState = $appState;
29+
}
30+
31+
/**
32+
* Cron activity.
33+
*
34+
* @param string $scopeType
35+
* @param string $scopeCode
36+
* @return bool
37+
*/
38+
public function getCronEnabled($scopeType = AScopeCfg::SCOPE_TYPE_DEFAULT, $scopeCode = null)
39+
{
40+
$result = $this->getEnabled($scopeType, $scopeCode);
41+
if ($result) {
42+
$result = $this->scopeConfig->getValue('fl32_csp/cron/enabled', $scopeType, $scopeCode);
43+
$result = filter_var($result, FILTER_VALIDATE_BOOLEAN);
44+
}
45+
return $result;
46+
}
47+
48+
/**
49+
* Activity of the module.
50+
*
51+
* @param string $scopeType
52+
* @param string $scopeCode
53+
* @return bool
54+
*/
55+
public function getEnabled($scopeType = AScopeCfg::SCOPE_TYPE_DEFAULT, $scopeCode = null)
56+
{
57+
$result = $this->scopeConfig->getValue('fl32_csp/general/enabled', $scopeType, $scopeCode);
58+
$result = filter_var($result, FILTER_VALIDATE_BOOLEAN);
59+
return $result;
60+
}
61+
62+
/**
63+
* Should new rules be active by default?
64+
*
65+
* @param string $scopeType
66+
* @param string $scopeCode
67+
* @return bool
68+
*/
69+
public function getRulesNewAreActive($scopeType = AScopeCfg::SCOPE_TYPE_DEFAULT, $scopeCode = null)
70+
{
71+
$result = $this->getEnabled($scopeType, $scopeCode);
72+
if ($result) {
73+
$result = $this->scopeConfig->getValue('fl32_csp/rules/new_rules_active', $scopeType, $scopeCode);
74+
$result = filter_var($result, FILTER_VALIDATE_BOOLEAN);
75+
}
76+
return $result;
77+
}
78+
79+
/**
80+
* Use 'Content-Security-Policy-Report-Only' or 'Content-Security-Policy'.
81+
*
82+
* @param string $scopeType
83+
* @param string $scopeCode
84+
* @return bool
85+
*/
86+
public function getRulesReportOnly($scopeType = AScopeCfg::SCOPE_TYPE_DEFAULT, $scopeCode = null)
87+
{
88+
$result = $this->getEnabled($scopeType, $scopeCode);
89+
if ($result) {
90+
$result = $this->scopeConfig->getValue('fl32_csp/rules/report_only', $scopeType, $scopeCode);
91+
$result = filter_var($result, FILTER_VALIDATE_BOOLEAN);
92+
}
93+
return $result;
94+
}
95+
96+
}

Model/Collector/Db.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,27 @@ class Db
2525
];
2626
/** @var \Flancer32\Csp\Model\Collector\Db\A\Query\GetRules */
2727
private $aQGetRules;
28+
/** @var \Flancer32\Csp\Helper\Config */
29+
private $hlpCfg;
2830

2931
public function __construct(
32+
\Flancer32\Csp\Helper\Config $hlpCfg,
3033
\Flancer32\Csp\Model\Collector\Db\A\Query\GetRules $aQGetRules
3134
) {
35+
$this->hlpCfg = $hlpCfg;
3236
$this->aQGetRules = $aQGetRules;
3337
}
3438

3539
public function collect(array $defaultPolicies = []): array
3640
{
37-
$rules = $this->getRules();
38-
foreach ($rules as $rule) {
39-
$id = $rule[QGetRules::A_TYPE];
40-
$source = $rule[QGetRules::A_SOURCE];
41-
$policy = new \Magento\Csp\Model\Policy\FetchPolicy($id, false, [$source]);
42-
$defaultPolicies[] = $policy;
41+
if ($this->hlpCfg->getEnabled()) {
42+
$rules = $this->getRules();
43+
foreach ($rules as $rule) {
44+
$id = $rule[QGetRules::A_TYPE];
45+
$source = $rule[QGetRules::A_SOURCE];
46+
$policy = new \Magento\Csp\Model\Policy\FetchPolicy($id, false, [$source]);
47+
$defaultPolicies[] = $policy;
48+
}
4349
}
4450
return $defaultPolicies;
4551
}

Plugin/Magento/Csp/Observer/Render.php

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
class Render
1212
{
13+
/** @var \Flancer32\Csp\Helper\Config */
14+
private $hlpCfg;
1315
/** @var \Magento\Framework\App\State */
1416
private $state;
1517
/** @var \Magento\Backend\Model\Url */
@@ -20,11 +22,13 @@ class Render
2022
public function __construct(
2123
\Magento\Framework\App\State $state,
2224
\Magento\Backend\Model\Url $urlBack,
23-
\Magento\Framework\Url $urlFront
25+
\Magento\Framework\Url $urlFront,
26+
\Flancer32\Csp\Helper\Config $hlpCfg
2427
) {
2528
$this->state = $state;
2629
$this->urlBack = $urlBack;
2730
$this->urlFront = $urlFront;
31+
$this->hlpCfg = $hlpCfg;
2832
}
2933

3034
/**
@@ -41,22 +45,38 @@ public function aroundExecute(
4145
) {
4246
// Collect all CSP rules and compose HTTP header.
4347
$proceed($observer);
44-
45-
// Setup reporting in HTTP header.
46-
/** @var \Magento\Framework\App\Response\HttpInterface $response */
47-
$response = $observer->getEvent()->getData('response');
48-
49-
$uri = $this->getReportUri();
50-
51-
// 'Report-To' is not widely supported yet, so remove it.
52-
// (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to)
53-
$response->clearHeader('Report-To');
54-
// TODO: we should get 'Content-Security-Policy-Report-Only' or 'Content-Security-Policy'
55-
$cspHeader = $response->getHeader('Content-Security-Policy-Report-Only');
56-
$value = $cspHeader->getFieldValue();
57-
$value = str_replace('report-to report-endpoint;', '', $value);
58-
$value .= " report-uri $uri;";
59-
$response->setHeader('Content-Security-Policy-Report-Only', $value);
48+
// ... then modify HTTP header
49+
if ($this->hlpCfg->getEnabled()) {
50+
// Setup reporting in HTTP header.
51+
/** @var \Magento\Framework\App\Response\HttpInterface $response */
52+
$response = $observer->getEvent()->getData('response');
53+
// URI to get CSP violation reports for admin/front areas
54+
$uri = $this->getReportUri();
55+
// 'Report-To' is not widely supported yet, so remove it. Use 'report-uri' directive instead.
56+
// (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to)
57+
$response->clearHeader('Report-To');
58+
// Get current CSP header and clear it.
59+
$cspHeader = $response->getHeader(Cfg::HTTP_HEAD_CSP_REPORT_ONLY);
60+
if ($cspHeader) {
61+
$response->clearHeader(Cfg::HTTP_HEAD_CSP_REPORT_ONLY);
62+
} else {
63+
$cspHeader = $response->getHeader(Cfg::HTTP_HEAD_CSP);
64+
$response->clearHeader(Cfg::HTTP_HEAD_CSP);
65+
}
66+
// Modify CSP header if exists.
67+
if ($cspHeader) {
68+
$value = $cspHeader->getFieldValue();
69+
// use deprecated 'report-uri' instead of 'report-to' because Chrome doesn't work correctly with
70+
// new 'report'to' or with both directives.
71+
$value = str_replace('report-to report-endpoint;', '', $value);
72+
// only one 'report-uri' directive is allowed
73+
$pattern = '/report-uri\s*.*;/';
74+
$value = preg_replace($pattern, '', $value);
75+
$value .= "report-uri $uri;";
76+
$header = $this->hlpCfg->getRulesReportOnly() ? Cfg::HTTP_HEAD_CSP_REPORT_ONLY : Cfg::HTTP_HEAD_CSP;
77+
$response->setHeader($header, $value);
78+
}
79+
}
6080
}
6181

6282
private function getReportUri()

0 commit comments

Comments
 (0)