diff --git a/Classes/Factory/PolicyFactory.php b/Classes/Factory/PolicyFactory.php index d441eda..4d38707 100644 --- a/Classes/Factory/PolicyFactory.php +++ b/Classes/Factory/PolicyFactory.php @@ -15,27 +15,28 @@ class PolicyFactory { /** - * @param string[][] $defaultDirective - * @param string[][] $customDirective + * @param string[][] $defaultDirectives + * @param string[][] $customDirectives * @throws InvalidDirectiveException */ - public function create(Nonce $nonce, array $defaultDirective, array $customDirective): Policy + public function create(Nonce $nonce, array $defaultDirectives, array $customDirectives): Policy { - $directiveCollections = [$defaultDirective, $customDirective]; - $defaultDirective = array_shift($directiveCollections); - - array_walk($defaultDirective, function (array &$item, string $key) use ($directiveCollections) { - foreach ($directiveCollections as $collection) { - if (array_key_exists($key, $collection)) { - $item = array_unique([...$item, ...$collection[$key]]); - } + $resultDirectives = $defaultDirectives; + foreach ($customDirectives as $key => $customDirective) { + if (array_key_exists($key, $resultDirectives)) { + $resultDirectives[$key] = array_merge($resultDirectives[$key], $customDirective); + } else { + // Custom directive is not present in default, still needs to be added. + $resultDirectives[$key] = $customDirective; } - }); + + $resultDirectives[$key] = array_unique($resultDirectives[$key]); + } $policy = new Policy(); $policy->setNonce($nonce); - foreach ($defaultDirective as $directive => $values) { + foreach ($resultDirectives as $directive => $values) { $policy->addDirective($directive, $values); } diff --git a/Tests/Unit/Factory/PolicyFactoryTest.php b/Tests/Unit/Factory/PolicyFactoryTest.php index 5b0cbf6..90e98c2 100644 --- a/Tests/Unit/Factory/PolicyFactoryTest.php +++ b/Tests/Unit/Factory/PolicyFactoryTest.php @@ -4,6 +4,7 @@ namespace Unit\Factory; +use Flowpack\ContentSecurityPolicy\Exceptions\InvalidDirectiveException; use Flowpack\ContentSecurityPolicy\Factory\PolicyFactory; use Flowpack\ContentSecurityPolicy\Model\Directive; use Flowpack\ContentSecurityPolicy\Model\Nonce; @@ -15,34 +16,152 @@ #[CoversClass(PolicyFactory::class)] #[UsesClass(Policy::class)] #[UsesClass(Directive::class)] +#[UsesClass(InvalidDirectiveException::class)] class PolicyFactoryTest extends TestCase { - public function testCreateShouldReturnPolicy(): void + public function testCreateShouldReturnPolicyAndMergeCustomWithDefaultDirective(): void { $policyFactory = new PolicyFactory(); $nonceMock = $this->createMock(Nonce::class); $defaultDirective = [ 'base-uri' => [ + 'test.com', + ], + 'script-src' => [ + 'test.com', + ], + ]; + $customDirective = [ + 'script-src' => [ + 'custom.com', + ], + ]; + + $expected = [ + 'base-uri' => [ + 'test.com', + ], + 'script-src' => [ + 'test.com', + 'custom.com', + ], + ]; + + $result = $policyFactory->create($nonceMock, $defaultDirective, $customDirective); + + self::assertSame($expected, $result->getDirectives()); + } + + public function testCreateShouldReturnPolicyAndHandleSpecialDirectives(): void + { + $policyFactory = new PolicyFactory(); + $nonceMock = $this->createMock(Nonce::class); + + $defaultDirective = [ + 'script-src' => [ + '{nonce}', 'self', ], + ]; + $customDirective = []; + + $expected = [ 'script-src' => [ + "'nonce-'", + "'self'", + ], + ]; + + $result = $policyFactory->create($nonceMock, $defaultDirective, $customDirective); + + self::assertSame($expected, $result->getDirectives()); + } + + public function testCreateShouldFailWithInvalidDirective(): void + { + $policyFactory = new PolicyFactory(); + $nonceMock = $this->createMock(Nonce::class); + + $defaultDirective = [ + 'invalid' => [ 'self', ], + 'script-src' => [ + 'self', + ], + ]; + $customDirective = []; + + $this->expectException(InvalidDirectiveException::class); + $policyFactory->create($nonceMock, $defaultDirective, $customDirective); + } + + public function testCreateShouldReturnPolicyWithUniqueValues(): void + { + $policyFactory = new PolicyFactory(); + $nonceMock = $this->createMock(Nonce::class); + + $defaultDirective = [ + 'base-uri' => [ + 'test.com', + ], + 'script-src' => [ + 'test.com', + ], ]; $customDirective = [ + 'base-uri' => [ + 'test.com', + 'test.com', + ], 'script-src' => [ - '{nonce}', + 'test.com', ], ]; $expected = [ 'base-uri' => [ - "'self'", + 'test.com', ], 'script-src' => [ - "'self'", - "'nonce-'", + 'test.com', + ], + ]; + + $result = $policyFactory->create($nonceMock, $defaultDirective, $customDirective); + + self::assertSame($expected, $result->getDirectives()); + } + + public function testCreateShouldAddDirectiveWhichIsPresentInCustomButNotDefaultConfiguration(): void + { + $policyFactory = new PolicyFactory(); + $nonceMock = $this->createMock(Nonce::class); + + $defaultDirective = [ + 'base-uri' => [ + 'test.com', + ], + 'script-src' => [ + 'test.com', + ], + ]; + $customDirective = [ + 'worker-src' => [ + 'test.com', + ], + ]; + + $expected = [ + 'base-uri' => [ + "test.com", + ], + 'script-src' => [ + "test.com", + ], + 'worker-src' => [ + "test.com", ], ];