Skip to content

Commit

Permalink
Add CSP Domains from SiteConfig
Browse files Browse the repository at this point in the history
- Support subsites for CSP headers per site
- Support several different types of sources from SiteConfig
- Managable from SiteConfig
- Permission providers for those that are allowed to add or remove CSP domains

# Todo
- Required defaults from config yml file
- Disallow 'self' or wildcard domains
- Enable Wizard or Report Only mode from SiteConfig
  • Loading branch information
Firesphere committed Dec 30, 2018
1 parent 7e5b37d commit 139a266
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 5 deletions.
5 changes: 4 additions & 1 deletion _config/extensions.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
PageController:
extensions:
- Firesphere\CSPHeaders\Extensions\ControllerCSPExtension
- Firesphere\CSPHeaders\Extensions\ControllerCSPExtension
SilverStripe\SiteConfig\SiteConfig:
extensions:
- Firesphere\CSPHeaders\Extensions\SiteConfigExtension
20 changes: 20 additions & 0 deletions src/Extensions/ControllerCSPExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

namespace Firesphere\CSPHeaders\Extensions;

use Firesphere\CSPHeaders\Models\CSPDomain;
use Firesphere\CSPHeaders\View\CSPBackend;
use Phpcsp\Security\ContentSecurityPolicyHeaderBuilder;
use Phpcsp\Security\InvalidValueException;
Expand All @@ -11,6 +12,7 @@
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Extension;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\SiteConfig\SiteConfig;

/**
* Class \Firesphere\CSPHeaders\Extensions\ControllerCSPExtension
Expand Down Expand Up @@ -87,6 +89,7 @@ public function onAfterInit()
if (Director::isLive() || $this->checkCookie($this->owner->getRequest())) {
$policy = $this->setDefaultPolicies();
$this->setConfigPolicies($policy);
$this->setSiteConfigPolicies($policy);
$this->setReportPolicy();

$headers = $policy->getHeaders(CSPBackend::config()->get('legacy_headers'));
Expand All @@ -99,6 +102,7 @@ public function onAfterInit()
/**
* @param HTTPRequest $request
* @return bool
* default-src 'self' http://192.168.33.5/ piwik.casa-laguna.net; font-src 'self' http://192.168.33.5/ netdna.bootstrapcdn.com fonts.gstatic.com; form-action 'self' http://192.168.33.5/; frame-src 'self' http://192.168.33.5/ *.vimeocdn.com player.vimeo.com www.youtube.com www.youtube-nocookie.com; img-src 'self' http://192.168.33.5/ secure.gravatar.com a.slack-edge.com avatars.slack-edge.com emoji.slack-edge.com *.imgur.com imgur.com *.wp.com piwik.casa-laguna.net data: i.ytimg.com packagist.org www.silverstripe.org www.silverstripe.com; media-src 'self' http://192.168.33.5/ *.vimeocdn.com player.vimeo.com www.youtube.com www.youtube-nocookie.com; script-src 'self' http://192.168.33.5/ code.jquery.com piwik.casa-laguna.net 'sha256-eMFqux9gzJQ++3HtmRPlbbsshtbkqI6ieVhYTWbaV1k='; style-src 'self' http://192.168.33.5/ 'unsafe-inline'; base-uri 'self' http://192.168.33.5/ https:; reflected-xss block; report-uri https://casalaguna.report-uri.com/r/d/csp/wizard;
*/
protected function checkCookie($request)
{
Expand Down Expand Up @@ -188,6 +192,22 @@ protected function setConfigPolicies($policy)
}
}

/**
* Add SiteConfig set domain policies
*
* @param ContentSecurityPolicyHeaderBuilder $policy
*/
protected function setSiteConfigPolicies($policy)
{
/** @var DataList|CSPDomain[] $domains */
$domains = SiteConfig::current_site_config()->CSPDomains();
$map = $this->allowedDirectivesMap;

foreach ($domains as $domain) {
$policy->addSourceExpression($map[$domain->Source], $domain->Domain);
}
}

protected function setReportPolicy()
{
$config = CSPBackend::config()->get('report_to');
Expand Down
39 changes: 39 additions & 0 deletions src/Extensions/SiteConfigExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Firesphere\CSPHeaders\Extensions;

use Firesphere\CSPHeaders\Models\CSPDomain;
use SilverStripe\Forms\FieldList;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
use SilverStripe\Forms\GridField\GridFieldExportButton;
use SilverStripe\Forms\GridField\GridFieldAddNewButton;

/**
* Class \Firesphere\CSPHeaders\Extensions\SiteConfigExtension
*
* @property SiteConfig|SiteConfigExtension $owner
* @method DataList|CSPDomain[] CSPDomains()
*/
class SiteConfigExtension extends DataExtension
{
private static $has_many = [
'CSPDomains' => CSPDomain::class
];

public function updateCMSFields(FieldList $fields)
{
$cspConfig = GridFieldConfig_RecordEditor::create();

$fields->addFieldToTab(
'Root.CSP',
GridField::create(
'CSPDomains',
_t(self::class . '.CSPDOMAINS', 'CSP Domains'),
CSPDomain::get(),
$cspConfig
)
);
}
}
145 changes: 145 additions & 0 deletions src/Models/CSPDomain.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

namespace Firesphere\CSPHeaders\Models;

use SilverStripe\ORM\DataObject;
use SilverStripe\Security\Permission;
use SilverStripe\Security\PermissionProvider;
use SilverStripe\SiteConfig\SiteConfig;

/**
* Class \Firesphere\CSPHeaders\Models\CSPDomain
*
* @property string $Domain
* @property string $Source
* @property int $SiteConfigID
* @method SiteConfig SiteConfig()
*/
class CSPDomain extends DataObject implements PermissionProvider
{
private static $singular_name = 'Content Security Policy Domain';
private static $plural_name = 'Content Security Policy Domains';

private static $table_name = 'CSPDomain';

private static $db = [
'Domain' => 'Varchar(255)',
'Source' => 'Enum("default,script,style,img,media,font,form,Frame")'
];

private static $has_one = [
'SiteConfig' => SiteConfig::class
];

private static $summary_fields = [
'Domain',
'Source'
];

public function getTitle()
{
return $this->Domain;
}

public function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->removeByName(['SiteConfigID']);

return $fields;
}

public function onBeforeWrite()
{
$this->SiteConfigID = SiteConfig::current_site_config()->ID;
parent::onBeforeWrite();
}

public function canCreate($member = null, $context = array())
{
$canCreate = parent::canCreate($member, $context);

if ($canCreate) {
return Permission::check('CREATE_CSPDomain', $member);
}

return $canCreate;
}

public function canEdit($member = null)
{
$canEdit = parent::canEdit($member, $context);

if ($canEdit) {
return Permission::check('EDIT_CSPDomain', $member);
}

return $canEdit;
}

public function canView($member = null)
{
$canView = parent::canView($member, $context);

if ($canView) {
return Permission::check('VIEW_CSPDomain', $member);
}

return $canView;
}

public function canDelete($member = null)
{
$canDelete = parent::canDelete($member, $context);

if ($canDelete) {
return Permission::check('DELETE_CSPDOMAIN', $member);
}

return $canDelete;
}

/**
* Return a map of permission codes to add to the dropdown shown in the Security section of the CMS.
* array(
* 'VIEW_SITE' => 'View the site',
* );
*/
public function providePermissions()
{
return [
'CREATE_CSPDOMAIN' => [
'name' => _t(self::class . '.PERMISSION_CREATE_DESCRIPTION', 'Create CSP Domains'),
'category' => _t('Permissions.CSPDOMAINS_CATEGORY', 'CSP Permissions'),
'help' => _t(
self::class . '.PERMISSION_CREATE_HELP',
'Permission required to create new CSP Domains.'
)
],
'EDIT_CSPDOMAIN' => [
'name' => _t(self::class . '.PERMISSION_EDIT_DESCRIPTION', 'Edit CSP Domains'),
'category' => _t('Permissions.CSPDOMAINS_CATEGORY', 'CSP Permissions'),
'help' => _t(
self::class . '.PERMISSION_EDIT_HELP',
'Permission required to edit existing CSP Domains.'
)
],
'DELETE_CSPDOMAIN' => [
'name' => _t(self::class . '.PERMISSION_DELETE_DESCRIPTION', 'Delete CSP Domains'),
'category' => _t('Permissions.CSPDOMAINS_CATEGORY', 'CSP Permissions'),
'help' => _t(
self::class . '.PERMISSION_DELETE_HELP',
'Permission required to delete existing CSP Domains.'
)
],
'VIEW_CSPDOMAIN' => [
'name' => _t(self::class . '.PERMISSION_VIEW_DESCRIPTION', 'View CSP Domains'),
'category' => _t('Permissions.CSPDOMAINS_CATEGORY', 'CSP Permissions'),
'help' => _t(
self::class . '.PERMISSION_VIEW_HELP',
'Permission required to view existing CSP Domains.'
)
],
];
}
}
5 changes: 1 addition & 4 deletions src/View/CSPRequirements.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ class CSPRequirements extends Requirements implements Flushable
public static function backend()
{
if (!self::$backend) {
self::$backend = parent::backend();
if (!self::$backend) {
self::$backend = CSPBackend::create();
}
self::$backend = CSPBackend::create();
}

return self::$backend;
Expand Down

0 comments on commit 139a266

Please sign in to comment.