Skip to content

Commit 1fc6f2d

Browse files
authored
Merge pull request #1 from Flowpack/fix-path
TASK: fix paths
2 parents b9d89d3 + b126176 commit 1fc6f2d

File tree

13 files changed

+921
-0
lines changed

13 files changed

+921
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
composer.lock
3+
vendor
4+
Packages
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flowpack\ContentSecurityPolicy\Command;
6+
7+
use Flowpack\ContentSecurityPolicy\Exceptions\InvalidDirectiveException;
8+
use Flowpack\ContentSecurityPolicy\Factory\PolicyFactory;
9+
use Flowpack\ContentSecurityPolicy\Model\Nonce;
10+
use Flowpack\ContentSecurityPolicy\Model\Policy;
11+
use Neos\Flow\Annotations as Flow;
12+
use Neos\Flow\Cli\CommandController;
13+
use Neos\Flow\Cli\Exception\StopCommandException;
14+
15+
class CspConfigCommandController extends CommandController
16+
{
17+
/**
18+
* @Flow\InjectConfiguration(path="enabled")
19+
*/
20+
protected bool $enabled;
21+
22+
/**
23+
* @Flow\Inject
24+
*/
25+
protected Nonce $nonce;
26+
27+
/**
28+
* @Flow\InjectConfiguration(path="content-security-policy")
29+
* @var mixed[]
30+
*/
31+
protected array $configuration;
32+
33+
/**
34+
* Show CSP config
35+
*
36+
* Shows the generated config for the CSP.
37+
* @throws StopCommandException
38+
*/
39+
public function showCommand(): void
40+
{
41+
try {
42+
$backendPolicy = PolicyFactory::create(
43+
$this->nonce,
44+
$this->configuration['backend'],
45+
$this->configuration['custom-backend']
46+
);
47+
48+
$frontendPolicy = PolicyFactory::create(
49+
$this->nonce,
50+
$this->configuration['default'],
51+
$this->configuration['custom']
52+
);
53+
} catch (InvalidDirectiveException $exception) {
54+
$this->outputLine(
55+
sprintf('<error>Invalid directive "%s" in configuration file.</error>', $exception->getMessage())
56+
);
57+
58+
$this->quit(1);
59+
}
60+
61+
$this->outputLine('<b>Backend CSP</b>');
62+
$this->printPolicy($backendPolicy);
63+
64+
$this->outputLine("\n<b>Frontend CSP</b>");
65+
$this->printPolicy($frontendPolicy);
66+
$this->quit();
67+
}
68+
69+
private function printPolicy(Policy $policy): void
70+
{
71+
$directives = $policy->getDirectives();
72+
$keys = array_keys($directives);
73+
74+
$items = array_map(function ($values, $directive) {
75+
$value = implode(', ', $values);
76+
77+
return "$directive: $value";
78+
}, $directives, $keys);
79+
foreach ($items as $item) {
80+
$this->outputLine($item);
81+
}
82+
}
83+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flowpack\ContentSecurityPolicy\Exceptions;
6+
7+
use Neos\Flow\Exception;
8+
9+
class InvalidDirectiveException extends Exception
10+
{
11+
public function __construct(string $invalidDirective)
12+
{
13+
parent::__construct(
14+
"Invalid directive '{$invalidDirective}' provided. Please check your settings.",
15+
);
16+
}
17+
}

Classes/Factory/PolicyFactory.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flowpack\ContentSecurityPolicy\Factory;
6+
7+
use Flowpack\ContentSecurityPolicy\Exceptions\InvalidDirectiveException;
8+
use Flowpack\ContentSecurityPolicy\Model\Nonce;
9+
use Flowpack\ContentSecurityPolicy\Model\Policy;
10+
11+
class PolicyFactory
12+
{
13+
/**
14+
* @throws InvalidDirectiveException
15+
*/
16+
public static function create(Nonce $nonce, array $defaultDirective, array $customDirective): Policy
17+
{
18+
$directiveCollections = [$defaultDirective, $customDirective];
19+
$defaultDirective = array_shift($directiveCollections);
20+
21+
array_walk($defaultDirective, function (array &$item, string $key) use ($directiveCollections) {
22+
foreach ($directiveCollections as $collection) {
23+
if (array_key_exists($key, $collection)) {
24+
$item = array_unique([...$item, ...$collection[$key]]);
25+
}
26+
}
27+
});
28+
29+
$policy = new Policy();
30+
$policy->setNonce($nonce);
31+
32+
foreach ($defaultDirective as $directive => $values) {
33+
$policy->addDirective($directive, $values);
34+
}
35+
36+
return $policy;
37+
}
38+
}

Classes/Helpers/TagHelper.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flowpack\ContentSecurityPolicy\Helpers;
6+
7+
class TagHelper
8+
{
9+
public const NONCE = 'nonce';
10+
11+
public static function tagHasAttribute(
12+
string $tag,
13+
string $name,
14+
string $value = null
15+
): bool {
16+
if (! $value) {
17+
return ! ! preg_match(
18+
self::buildMatchAttributeNameReqex($name),
19+
$tag
20+
);
21+
} else {
22+
return ! ! preg_match(
23+
self::buildMatchAttributeNameWithSpecificValueReqex(
24+
$name,
25+
$value
26+
),
27+
$tag
28+
);
29+
}
30+
}
31+
32+
public static function tagChangeAttributeValue(
33+
string $tag,
34+
string $name,
35+
string $newValue
36+
): string {
37+
return preg_replace_callback(
38+
self::buildMatchAttributeNameWithAnyValueReqex($name),
39+
function ($hits) use ($newValue) {
40+
return $hits["pre"].
41+
$hits["name"].
42+
$hits["glue"].
43+
$newValue.
44+
$hits["post"];
45+
},
46+
$tag
47+
);
48+
}
49+
50+
public static function tagAddAttribute(
51+
string $tag,
52+
string $name,
53+
string $value = null
54+
): string {
55+
return preg_replace_callback(
56+
self::buildMatchEndOfOpeningTagReqex(),
57+
function ($hits) use ($name, $value) {
58+
if ($value) {
59+
return $hits["start"].
60+
' '.
61+
$name.
62+
'="'.
63+
$value.
64+
'"'.
65+
$hits["end"];
66+
} else {
67+
return $hits["start"].' '.$name.$hits["end"];
68+
}
69+
},
70+
$tag
71+
);
72+
}
73+
74+
private static function escapeReqexCharsInString(string $value): string
75+
{
76+
// for some reason "/" is not escaped
77+
return str_replace("/", "\/", preg_quote($value));
78+
}
79+
80+
private static function buildMatchEndOfOpeningTagReqex(): string
81+
{
82+
return '/(?<start><[a-z]+.*?)(?<end>>|\/>)/';
83+
}
84+
85+
private static function buildMatchAttributeNameWithAnyValueReqex(
86+
string $name
87+
): string {
88+
$nameQuoted = self::escapeReqexCharsInString($name);
89+
90+
return '/(?<pre><.*? )(?<name>'.
91+
$nameQuoted.
92+
')(?<glue>=")(?<value>.*?)(?<post>".*?>)/';
93+
}
94+
95+
private static function buildMatchAttributeNameReqex(string $name): string
96+
{
97+
$nameQuoted = self::escapeReqexCharsInString($name);
98+
99+
return '/(?<pre><.*? )(?<name>'.$nameQuoted.')(?<post>.*?>)/';
100+
}
101+
102+
private static function buildMatchAttributeNameWithSpecificValueReqex(
103+
string $name,
104+
string $value
105+
): string {
106+
$nameQuoted = self::escapeReqexCharsInString($name);
107+
$valueQuoted = self::escapeReqexCharsInString($value);
108+
109+
return '/(?<pre><.*? )(?<name>'.
110+
$nameQuoted.
111+
')(?<glue>=")(?<value>'.
112+
$valueQuoted.
113+
')(?<post>".*?>)/';
114+
}
115+
}

0 commit comments

Comments
 (0)