TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Security\ContentSecurityPolicy\Configuration;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class Behavior
|
||||
{
|
||||
/**
|
||||
* @param bool|null $useNonce Whether to use nonce values
|
||||
* @param bool|null $useHash Whether to use hash values
|
||||
*/
|
||||
public function __construct(
|
||||
/**
|
||||
* Controls nonce usage. null = system decides per context, true = always use nonce, false = never use nonce.
|
||||
*/
|
||||
public ?bool $useNonce = null,
|
||||
|
||||
/**
|
||||
* Whether to collect CSP hash values for assets. Always true by default because hashes enable
|
||||
* response caching (unlike nonces which are per-request). Even when nonces are used, hashes are
|
||||
* still collected so that cached responses include the correct CSP directives.
|
||||
*/
|
||||
public ?bool $useHash = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Creates a Behavior instance from a `csp.yaml` `behavior:` section.
|
||||
*
|
||||
* Example:
|
||||
* ```yaml
|
||||
* behavior:
|
||||
* useNonce: false
|
||||
* useHash: true
|
||||
* ```
|
||||
*/
|
||||
public static function fromArray(array $data): self
|
||||
{
|
||||
$useNonce = isset($data['useNonce']) ? (bool)$data['useNonce'] : null;
|
||||
$useHash = isset($data['useHash']) ? (bool)$data['useHash'] : null;
|
||||
return new self($useNonce, $useHash);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Security\ContentSecurityPolicy\Configuration;
|
||||
|
||||
use TYPO3\CMS\Core\Configuration\Features;
|
||||
use TYPO3\CMS\Core\Security\ContentSecurityPolicy\Disposition;
|
||||
use TYPO3\CMS\Core\Type\Map;
|
||||
|
||||
/**
|
||||
* Transforms a `csp.yaml` site configuration into a configuration model.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
readonly class CspConfigurationFactory
|
||||
{
|
||||
public function __construct(private Features $features) {}
|
||||
|
||||
/**
|
||||
* @return list<Disposition>
|
||||
*/
|
||||
public function resolveFallbackDispositions(): array
|
||||
{
|
||||
$dispositions = [];
|
||||
if ($this->features->isFeatureEnabled('security.frontend.enforceContentSecurityPolicy')) {
|
||||
$dispositions[] = Disposition::enforce;
|
||||
}
|
||||
if ($this->features->isFeatureEnabled('security.frontend.reportContentSecurityPolicy')) {
|
||||
$dispositions[] = Disposition::report;
|
||||
}
|
||||
return $dispositions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a Behavior instance from the top-level `behavior:` section of the `csp.yaml` configuration.
|
||||
*/
|
||||
public function buildBehavior(array $siteConfiguration): Behavior
|
||||
{
|
||||
$behaviorData = $siteConfiguration['behavior'] ?? [];
|
||||
return is_array($behaviorData) ? Behavior::fromArray($behaviorData) : new Behavior();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Map<Disposition, DispositionConfiguration>
|
||||
*/
|
||||
public function buildDispositionMap(array $siteConfiguration): Map
|
||||
{
|
||||
$activeAssignment = (bool)($siteConfiguration['active'] ?? true);
|
||||
// @todo future TYPO3 v14 should explicitly require `active: true` to get rid of the feature fallbacks
|
||||
if ($activeAssignment === false) {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
$dispositions = new Map();
|
||||
// assign site-specific dispositions
|
||||
foreach (Disposition::cases() as $disposition) {
|
||||
$assignment = $siteConfiguration[$disposition->value] ?? null;
|
||||
if ($this->isActive($assignment)) {
|
||||
$dispositions[$disposition] = $this->buildDispositionConfiguration(
|
||||
$assignment,
|
||||
$siteConfiguration
|
||||
);
|
||||
}
|
||||
}
|
||||
// in case there is no site-specific configuration, use the fallbacks as defined by top-level features
|
||||
if (count($dispositions) === 0) {
|
||||
foreach ($this->resolveFallbackDispositions() as $fallbackDisposition) {
|
||||
// skip fallbacks in case the disposition was disabled explicitly (e.g. `enforce: false`)
|
||||
if (($siteConfiguration[$fallbackDisposition->value] ?? null) !== false) {
|
||||
$dispositions[$fallbackDisposition] = $this->buildDispositionConfiguration(
|
||||
true,
|
||||
$siteConfiguration
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $dispositions;
|
||||
}
|
||||
|
||||
private function isActive(mixed $assignment): bool
|
||||
{
|
||||
return $assignment === true || is_array($assignment);
|
||||
}
|
||||
|
||||
private function buildDispositionConfiguration(
|
||||
true|array $assignment,
|
||||
array $siteConfiguration = []
|
||||
): DispositionConfiguration {
|
||||
if ($assignment === true) {
|
||||
// take from top-level configuration
|
||||
// (`includeResolutions` and `packages` are ignored on purpose)
|
||||
$inheritDefault = $siteConfiguration['inheritDefault'] ?? true;
|
||||
$includeResolutions = true;
|
||||
$reportingUrl = null;
|
||||
$mutations = $siteConfiguration['mutations'] ?? [];
|
||||
$packages = [];
|
||||
} else {
|
||||
$inheritDefault = $assignment['inheritDefault'] ?? true;
|
||||
$includeResolutions = $assignment['includeResolutions'] ?? true;
|
||||
$reportingUrl = $assignment['reportingUrl'] ?? null;
|
||||
$mutations = $assignment['mutations'] ?? [];
|
||||
$packages = $assignment['packages'] ?? [];
|
||||
}
|
||||
return new DispositionConfiguration(
|
||||
(bool)$inheritDefault,
|
||||
(bool)$includeResolutions,
|
||||
$reportingUrl,
|
||||
is_array($mutations) ? $mutations : [],
|
||||
is_array($packages) ? $packages : [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Security\ContentSecurityPolicy\Configuration;
|
||||
|
||||
/**
|
||||
* Represents a `csp.yaml` site configuration section for the disposition modes `enforce` and `report.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
readonly class DispositionConfiguration
|
||||
{
|
||||
public bool|string|null $reportingUrl;
|
||||
|
||||
public function __construct(
|
||||
public bool $inheritDefault,
|
||||
public bool $includeResolutions,
|
||||
mixed $reportingUrl,
|
||||
public array $mutations = [],
|
||||
/** @var array<string, bool> $packages */
|
||||
public array $packages = [],
|
||||
) {
|
||||
$this->reportingUrl = self::normalizeReportingUrl($reportingUrl);
|
||||
}
|
||||
|
||||
public static function normalizeReportingUrl(mixed $reportingUrl): bool|string|null
|
||||
{
|
||||
if ($reportingUrl === null || is_bool($reportingUrl)) {
|
||||
return $reportingUrl;
|
||||
}
|
||||
if (!is_scalar($reportingUrl)) {
|
||||
return null;
|
||||
}
|
||||
if ($reportingUrl === 0 || $reportingUrl === '0') {
|
||||
return false;
|
||||
}
|
||||
if ($reportingUrl === 1 || $reportingUrl === '1') {
|
||||
return true;
|
||||
}
|
||||
return (string)$reportingUrl;
|
||||
}
|
||||
|
||||
public function resolveEffectivePackages(string ...$packageNames): array
|
||||
{
|
||||
if ($this->packages === []) {
|
||||
return $packageNames;
|
||||
}
|
||||
|
||||
$effectivePackageNames = [];
|
||||
if (($this->packages['*'] ?? null) === true) {
|
||||
$effectivePackageNames = $packageNames;
|
||||
}
|
||||
|
||||
$dropPackageNames = array_filter($packageNames, fn(string $package): bool => ($this->packages[$package] ?? null) === false);
|
||||
$effectivePackageNames = array_diff($effectivePackageNames, $dropPackageNames);
|
||||
|
||||
$includePackageNames = array_filter($packageNames, fn(string $package): bool => ($this->packages[$package] ?? null) === true);
|
||||
$effectivePackageNames = [...$effectivePackageNames, ...array_diff($includePackageNames, $effectivePackageNames)];
|
||||
|
||||
return $effectivePackageNames;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user