diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 9a9740b7bccc4..a6772fd8bfc2c 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -268,7 +268,7 @@ public function __construct(string $appName, array $urlParams = [], ServerContai ) ); $dispatcher->registerMiddleware( - $server->query(OC\AppFramework\Middleware\Security\FeaturePolicyMiddleware::class) + $server->query(OC\AppFramework\Middleware\Security\PermissionPolicyMiddleware::class) ); $dispatcher->registerMiddleware( new OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware( diff --git a/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php b/lib/private/AppFramework/Middleware/Security/PermissionPolicyMiddleware.php similarity index 52% rename from lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php rename to lib/private/AppFramework/Middleware/Security/PermissionPolicyMiddleware.php index 418d4185184e5..6f5c897f5aeec 100644 --- a/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/PermissionPolicyMiddleware.php @@ -23,21 +23,30 @@ * along with this program. If not, see . * */ + namespace OC\AppFramework\Middleware\Security; use OC\Security\FeaturePolicy\FeaturePolicy; use OC\Security\FeaturePolicy\FeaturePolicyManager; +use OC\Security\PermissionPolicy\PermissionPolicy; +use OC\Security\PermissionPolicy\PermissionPolicyManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\EmptyFeaturePolicy; +use OCP\AppFramework\Http\EmptyPermissionPolicy; use OCP\AppFramework\Http\Response; use OCP\AppFramework\Middleware; -class FeaturePolicyMiddleware extends Middleware { +class PermissionPolicyMiddleware extends Middleware { + /** @var FeaturePolicyManager */ - private $policyManager; + private $featurePolicyManager; + + /** @var PermissionPolicyManager */ + private $permissionPolicyManager; - public function __construct(FeaturePolicyManager $policyManager) { - $this->policyManager = $policyManager; + public function __construct(FeaturePolicyManager $featurePolicyManager, PermissionPolicyManager $permissionPolicyManager) { + $this->featurePolicyManager = $featurePolicyManager; + $this->permissionPolicyManager = $permissionPolicyManager; } /** @@ -50,15 +59,20 @@ public function __construct(FeaturePolicyManager $policyManager) { * @return Response */ public function afterController($controller, $methodName, Response $response): Response { - $policy = !is_null($response->getFeaturePolicy()) ? $response->getFeaturePolicy() : new FeaturePolicy(); - - if (get_class($policy) === EmptyFeaturePolicy::class) { - return $response; + $featurePolicy = $response->getFeaturePolicy() ?? new FeaturePolicy(); + if ($featurePolicy::class !== EmptyFeaturePolicy::class) { + $defaultPolicy = $this->featurePolicyManager->getDefaultPolicy(); + $defaultPolicy = $this->featurePolicyManager->mergePolicies($defaultPolicy, $featurePolicy); + $response->setFeaturePolicy($defaultPolicy); } - $defaultPolicy = $this->policyManager->getDefaultPolicy(); - $defaultPolicy = $this->policyManager->mergePolicies($defaultPolicy, $policy); - $response->setFeaturePolicy($defaultPolicy); + $permissionPolicy = $response->getPermissionPolicy() ?? new PermissionPolicy(); + if ($permissionPolicy::class !== EmptyPermissionPolicy::class) { + $defaultPolicy = $this->permissionPolicyManager->getDefaultPolicy(); + $defaultPolicy = $this->permissionPolicyManager->mergePolicies($defaultPolicy, $permissionPolicy); + $defaultPolicy = $this->permissionPolicyManager->mergeFeaturePolicy($defaultPolicy, $response->getFeaturePolicy()); + $response->setPermissionPolicy($defaultPolicy); + } return $response; } diff --git a/lib/private/Security/PermissionPolicy/PermissionPolicy.php b/lib/private/Security/PermissionPolicy/PermissionPolicy.php new file mode 100644 index 0000000000000..87f4240f9ad63 --- /dev/null +++ b/lib/private/Security/PermissionPolicy/PermissionPolicy.php @@ -0,0 +1,76 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Security\PermissionPolicy; + +class PermissionPolicy extends \OCP\AppFramework\Http\PermissionPolicy { + public function getAutoplayDomains(): array { + return $this->autoplayDomains; + } + + public function setAutoplayDomains(array $autoplayDomains): void { + $this->autoplayDomains = $autoplayDomains; + } + + public function getCameraDomains(): array { + return $this->cameraDomains; + } + + public function setCameraDomains(array $cameraDomains): void { + $this->cameraDomains = $cameraDomains; + } + + public function getFullscreenDomains(): array { + return $this->fullscreenDomains; + } + + public function setFullscreenDomains(array $fullscreenDomains): void { + $this->fullscreenDomains = $fullscreenDomains; + } + + public function getGeolocationDomains(): array { + return $this->geolocationDomains; + } + + public function setGeolocationDomains(array $geolocationDomains): void { + $this->geolocationDomains = $geolocationDomains; + } + + public function getMicrophoneDomains(): array { + return $this->microphoneDomains; + } + + public function setMicrophoneDomains(array $microphoneDomains): void { + $this->microphoneDomains = $microphoneDomains; + } + + public function getPaymentDomains(): array { + return $this->paymentDomains; + } + + public function setPaymentDomains(array $paymentDomains): void { + $this->paymentDomains = $paymentDomains; + } +} diff --git a/lib/private/Security/PermissionPolicy/PermissionPolicyManager.php b/lib/private/Security/PermissionPolicy/PermissionPolicyManager.php new file mode 100644 index 0000000000000..1aedeb6b3e255 --- /dev/null +++ b/lib/private/Security/PermissionPolicy/PermissionPolicyManager.php @@ -0,0 +1,94 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + + +namespace OC\Security\PermissionPolicy; + +use OCP\AppFramework\Http\EmptyFeaturePolicy; +use OCP\AppFramework\Http\EmptyPermissionPolicy; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Security\PermissionPolicy\AddPermissionsPolicyEvent; + +class PermissionPolicyManager { + /** @var EmptyPermissionPolicy[] */ + private $policies = []; + + /** @var IEventDispatcher */ + private $dispatcher; + + public function __construct(IEventDispatcher $dispatcher) { + $this->dispatcher = $dispatcher; + } + + public function addDefaultPolicy(EmptyPermissionPolicy $policy): void { + $this->policies[] = $policy; + } + + public function getDefaultPolicy(): PermissionPolicy { + $event = new AddPermissionsPolicyEvent($this); + $this->dispatcher->dispatchTyped($event); + + $defaultPolicy = new PermissionPolicy(); + foreach ($this->policies as $policy) { + $defaultPolicy = $this->mergePolicies($defaultPolicy, $policy); + } + return $defaultPolicy; + } + + /** + * Merges the first given policy with the second one + * + */ + public function mergePolicies(PermissionPolicy $defaultPolicy, + EmptyPermissionPolicy $originalPolicy): PermissionPolicy { + foreach ((object)(array)$originalPolicy as $name => $value) { + $setter = 'set' . ucfirst($name); + if (\is_array($value)) { + $getter = 'get' . ucfirst($name); + $currentValues = \is_array($defaultPolicy->$getter()) ? $defaultPolicy->$getter() : []; + $defaultPolicy->$setter(\array_values(\array_unique(\array_merge($currentValues, $value)))); + } elseif (\is_bool($value)) { + $defaultPolicy->$setter($value); + } + } + + return $defaultPolicy; + } + + public function mergeFeaturePolicy(PermissionPolicy $defaultPolicy, EmptyFeaturePolicy $featurePolicy): PermissionPolicy { + foreach ((object)(array)$featurePolicy as $name => $value) { + $setter = 'set' . ucfirst($name); + if (\is_array($value)) { + $getter = 'get' . ucfirst($name); + $currentValues = \is_array($defaultPolicy->$getter()) ? $defaultPolicy->$getter() : []; + $defaultPolicy->$setter(\array_values(\array_unique(\array_merge($currentValues, $value)))); + } elseif (\is_bool($value)) { + $defaultPolicy->$setter($value); + } + } + + return $defaultPolicy; + } +} diff --git a/lib/public/AppFramework/Http/EmptyFeaturePolicy.php b/lib/public/AppFramework/Http/EmptyFeaturePolicy.php index b73eaf667e73f..d254954e4f8b8 100644 --- a/lib/public/AppFramework/Http/EmptyFeaturePolicy.php +++ b/lib/public/AppFramework/Http/EmptyFeaturePolicy.php @@ -34,6 +34,7 @@ * * @see \OCP\AppFramework\Http\FeaturePolicy * @since 17.0.0 + * @deprecated 28.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy */ class EmptyFeaturePolicy { /** @var string[] of allowed domains to autoplay media */ @@ -60,6 +61,7 @@ class EmptyFeaturePolicy { * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. * @return $this * @since 17.0.0 + * @deprecated 28.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy */ public function addAllowedAutoplayDomain(string $domain): self { $this->autoplayDomains[] = $domain; @@ -72,6 +74,7 @@ public function addAllowedAutoplayDomain(string $domain): self { * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. * @return $this * @since 17.0.0 + * @deprecated 28.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy */ public function addAllowedCameraDomain(string $domain): self { $this->cameraDomains[] = $domain; @@ -84,6 +87,7 @@ public function addAllowedCameraDomain(string $domain): self { * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. * @return $this * @since 17.0.0 + * @deprecated 28.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy */ public function addAllowedFullScreenDomain(string $domain): self { $this->fullscreenDomains[] = $domain; @@ -96,6 +100,7 @@ public function addAllowedFullScreenDomain(string $domain): self { * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. * @return $this * @since 17.0.0 + * @deprecated 28.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy */ public function addAllowedGeoLocationDomain(string $domain): self { $this->geolocationDomains[] = $domain; @@ -108,6 +113,7 @@ public function addAllowedGeoLocationDomain(string $domain): self { * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. * @return $this * @since 17.0.0 + * @deprecated 28.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy */ public function addAllowedMicrophoneDomain(string $domain): self { $this->microphoneDomains[] = $domain; @@ -120,6 +126,7 @@ public function addAllowedMicrophoneDomain(string $domain): self { * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. * @return $this * @since 17.0.0 + * @deprecated 28.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy */ public function addAllowedPaymentDomain(string $domain): self { $this->paymentDomains[] = $domain; @@ -131,6 +138,7 @@ public function addAllowedPaymentDomain(string $domain): self { * * @return string * @since 17.0.0 + * @deprecated 28.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy */ public function buildPolicy(): string { $policy = ''; diff --git a/lib/public/AppFramework/Http/EmptyPermissionPolicy.php b/lib/public/AppFramework/Http/EmptyPermissionPolicy.php new file mode 100644 index 0000000000000..1d94467e5a4f7 --- /dev/null +++ b/lib/public/AppFramework/Http/EmptyPermissionPolicy.php @@ -0,0 +1,173 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\AppFramework\Http; + +/** + * Class EmptyPermissionsPolicy is a simple helper which allows applications + * to modify the PermissionPolicy sent by Nextcloud. Per default the policy + * is forbidding everything. + * + * As alternative with sane exemptions look at PermissionPolicy + * + * @see \OCP\AppFramework\Http\FeaturePolicy + * @since 21.0.0 + */ +class EmptyPermissionPolicy { + + /** @var string[] of allowed domains to autoplay media */ + protected $autoplayDomains = null; + + /** @var string[] of allowed domains that can access the camera */ + protected $cameraDomains = null; + + /** @var string[] of allowed domains that can use fullscreen */ + protected $fullscreenDomains = null; + + /** @var string[] of allowed domains that can use the geolocation of the device */ + protected $geolocationDomains = null; + + /** @var string[] of allowed domains that can use the microphone */ + protected $microphoneDomains = null; + + /** @var string[] of allowed domains that can use the payment API */ + protected $paymentDomains = null; + + /** + * Allows to use autoplay from a specific domain. Use * to allow from all domains. + * + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + * @since 21.0.0 + */ + public function addAllowedAutoplayDomain(string $domain): self { + $this->autoplayDomains[] = $domain; + return $this; + } + + /** + * Allows to use the camera on a specific domain. Use * to allow from all domains + * + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + * @since 21.0.0 + */ + public function addAllowedCameraDomain(string $domain): self { + $this->cameraDomains[] = $domain; + return $this; + } + + /** + * Allows the full screen functionality to be used on a specific domain. Use * to allow from all domains + * + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + * @since 21.0.0 + */ + public function addAllowedFullScreenDomain(string $domain): self { + $this->fullscreenDomains[] = $domain; + return $this; + } + + /** + * Allows to use the geolocation on a specific domain. Use * to allow from all domains + * + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + * @since 21.0.0 + */ + public function addAllowedGeoLocationDomain(string $domain): self { + $this->geolocationDomains[] = $domain; + return $this; + } + + /** + * Allows to use the microphone on a specific domain. Use * to allow from all domains + * + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + * @since 21.0.0 + */ + public function addAllowedMicrophoneDomain(string $domain): self { + $this->microphoneDomains[] = $domain; + return $this; + } + + /** + * Allows to use the payment API on a specific domain. Use * to allow from all domains + * + * @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized. + * @return $this + * @since 21.0.0 + */ + public function addAllowedPaymentDomain(string $domain): self { + $this->paymentDomains[] = $domain; + return $this; + } + + /** + * Get the generated Feature-Policy as a string + * + * @return string + * @since 21.0.0 + */ + public function buildPolicy(): string { + $policy = ''; + + $policy .= 'autoplay=(' . implode(' ', $this->formatDomainList($this->autoplayDomains)) . ') '; + $policy .= 'camera=(' . implode(' ', $this->formatDomainList($this->cameraDomains)) . ') '; + $policy .= 'fullscreen=(' . implode(' ', $this->formatDomainList($this->fullscreenDomains)) . ') '; + $policy .= 'geolocation=(' . implode(' ', $this->formatDomainList($this->geolocationDomains)) . ') '; + $policy .= 'microphone=(' . implode(' ', $this->formatDomainList($this->microphoneDomains)) . ') '; + $policy .= 'payment=(' . implode(' ', $this->formatDomainList($this->paymentDomains)) . ') '; + + return rtrim($policy, ' '); + } + + private function formatDomainList(?array $domains): array { + if ($domains === null) { + return []; + } + + $result = []; + + foreach ($domains as $domain) { + if (!is_string($domain)) { + // Ignore wrong entries + continue; + } + + if ($domain === '\'self\'') { + $domain = 'self'; + } + + $result[] = $domain; + } + + $result = array_unique($result); + + return $result; + } +} diff --git a/lib/public/AppFramework/Http/FeaturePolicy.php b/lib/public/AppFramework/Http/FeaturePolicy.php index d193dda546be0..39e4847ef1c43 100644 --- a/lib/public/AppFramework/Http/FeaturePolicy.php +++ b/lib/public/AppFramework/Http/FeaturePolicy.php @@ -35,6 +35,7 @@ * should require no modification at all for most use-cases. * * @since 17.0.0 + * @depreacted 21.0.0 use \OCP\AppFramework\Http\PermissionPolicy */ class FeaturePolicy extends EmptyFeaturePolicy { protected $autoplayDomains = [ diff --git a/lib/public/AppFramework/Http/PermissionPolicy.php b/lib/public/AppFramework/Http/PermissionPolicy.php new file mode 100644 index 0000000000000..958080aef0df9 --- /dev/null +++ b/lib/public/AppFramework/Http/PermissionPolicy.php @@ -0,0 +1,59 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\AppFramework\Http; + +/** + * Class PermissionPolicy is a simple helper which allows applications to + * modify the Permission-Policy sent by Nextcloud. Per default only autoplay is allowed + * from the same domain and full screen as well from the same domain. + * + * Even if a value gets modified above defaults will still get appended. Please + * notice that Nextcloud ships already with sensible defaults and those policies + * should require no modification at all for most use-cases. + * + * @since 21.0.0 + */ +class PermissionPolicy extends EmptyPermissionPolicy { + protected $autoplayDomains = [ + 'self', + ]; + + /** @var string[] of allowed domains that can access the camera */ + protected $cameraDomains = []; + + protected $fullscreenDomains = [ + 'self', + ]; + + /** @var string[] of allowed domains that can use the geolocation of the device */ + protected $geolocationDomains = []; + + /** @var string[] of allowed domains that can use the microphone */ + protected $microphoneDomains = []; + + /** @var string[] of allowed domains that can use the payment API */ + protected $paymentDomains = []; +} diff --git a/lib/public/AppFramework/Http/Response.php b/lib/public/AppFramework/Http/Response.php index 152f8c4a3c5ea..593916883559e 100644 --- a/lib/public/AppFramework/Http/Response.php +++ b/lib/public/AppFramework/Http/Response.php @@ -85,6 +85,9 @@ class Response { /** @var FeaturePolicy */ private $featurePolicy; + /** @var PermissionPolicy */ + private $permissionPolicy; + /** @var bool */ private $throttled = false; /** @var array */ @@ -257,6 +260,7 @@ public function getHeaders() { $this->headers['Content-Security-Policy'] = $this->getContentSecurityPolicy()->buildPolicy(); $this->headers['Feature-Policy'] = $this->getFeaturePolicy()->buildPolicy(); + $this->headers['Permissions-Policy'] = $this->getPermissionPolicy()->buildPolicy(); $this->headers['X-Robots-Tag'] = 'noindex, nofollow'; if ($this->ETag) { @@ -316,6 +320,7 @@ public function getContentSecurityPolicy() { /** * @since 17.0.0 + * @depreacted 28.0.0 Use getPermissionPolicy */ public function getFeaturePolicy(): EmptyFeaturePolicy { if ($this->featurePolicy === null) { @@ -326,6 +331,7 @@ public function getFeaturePolicy(): EmptyFeaturePolicy { /** * @since 17.0.0 + * @depreacted 28.0.0 Use setPermissionPolicy */ public function setFeaturePolicy(EmptyFeaturePolicy $featurePolicy): self { $this->featurePolicy = $featurePolicy; @@ -333,6 +339,26 @@ public function setFeaturePolicy(EmptyFeaturePolicy $featurePolicy): self { return $this; } + /** + * @since 28.0.0 + */ + public function getPermissionPolicy(): EmptyPermissionPolicy { + if ($this->permissionPolicy === null) { + $this->setPermissionPolicy(new EmptyPermissionPolicy()); + } + return $this->permissionPolicy; + } + + /** + * @since 17.0.0 + * @depreacted 28.0.0 Use setPermissionPolicy + */ + public function setPermissionPolicy(EmptyPermissionPolicy $permissionPolicy): self { + $this->permissionPolicy = $permissionPolicy; + + return $this; + } + /** diff --git a/lib/public/AppFramework/Http/TemplateResponse.php b/lib/public/AppFramework/Http/TemplateResponse.php index 23843cd21d1cd..f4a14175ec001 100644 --- a/lib/public/AppFramework/Http/TemplateResponse.php +++ b/lib/public/AppFramework/Http/TemplateResponse.php @@ -111,6 +111,7 @@ public function __construct($appName, $templateName, array $params = [], $this->setContentSecurityPolicy(new ContentSecurityPolicy()); $this->setFeaturePolicy(new FeaturePolicy()); + $this->setPermissionPolicy(new PermissionPolicy()); } diff --git a/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php b/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php index 8d39b58a141c8..784322f65c35a 100644 --- a/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php +++ b/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php @@ -35,6 +35,7 @@ * Event that allows to register a feature policy header to a request. * * @since 17.0.0 + * @deprecated 28.0.0 use AddPermissionPolicyEvent */ class AddFeaturePolicyEvent extends Event { /** @var FeaturePolicyManager */ @@ -42,6 +43,7 @@ class AddFeaturePolicyEvent extends Event { /** * @since 17.0.0 + * @deprecated 28.0.0 use AddPermissionPolicyEvent */ public function __construct(FeaturePolicyManager $policyManager) { parent::__construct(); @@ -50,6 +52,7 @@ public function __construct(FeaturePolicyManager $policyManager) { /** * @since 17.0.0 + * @depreacted 21.0.0 use AddPermissionPolicyEvent */ public function addPolicy(EmptyFeaturePolicy $policy) { $this->policyManager->addDefaultPolicy($policy); diff --git a/lib/public/Security/PermissionsPolicy/AddPermissionsPolicyEvent.php b/lib/public/Security/PermissionsPolicy/AddPermissionsPolicyEvent.php new file mode 100644 index 0000000000000..0e810fd7aab95 --- /dev/null +++ b/lib/public/Security/PermissionsPolicy/AddPermissionsPolicyEvent.php @@ -0,0 +1,56 @@ + + * + * @author Roeland Jago Douma + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCP\Security\PermissionPolicy; + +use OC\Security\PermissionPolicy\PermissionPolicyManager; +use OCP\AppFramework\Http\EmptyPermissionPolicy; +use OCP\EventDispatcher\Event; + +/** + * Event that allows to register a feature policy header to a request. + * + * @since 28.0.0 + */ +class AddPermissionsPolicyEvent extends Event { + + /** @var PermissionPolicyManager */ + private $policyManager; + + /** + * @since 28.0.0 + */ + public function __construct(PermissionPolicyManager $policyManager) { + parent::__construct(); + $this->policyManager = $policyManager; + } + + /** + * @since 28.0.0 + */ + public function addPolicy(EmptyPermissionPolicy $policy) { + $this->policyManager->addDefaultPolicy($policy); + } +} diff --git a/tests/lib/AppFramework/Middleware/Security/FeaturePolicyMiddlewareTest.php b/tests/lib/AppFramework/Middleware/Security/PermissionPolicyMiddlewareTest.php similarity index 57% rename from tests/lib/AppFramework/Middleware/Security/FeaturePolicyMiddlewareTest.php rename to tests/lib/AppFramework/Middleware/Security/PermissionPolicyMiddlewareTest.php index 154b43f69a5ba..65cc0c31eb5bd 100644 --- a/tests/lib/AppFramework/Middleware/Security/FeaturePolicyMiddlewareTest.php +++ b/tests/lib/AppFramework/Middleware/Security/PermissionPolicyMiddlewareTest.php @@ -25,16 +25,20 @@ namespace Test\AppFramework\Middleware\Security; -use OC\AppFramework\Middleware\Security\FeaturePolicyMiddleware; +use OC\AppFramework\Middleware\Security\PermissionPolicyMiddleware; use OC\Security\FeaturePolicy\FeaturePolicy; use OC\Security\FeaturePolicy\FeaturePolicyManager; +use OC\Security\PermissionPolicy\PermissionPolicy; +use OC\Security\PermissionPolicy\PermissionPolicyManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\EmptyFeaturePolicy; +use OCP\AppFramework\Http\EmptyPermissionPolicy; use OCP\AppFramework\Http\Response; use PHPUnit\Framework\MockObject\MockObject; -class FeaturePolicyMiddlewareTest extends \Test\TestCase { - /** @var FeaturePolicyMiddleware|MockObject */ +class PermissionPolicyMiddlewareTest extends \Test\TestCase { + + /** @var PermissionPolicyMiddleware|MockObject */ private $middleware; /** @var Controller|MockObject */ private $controller; @@ -45,9 +49,11 @@ protected function setUp(): void { parent::setUp(); $this->controller = $this->createMock(Controller::class); - $this->manager = $this->createMock(FeaturePolicyManager::class); - $this->middleware = new FeaturePolicyMiddleware( - $this->manager + $this->featurePolicyManager = $this->createMock(FeaturePolicyManager::class); + $this->permissionPolicyManager = $this->createMock(PermissionPolicyManager::class); + $this->middleware = new PermissionPolicyMiddleware( + $this->featurePolicyManager, + $this->permissionPolicyManager ); } @@ -61,25 +67,49 @@ public function testAfterController() { $mergedPolicy->addAllowedGeoLocationDomain('mergedPolicy'); $response->method('getFeaturePolicy') ->willReturn($currentPolicy); - $this->manager->method('getDefaultPolicy') + $this->featurePolicyManager->method('getDefaultPolicy') ->willReturn($defaultPolicy); - $this->manager->method('mergePolicies') + $this->featurePolicyManager->method('mergePolicies') ->with($defaultPolicy, $currentPolicy) ->willReturn($mergedPolicy); $response->expects($this->once()) ->method('setFeaturePolicy') ->with($mergedPolicy); + $defaultPermissionPolicy = new PermissionPolicy(); + $this->permissionPolicyManager->method('getDefaultPolicy') + ->willReturn($defaultPermissionPolicy); + $currentPermissionPolicy = new PermissionPolicy(); + $response->method('getPermissionPolicy') + ->willReturn($currentPermissionPolicy); + $mergedPermissionPolicy = new PermissionPolicy(); + $this->permissionPolicyManager->method('mergePolicies') + ->with($defaultPermissionPolicy, $currentPermissionPolicy) + ->willReturn($mergedPermissionPolicy); + $mergedPermissionPolicyWithFeaturePolicy = new PermissionPolicy(); + $this->permissionPolicyManager->method('mergeFeaturePolicy') + ->with($mergedPermissionPolicy, $currentPolicy) + ->willReturn($mergedPermissionPolicyWithFeaturePolicy); + + $response->expects($this->once()) + ->method('setPermissionPolicy') + ->with($mergedPermissionPolicy); + $this->middleware->afterController($this->controller, 'test', $response); } - public function testAfterControllerEmptyCSP() { + public function testAfterControllerEmpty() { $response = $this->createMock(Response::class); $emptyPolicy = new EmptyFeaturePolicy(); + $emptyPermissionPolicy = new EmptyPermissionPolicy(); $response->method('getFeaturePolicy') ->willReturn($emptyPolicy); + $response->method('getPermissionPolicy') + ->willReturn($emptyPermissionPolicy); $response->expects($this->never()) ->method('setFeaturePolicy'); + $response->expects($this->never()) + ->method('setPermissionPolicy'); $this->middleware->afterController($this->controller, 'test', $response); }