Skip to content

Commit

Permalink
Merge pull request #33 from comsolit/master
Browse files Browse the repository at this point in the history
fix #31 please make it possible to report and inforce policies
  • Loading branch information
Seldaek committed Nov 19, 2014
2 parents f9cdb99 + cccd809 commit ca17c83
Show file tree
Hide file tree
Showing 15 changed files with 515 additions and 220 deletions.
2 changes: 1 addition & 1 deletion ContentSecurityPolicy/ContentSecurityPolicyParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ function ($source) use ($keywords) {
$sourceList
);
}
}
}
125 changes: 125 additions & 0 deletions ContentSecurityPolicy/DirectiveSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

namespace Nelmio\SecurityBundle\ContentSecurityPolicy;

class DirectiveSet
{
private static $directiveNames = array(
'default-src',
'script-src',
'object-src',
'style-src',
'img-src',
'media-src',
'frame-src',
'font-src',
'connect-src',
'report-uri'
);

private $directiceValues = array();

public function getDirective($name)
{
$this->checkDirectiveName($name);

if (array_key_exists($name, $this->directiceValues)) {
return $this->directiceValues[$name];
}

return '';
}

public function setDirective($name, $value)
{
$this->checkDirectiveName($name);
$this->directiceValues[$name] = $value;
}

public function setDirectives(array $directives)
{
foreach ($directives as $name => $value) {
$this->setDirective($name, $value);
}
}

public function buildHeaderValue()
{
$policy = array();
foreach ($this->directiceValues as $name => $value) {
$policy[] = $name . ' ' . $value;
}

return join('; ', $policy);
}

public static function fromLegacyConfig(array $config)
{
$directiveSet = new self();
$parser = new ContentSecurityPolicyParser();

foreach (self::getLegacyNamesMap() as $old => $new) {
if (!array_key_exists($old, $config)) {
continue;
}

if ($old === 'report_uri') {
$directiveSet->setDirective($new, $config[$old]);
} else {
$directiveSet->setDirective($new, $parser->parseSourceList($config[$old]));
}
}

return $directiveSet;
}

public static function fromConfig(array $config, $kind)
{
$directiveSet = new self();
if (!array_key_exists($kind, $config)) {
return $directiveSet;
}

$parser = new ContentSecurityPolicyParser();
foreach (self::getNames() as $name) {
if (!array_key_exists($name, $config[$kind])) {
continue;
}

$directiveSet->setDirective($name, $parser->parseSourceList($config[$kind][$name]));
}

return $directiveSet;
}

public static function getNames()
{
return self::$directiveNames;
}

/**
* @deprecated
*/
public static function getLegacyNamesMap()
{
return array(
'default' => 'default-src',
'script' => 'script-src',
'object' => 'object-src',
'style' => 'style-src',
'img' => 'img-src',
'media' => 'media-src',
'frame' => 'frame-src',
'font' => 'font-src',
'connect' => 'connect-src',
'report_uri' => 'report-uri'
);
}

private function checkDirectiveName($name)
{
if (!in_array($name, self::$directiveNames)) {
throw new \InvalidArgumentException('Unknown CSP directive name: ' . $name);
}
}
}
130 changes: 85 additions & 45 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

use Symfony\Component\Config\Definition\Builder\TreeBuilder,
Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Builder\NodeBuilder;
use Nelmio\SecurityBundle\ContentSecurityPolicy\DirectiveSet;

class Configuration implements ConfigurationInterface
{
Expand Down Expand Up @@ -142,54 +144,92 @@ public function getConfigTreeBuilder()
->end()
->end()

->arrayNode('csp')
->children()
->arrayNode('default')
->prototype('scalar')->end()
->defaultValue(array())
->end()
->arrayNode('script')
->prototype('scalar')->end()
->defaultValue(array())
->end()
->arrayNode('object')
->prototype('scalar')->end()
->defaultValue(array())
->end()
->arrayNode('style')
->prototype('scalar')->end()
->defaultValue(array())
->end()
->arrayNode('img')
->prototype('scalar')->end()
->defaultValue(array())
->end()
->arrayNode('media')
->prototype('scalar')->end()
->defaultValue(array())
->end()
->arrayNode('frame')
->prototype('scalar')->end()
->defaultValue(array())
->end()
->arrayNode('font')
->prototype('scalar')->end()
->defaultValue(array())
->end()
->arrayNode('connect')
->prototype('scalar')->end()
->defaultValue(array())
->end()
->scalarNode('report_uri')->defaultValue('')->end()
->booleanNode('report_only')->defaultValue(false)->end()
// leaving this enabled can cause issues with older iOS (5.x) versions and possibly other early CSP implementations
->booleanNode('compat_headers')->defaultValue(true)->end()
->scalarNode('report_logger_service')->defaultValue('logger')->end()
->end()
->end()
->append($this->addCspNode())
->end()
->end();

return $treeBuilder;
}

private function addCspNode()
{
$builder = new TreeBuilder();
$node = $builder->root('csp');

$this
->addDirectives($node->children())
->scalarNode('report_uri')->defaultValue('')->end()
->booleanNode('report_only')->end()
// leaving this enabled can cause issues with older iOS (5.x) versions
// and possibly other early CSP implementations
->booleanNode('compat_headers')->defaultValue(true)->end()
->scalarNode('report_logger_service')->defaultValue('logger')->end()
->append($this->addReportOrEnforceNode('report'))
->append($this->addReportOrEnforceNode('enforce'))
->end()
->validate()
->ifTrue(function($v) {
return array_key_exists('report_only', $v)
&& (array_key_exists('report', $v) || array_key_exists('enforce', $v));
})
->thenInvalid('"report_only" and "(report|enforce)" can not be used together')
->end()
->validate()
->ifTrue(
function($v) {
return
!array_key_exists('report', $v)
&& !array_key_exists('enforce', $v)
&& !array_key_exists('report_only', $v);
}
)
->then(function($c) {
$c['report_only'] = false;
return $c;
})
->end();

return $node;
}

private function addReportOrEnforceNode($reportOrEnforce)
{
$builder = new TreeBuilder();
$node = $builder->root($reportOrEnforce);
$children = $node->children();
// Symfony should not normalize dashes to underlines, e.g. img-src to img_src
$node->normalizeKeys(false);

foreach (DirectiveSet::getNames() as $name) {
$children
->arrayNode($name)
->prototype('scalar')
->end();
}
return $children->end();
}

private function addDirectives(NodeBuilder $node)
{
$directives = array(
'default',
'script',
'object',
'style',
'img',
'media',
'frame',
'font',
'connect'
);

foreach ($directives as $directive) {
$node
->arrayNode($directive)
->prototype('scalar')
->end();
}

return $node;
}
}
16 changes: 3 additions & 13 deletions DependencyInjection/NelmioSecurityExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Nelmio\SecurityBundle\ContentSecurityPolicy\DirectiveSet;

class NelmioSecurityExtension extends Extension
{
Expand Down Expand Up @@ -56,20 +57,9 @@ public function load(array $configs, ContainerBuilder $container)
if (!empty($config['csp'])) {
$loader->load('csp.yml');

$parser = new ContentSecurityPolicyParser();
$container->getDefinition('nelmio_security.csp_listener')
->setArguments(array($config['csp']));

$container->setParameter('nelmio_security.csp.default', $parser->parseSourceList($config['csp']['default']));
$container->setParameter('nelmio_security.csp.script', $parser->parseSourceList($config['csp']['script']));
$container->setParameter('nelmio_security.csp.object', $parser->parseSourceList($config['csp']['object']));
$container->setParameter('nelmio_security.csp.style', $parser->parseSourceList($config['csp']['style']));
$container->setParameter('nelmio_security.csp.img', $parser->parseSourceList($config['csp']['img']));
$container->setParameter('nelmio_security.csp.media', $parser->parseSourceList($config['csp']['media']));
$container->setParameter('nelmio_security.csp.frame', $parser->parseSourceList($config['csp']['frame']));
$container->setParameter('nelmio_security.csp.font', $parser->parseSourceList($config['csp']['font']));
$container->setParameter('nelmio_security.csp.connect', $parser->parseSourceList($config['csp']['connect']));
$container->setParameter('nelmio_security.csp.report_uri', $config['csp']['report_uri']);
$container->setParameter('nelmio_security.csp.report_only', !!$config['csp']['report_only']);
$container->setParameter('nelmio_security.csp.compat_headers', !!$config['csp']['compat_headers']);
$container->getDefinition('nelmio_security.csp_reporter_controller')
->setArguments(array(new Reference($config['csp']['report_logger_service'])));
}
Expand Down
Loading

0 comments on commit ca17c83

Please sign in to comment.