FeaturePolicy => PermissionPolicy

We already had the FeaturePolicy header. However this call got renamed
to PermisionPolicy. Here we move this over. The old mechanism stays
there it just won't get extended. So apps that use the FeaturePolicy
will not stop to work.

Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>
This commit is contained in:
Roeland Jago Douma 2020-11-01 11:57:07 +01:00
parent d602aa1825
commit f65ce546c0
No known key found for this signature in database
GPG Key ID: F941078878347C0C
15 changed files with 574 additions and 25 deletions

View File

@ -42,6 +42,7 @@ return array(
'OCP\\AppFramework\\Http\\DownloadResponse' => $baseDir . '/lib/public/AppFramework/Http/DownloadResponse.php',
'OCP\\AppFramework\\Http\\EmptyContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php',
'OCP\\AppFramework\\Http\\EmptyFeaturePolicy' => $baseDir . '/lib/public/AppFramework/Http/EmptyFeaturePolicy.php',
'OCP\\AppFramework\\Http\\EmptyPermissionPolicy' => $baseDir . '/lib/public/AppFramework/Http/EmptyPermissionPolicy.php',
'OCP\\AppFramework\\Http\\Events\\BeforeTemplateRenderedEvent' => $baseDir . '/lib/public/AppFramework/Http/Events/BeforeTemplateRenderedEvent.php',
'OCP\\AppFramework\\Http\\FeaturePolicy' => $baseDir . '/lib/public/AppFramework/Http/FeaturePolicy.php',
'OCP\\AppFramework\\Http\\FileDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/FileDisplayResponse.php',
@ -49,6 +50,7 @@ return array(
'OCP\\AppFramework\\Http\\IOutput' => $baseDir . '/lib/public/AppFramework/Http/IOutput.php',
'OCP\\AppFramework\\Http\\JSONResponse' => $baseDir . '/lib/public/AppFramework/Http/JSONResponse.php',
'OCP\\AppFramework\\Http\\NotFoundResponse' => $baseDir . '/lib/public/AppFramework/Http/NotFoundResponse.php',
'OCP\\AppFramework\\Http\\PermissionPolicy' => $baseDir . '/lib/public/AppFramework/Http/PermissionPolicy.php',
'OCP\\AppFramework\\Http\\RedirectResponse' => $baseDir . '/lib/public/AppFramework/Http/RedirectResponse.php',
'OCP\\AppFramework\\Http\\RedirectToDefaultAppResponse' => $baseDir . '/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php',
'OCP\\AppFramework\\Http\\Response' => $baseDir . '/lib/public/AppFramework/Http/Response.php',
@ -461,6 +463,7 @@ return array(
'OCP\\Security\\ICrypto' => $baseDir . '/lib/public/Security/ICrypto.php',
'OCP\\Security\\IHasher' => $baseDir . '/lib/public/Security/IHasher.php',
'OCP\\Security\\ISecureRandom' => $baseDir . '/lib/public/Security/ISecureRandom.php',
'OCP\\Security\\PermissionPolicy\\AddPermissionsPolicyEvent' => $baseDir . '/lib/public/Security/PermissionsPolicy/AddPermissionsPolicyEvent.php',
'OCP\\Session\\Exceptions\\SessionNotAvailableException' => $baseDir . '/lib/public/Session/Exceptions/SessionNotAvailableException.php',
'OCP\\Settings\\IIconSection' => $baseDir . '/lib/public/Settings/IIconSection.php',
'OCP\\Settings\\IManager' => $baseDir . '/lib/public/Settings/IManager.php',
@ -595,8 +598,8 @@ return array(
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\ReloadExecutionException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\SecurityException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\StrictCookieMissingException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php',
'OC\\AppFramework\\Middleware\\Security\\FeaturePolicyMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\PasswordConfirmationMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\PermissionPolicyMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/PermissionPolicyMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\RateLimitingMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\ReloadExecutionMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\SameSiteCookieMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php',
@ -1303,6 +1306,8 @@ return array(
'OC\\Security\\IdentityProof\\Manager' => $baseDir . '/lib/private/Security/IdentityProof/Manager.php',
'OC\\Security\\IdentityProof\\Signer' => $baseDir . '/lib/private/Security/IdentityProof/Signer.php',
'OC\\Security\\Normalizer\\IpAddress' => $baseDir . '/lib/private/Security/Normalizer/IpAddress.php',
'OC\\Security\\PermissionPolicy\\PermissionPolicy' => $baseDir . '/lib/private/Security/PermissionPolicy/PermissionPolicy.php',
'OC\\Security\\PermissionPolicy\\PermissionPolicyManager' => $baseDir . '/lib/private/Security/PermissionPolicy/PermissionPolicyManager.php',
'OC\\Security\\RateLimiting\\Backend\\IBackend' => $baseDir . '/lib/private/Security/RateLimiting/Backend/IBackend.php',
'OC\\Security\\RateLimiting\\Backend\\MemoryCache' => $baseDir . '/lib/private/Security/RateLimiting/Backend/MemoryCache.php',
'OC\\Security\\RateLimiting\\Exception\\RateLimitExceededException' => $baseDir . '/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php',

View File

@ -71,6 +71,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\AppFramework\\Http\\DownloadResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DownloadResponse.php',
'OCP\\AppFramework\\Http\\EmptyContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php',
'OCP\\AppFramework\\Http\\EmptyFeaturePolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/EmptyFeaturePolicy.php',
'OCP\\AppFramework\\Http\\EmptyPermissionPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/EmptyPermissionPolicy.php',
'OCP\\AppFramework\\Http\\Events\\BeforeTemplateRenderedEvent' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Events/BeforeTemplateRenderedEvent.php',
'OCP\\AppFramework\\Http\\FeaturePolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/FeaturePolicy.php',
'OCP\\AppFramework\\Http\\FileDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/FileDisplayResponse.php',
@ -78,6 +79,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\AppFramework\\Http\\IOutput' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/IOutput.php',
'OCP\\AppFramework\\Http\\JSONResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/JSONResponse.php',
'OCP\\AppFramework\\Http\\NotFoundResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/NotFoundResponse.php',
'OCP\\AppFramework\\Http\\PermissionPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/PermissionPolicy.php',
'OCP\\AppFramework\\Http\\RedirectResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/RedirectResponse.php',
'OCP\\AppFramework\\Http\\RedirectToDefaultAppResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/RedirectToDefaultAppResponse.php',
'OCP\\AppFramework\\Http\\Response' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/Response.php',
@ -490,6 +492,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\Security\\ICrypto' => __DIR__ . '/../../..' . '/lib/public/Security/ICrypto.php',
'OCP\\Security\\IHasher' => __DIR__ . '/../../..' . '/lib/public/Security/IHasher.php',
'OCP\\Security\\ISecureRandom' => __DIR__ . '/../../..' . '/lib/public/Security/ISecureRandom.php',
'OCP\\Security\\PermissionPolicy\\AddPermissionsPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/PermissionsPolicy/AddPermissionsPolicyEvent.php',
'OCP\\Session\\Exceptions\\SessionNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/Session/Exceptions/SessionNotAvailableException.php',
'OCP\\Settings\\IIconSection' => __DIR__ . '/../../..' . '/lib/public/Settings/IIconSection.php',
'OCP\\Settings\\IManager' => __DIR__ . '/../../..' . '/lib/public/Settings/IManager.php',
@ -624,8 +627,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\ReloadExecutionException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/ReloadExecutionException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\SecurityException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/SecurityException.php',
'OC\\AppFramework\\Middleware\\Security\\Exceptions\\StrictCookieMissingException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/StrictCookieMissingException.php',
'OC\\AppFramework\\Middleware\\Security\\FeaturePolicyMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\PasswordConfirmationMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/PasswordConfirmationMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\PermissionPolicyMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/PermissionPolicyMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\RateLimitingMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\ReloadExecutionMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php',
'OC\\AppFramework\\Middleware\\Security\\SameSiteCookieMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/SameSiteCookieMiddleware.php',
@ -1332,6 +1335,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Security\\IdentityProof\\Manager' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Manager.php',
'OC\\Security\\IdentityProof\\Signer' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Signer.php',
'OC\\Security\\Normalizer\\IpAddress' => __DIR__ . '/../../..' . '/lib/private/Security/Normalizer/IpAddress.php',
'OC\\Security\\PermissionPolicy\\PermissionPolicy' => __DIR__ . '/../../..' . '/lib/private/Security/PermissionPolicy/PermissionPolicy.php',
'OC\\Security\\PermissionPolicy\\PermissionPolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/PermissionPolicy/PermissionPolicyManager.php',
'OC\\Security\\RateLimiting\\Backend\\IBackend' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/IBackend.php',
'OC\\Security\\RateLimiting\\Backend\\MemoryCache' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Backend/MemoryCache.php',
'OC\\Security\\RateLimiting\\Exception\\RateLimitExceededException' => __DIR__ . '/../../..' . '/lib/private/Security/RateLimiting/Exception/RateLimitExceededException.php',

View File

@ -256,7 +256,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
)
);
$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(

View File

@ -28,18 +28,25 @@ 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;
public function __construct(FeaturePolicyManager $policyManager) {
$this->policyManager = $policyManager;
/** @var PermissionPolicyManager */
private $permissionPolicyManager;
public function __construct(FeaturePolicyManager $featurePolicyManager, PermissionPolicyManager $permissionPolicyManager) {
$this->featurePolicyManager = $featurePolicyManager;
$this->permissionPolicyManager = $permissionPolicyManager;
}
/**
@ -52,15 +59,20 @@ class FeaturePolicyMiddleware extends Middleware {
* @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 = !is_null($response->getFeaturePolicy()) ? $response->getFeaturePolicy() : new FeaturePolicy();
if (get_class($featurePolicy) !== 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 = !is_null($response->getPermissionPolicy()) ? $response->getPermissionPolicy() : new PermissionPolicy();
if (get_class($permissionPolicy) !== EmptyPermissionPolicy::class) {
$defaultPolicy = $this->permissionPolicyManager->getDefaultPolicy();
$defaultPolicy = $this->permissionPolicyManager->mergePolicies($defaultPolicy, $permissionPolicy);
$defaultPolicy = $this->permissionPolicyManager->mergeFeaturePolicy($defaultPolicy, $response->getFeaturePolicy());
$response->setPermissionPolicy($defaultPolicy);
}
return $response;
}

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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;
}
}

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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;
}
}

View File

@ -35,6 +35,7 @@ namespace OCP\AppFramework\Http;
*
* @see \OCP\AppFramework\Http\FeaturePolicy
* @since 17.0.0
* @depreacted 21.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy
*/
class EmptyFeaturePolicy {
@ -62,6 +63,7 @@ class EmptyFeaturePolicy {
* @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
* @return $this
* @since 17.0.0
* @depreacted 21.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy
*/
public function addAllowedAutoplayDomain(string $domain): self {
$this->autoplayDomains[] = $domain;
@ -74,6 +76,7 @@ class EmptyFeaturePolicy {
* @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
* @return $this
* @since 17.0.0
* @depreacted 21.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy
*/
public function addAllowedCameraDomain(string $domain): self {
$this->cameraDomains[] = $domain;
@ -86,6 +89,7 @@ class EmptyFeaturePolicy {
* @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
* @return $this
* @since 17.0.0
* @depreacted 21.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy
*/
public function addAllowedFullScreenDomain(string $domain): self {
$this->fullscreenDomains[] = $domain;
@ -98,6 +102,7 @@ class EmptyFeaturePolicy {
* @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
* @return $this
* @since 17.0.0
* @depreacted 21.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy
*/
public function addAllowedGeoLocationDomain(string $domain): self {
$this->geolocationDomains[] = $domain;
@ -110,6 +115,7 @@ class EmptyFeaturePolicy {
* @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
* @return $this
* @since 17.0.0
* @depreacted 21.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy
*/
public function addAllowedMicrophoneDomain(string $domain): self {
$this->microphoneDomains[] = $domain;
@ -122,6 +128,7 @@ class EmptyFeaturePolicy {
* @param string $domain Domain to whitelist. Any passed value needs to be properly sanitized.
* @return $this
* @since 17.0.0
* @depreacted 21.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy
*/
public function addAllowedPaymentDomain(string $domain): self {
$this->paymentDomains[] = $domain;
@ -133,6 +140,7 @@ class EmptyFeaturePolicy {
*
* @return string
* @since 17.0.0
* @depreacted 21.0.0 Use \OCP\AppFramework\Http\EmptyPermissionPolicy
*/
public function buildPolicy(): string {
$policy = '';

View File

@ -0,0 +1,173 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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;
}
}

View File

@ -36,6 +36,7 @@ namespace OCP\AppFramework\Http;
* 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 = [

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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 = [];
}

View File

@ -89,6 +89,9 @@ class Response {
/** @var FeaturePolicy */
private $featurePolicy;
/** @var PermissionPolicy */
private $permissionPolicy;
/** @var bool */
private $throttled = false;
/** @var array */
@ -240,7 +243,7 @@ class Response {
}
$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'] = 'none';
if ($this->ETag) {
@ -300,6 +303,7 @@ class Response {
/**
* @since 17.0.0
* @depreacted 21.0.0 Use getPermissionPolicy
*/
public function getFeaturePolicy(): EmptyFeaturePolicy {
if ($this->featurePolicy === null) {
@ -310,6 +314,7 @@ class Response {
/**
* @since 17.0.0
* @depreacted 21.0.0 Use setPermissionPolicy
*/
public function setFeaturePolicy(EmptyFeaturePolicy $featurePolicy): self {
$this->featurePolicy = $featurePolicy;
@ -317,6 +322,26 @@ class Response {
return $this;
}
/**
* @since 21.0.0
*/
public function getPermissionPolicy(): EmptyPermissionPolicy {
if ($this->permissionPolicy === null) {
$this->setPermissionPolicy(new EmptyPermissionPolicy());
}
return $this->permissionPolicy;
}
/**
* @since 17.0.0
* @depreacted 21.0.0 Use setPermissionPolicy
*/
public function setPermissionPolicy(EmptyPermissionPolicy $permissionPolicy): self {
$this->permissionPolicy = $permissionPolicy;
return $this;
}
/**

View File

@ -117,6 +117,7 @@ class TemplateResponse extends Response {
$this->setContentSecurityPolicy(new ContentSecurityPolicy());
$this->setFeaturePolicy(new FeaturePolicy());
$this->setPermissionPolicy(new PermissionPolicy());
}

View File

@ -36,6 +36,7 @@ use OCP\EventDispatcher\Event;
* Event that allows to register a feature policy header to a request.
*
* @since 17.0.0
* @depreacted 21.0.0 use AddPermissionPolicyEvent
*/
class AddFeaturePolicyEvent extends Event {
@ -44,6 +45,7 @@ class AddFeaturePolicyEvent extends Event {
/**
* @since 17.0.0
* @depreacted 21.0.0 use AddPermissionPolicyEvent
*/
public function __construct(FeaturePolicyManager $policyManager) {
parent::__construct();
@ -52,6 +54,7 @@ class AddFeaturePolicyEvent extends Event {
/**
* @since 17.0.0
* @depreacted 21.0.0 use AddPermissionPolicyEvent
*/
public function addPolicy(EmptyFeaturePolicy $policy) {
$this->policyManager->addDefaultPolicy($policy);

View File

@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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 21.0.0
*/
class AddPermissionsPolicyEvent extends Event {
/** @var PermissionPolicyManager */
private $policyManager;
/**
* @since 21.0.0
*/
public function __construct(PermissionPolicyManager $policyManager) {
parent::__construct();
$this->policyManager = $policyManager;
}
/**
* @since 21.0.0
*/
public function addPolicy(EmptyPermissionPolicy $policy) {
$this->policyManager->addDefaultPolicy($policy);
}
}

View File

@ -25,30 +25,37 @@ declare(strict_types=1);
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 {
class PermissionPolicyMiddlewareTest extends \Test\TestCase {
/** @var FeaturePolicyMiddleware|MockObject */
/** @var PermissionPolicyMiddleware|MockObject */
private $middleware;
/** @var Controller|MockObject */
private $controller;
/** @var FeaturePolicyManager|MockObject */
private $manager;
private $featurePolicyManager;
/** @var PermissionPolicyManager|MockObject */
private $permissionPolicyManager;
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
);
}
@ -62,25 +69,49 @@ class FeaturePolicyMiddlewareTest extends \Test\TestCase {
$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);
}