This adds the events and the classes to modify the feature policy. It also adds a default restricted feature policy. Signed-off-by: Roeland Jago Douma <roeland@famdouma.nl>tags/v17.0.0beta1
@@ -37,6 +37,8 @@ return array( | |||
'OCP\\AppFramework\\Http\\DataResponse' => $baseDir . '/lib/public/AppFramework/Http/DataResponse.php', | |||
'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\\FeaturePolicy' => $baseDir . '/lib/public/AppFramework/Http/FeaturePolicy.php', | |||
'OCP\\AppFramework\\Http\\FileDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/FileDisplayResponse.php', | |||
'OCP\\AppFramework\\Http\\ICallbackResponse' => $baseDir . '/lib/public/AppFramework/Http/ICallbackResponse.php', | |||
'OCP\\AppFramework\\Http\\IOutput' => $baseDir . '/lib/public/AppFramework/Http/IOutput.php', | |||
@@ -380,6 +382,7 @@ return array( | |||
'OCP\\Search\\Provider' => $baseDir . '/lib/public/Search/Provider.php', | |||
'OCP\\Search\\Result' => $baseDir . '/lib/public/Search/Result.php', | |||
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => $baseDir . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', | |||
'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => $baseDir . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php', | |||
'OCP\\Security\\IContentSecurityPolicyManager' => $baseDir . '/lib/public/Security/IContentSecurityPolicyManager.php', | |||
'OCP\\Security\\ICredentialsManager' => $baseDir . '/lib/public/Security/ICredentialsManager.php', | |||
'OCP\\Security\\ICrypto' => $baseDir . '/lib/public/Security/ICrypto.php', | |||
@@ -470,6 +473,7 @@ 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\\RateLimitingMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php', | |||
'OC\\AppFramework\\Middleware\\Security\\ReloadExecutionMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php', | |||
@@ -1107,6 +1111,8 @@ return array( | |||
'OC\\Security\\CertificateManager' => $baseDir . '/lib/private/Security/CertificateManager.php', | |||
'OC\\Security\\CredentialsManager' => $baseDir . '/lib/private/Security/CredentialsManager.php', | |||
'OC\\Security\\Crypto' => $baseDir . '/lib/private/Security/Crypto.php', | |||
'OC\\Security\\FeaturePolicy\\FeaturePolicy' => $baseDir . '/lib/private/Security/FeaturePolicy/FeaturePolicy.php', | |||
'OC\\Security\\FeaturePolicy\\FeaturePolicyManager' => $baseDir . '/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php', | |||
'OC\\Security\\Hasher' => $baseDir . '/lib/private/Security/Hasher.php', | |||
'OC\\Security\\IdentityProof\\Key' => $baseDir . '/lib/private/Security/IdentityProof/Key.php', | |||
'OC\\Security\\IdentityProof\\Manager' => $baseDir . '/lib/private/Security/IdentityProof/Manager.php', |
@@ -71,6 +71,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OCP\\AppFramework\\Http\\DataResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataResponse.php', | |||
'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\\FeaturePolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/FeaturePolicy.php', | |||
'OCP\\AppFramework\\Http\\FileDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/FileDisplayResponse.php', | |||
'OCP\\AppFramework\\Http\\ICallbackResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ICallbackResponse.php', | |||
'OCP\\AppFramework\\Http\\IOutput' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/IOutput.php', | |||
@@ -414,6 +416,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OCP\\Search\\Provider' => __DIR__ . '/../../..' . '/lib/public/Search/Provider.php', | |||
'OCP\\Search\\Result' => __DIR__ . '/../../..' . '/lib/public/Search/Result.php', | |||
'OCP\\Security\\CSP\\AddContentSecurityPolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/CSP/AddContentSecurityPolicyEvent.php', | |||
'OCP\\Security\\FeaturePolicy\\AddFeaturePolicyEvent' => __DIR__ . '/../../..' . '/lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php', | |||
'OCP\\Security\\IContentSecurityPolicyManager' => __DIR__ . '/../../..' . '/lib/public/Security/IContentSecurityPolicyManager.php', | |||
'OCP\\Security\\ICredentialsManager' => __DIR__ . '/../../..' . '/lib/public/Security/ICredentialsManager.php', | |||
'OCP\\Security\\ICrypto' => __DIR__ . '/../../..' . '/lib/public/Security/ICrypto.php', | |||
@@ -504,6 +507,7 @@ 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\\RateLimitingMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/RateLimitingMiddleware.php', | |||
'OC\\AppFramework\\Middleware\\Security\\ReloadExecutionMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/ReloadExecutionMiddleware.php', | |||
@@ -1141,6 +1145,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OC\\Security\\CertificateManager' => __DIR__ . '/../../..' . '/lib/private/Security/CertificateManager.php', | |||
'OC\\Security\\CredentialsManager' => __DIR__ . '/../../..' . '/lib/private/Security/CredentialsManager.php', | |||
'OC\\Security\\Crypto' => __DIR__ . '/../../..' . '/lib/private/Security/Crypto.php', | |||
'OC\\Security\\FeaturePolicy\\FeaturePolicy' => __DIR__ . '/../../..' . '/lib/private/Security/FeaturePolicy/FeaturePolicy.php', | |||
'OC\\Security\\FeaturePolicy\\FeaturePolicyManager' => __DIR__ . '/../../..' . '/lib/private/Security/FeaturePolicy/FeaturePolicyManager.php', | |||
'OC\\Security\\Hasher' => __DIR__ . '/../../..' . '/lib/private/Security/Hasher.php', | |||
'OC\\Security\\IdentityProof\\Key' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Key.php', | |||
'OC\\Security\\IdentityProof\\Manager' => __DIR__ . '/../../..' . '/lib/private/Security/IdentityProof/Manager.php', |
@@ -231,6 +231,9 @@ class DIContainer extends SimpleContainer implements IAppContainer { | |||
$server->query(OC\Security\CSRF\CsrfTokenManager::class) | |||
) | |||
); | |||
$dispatcher->registerMiddleware( | |||
$server->query(OC\AppFramework\Middleware\Security\FeaturePolicyMiddleware::class) | |||
); | |||
$dispatcher->registerMiddleware( | |||
new OC\AppFramework\Middleware\Security\PasswordConfirmationMiddleware( | |||
$c->query(IControllerMethodReflector::class), |
@@ -0,0 +1,70 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, 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\AppFramework\Middleware\Security; | |||
use OC\Security\CSP\ContentSecurityPolicyManager; | |||
use OC\Security\CSP\ContentSecurityPolicyNonceManager; | |||
use OC\Security\CSRF\CsrfTokenManager; | |||
use OC\Security\FeaturePolicy\FeaturePolicy; | |||
use OC\Security\FeaturePolicy\FeaturePolicyManager; | |||
use OCP\AppFramework\Controller; | |||
use OCP\AppFramework\Http\ContentSecurityPolicy; | |||
use OCP\AppFramework\Http\EmptyContentSecurityPolicy; | |||
use OCP\AppFramework\Http\EmptyFeaturePolicy; | |||
use OCP\AppFramework\Http\Response; | |||
use OCP\AppFramework\Middleware; | |||
class FeaturePolicyMiddleware extends Middleware { | |||
/** @var FeaturePolicyManager */ | |||
private $policyManager; | |||
public function __construct(FeaturePolicyManager $policyManager) { | |||
$this->policyManager = $policyManager; | |||
} | |||
/** | |||
* Performs the default FeaturePolicy modifications that may be injected by other | |||
* applications | |||
* | |||
* @param Controller $controller | |||
* @param string $methodName | |||
* @param Response $response | |||
* @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; | |||
} | |||
$defaultPolicy = $this->policyManager->getDefaultPolicy(); | |||
$defaultPolicy = $this->policyManager->mergePolicies($defaultPolicy, $policy); | |||
$response->setFeaturePolicy($defaultPolicy); | |||
return $response; | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, 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\FeaturePolicy; | |||
class FeaturePolicy extends \OCP\AppFramework\Http\FeaturePolicy { | |||
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; | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, 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\FeaturePolicy; | |||
use OCP\AppFramework\Http\EmptyFeaturePolicy; | |||
use OCP\EventDispatcher\IEventDispatcher; | |||
use OCP\Security\FeaturePolicy\AddFeaturePolicyEvent; | |||
class FeaturePolicyManager { | |||
/** @var EmptyFeaturePolicy[] */ | |||
private $policies = []; | |||
/** @var IEventDispatcher */ | |||
private $dispatcher; | |||
public function __construct(IEventDispatcher $dispatcher) { | |||
$this->dispatcher = $dispatcher; | |||
} | |||
public function addDefaultPolicy(EmptyFeaturePolicy $policy): void { | |||
$this->policies[] = $policy; | |||
} | |||
public function getDefaultPolicy(): FeaturePolicy { | |||
$event = new AddFeaturePolicyEvent($this); | |||
$this->dispatcher->dispatch(AddFeaturePolicyEvent::class, $event); | |||
$defaultPolicy = new FeaturePolicy(); | |||
foreach ($this->policies as $policy) { | |||
$defaultPolicy = $this->mergePolicies($defaultPolicy, $policy); | |||
} | |||
return $defaultPolicy; | |||
} | |||
/** | |||
* Merges the first given policy with the second one | |||
* | |||
*/ | |||
public function mergePolicies(FeaturePolicy $defaultPolicy, | |||
EmptyFeaturePolicy $originalPolicy): FeaturePolicy { | |||
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; | |||
} | |||
} |
@@ -0,0 +1,183 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, 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 EmptyFeaturePolicy is a simple helper which allows applications | |||
* to modify the FeaturePolicy sent by Nextcloud. Per default the policy | |||
* is forbidding everything. | |||
* | |||
* As alternative with sane exemptions look at FeaturePolicy | |||
* | |||
* @see \OCP\AppFramework\Http\FeaturePolicy | |||
* @package OCP\AppFramework\Http | |||
* @since 17.0.0 | |||
*/ | |||
class EmptyFeaturePolicy { | |||
/** @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 17.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 17.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 17.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 17.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 17.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 17.0.0 | |||
*/ | |||
public function addAllowedPaymentDomain(string $domain): self { | |||
$this->paymentDomains[] = $domain; | |||
return $this; | |||
} | |||
/** | |||
* Get the generated Feature-Policy as a string | |||
* | |||
* @return string | |||
* @since 17.0.0 | |||
*/ | |||
public function buildPolicy(): string { | |||
$policy = ''; | |||
if (empty($this->autoplayDomains)) { | |||
$policy .= "autoplay 'none';"; | |||
} else { | |||
$policy .= 'autoplay ' . implode(' ', $this->autoplayDomains); | |||
$policy .= ';'; | |||
} | |||
if (empty($this->cameraDomains)) { | |||
$policy .= "camera 'none';"; | |||
} else { | |||
$policy .= 'camera ' . implode(' ', $this->cameraDomains); | |||
$policy .= ';'; | |||
} | |||
if (empty($this->fullscreenDomains)) { | |||
$policy .= "fullscreen 'none';"; | |||
} else { | |||
$policy .= 'fullscreen ' . implode(' ', $this->fullscreenDomains); | |||
$policy .= ';'; | |||
} | |||
if (empty($this->geolocationDomains)) { | |||
$policy .= "geolocation 'none';"; | |||
} else { | |||
$policy .= 'geolocation ' . implode(' ', $this->geolocationDomains); | |||
$policy .= ';'; | |||
} | |||
if (empty($this->microphoneDomains)) { | |||
$policy .= "microphone 'none';"; | |||
} else { | |||
$policy .= 'microphone ' . implode(' ', $this->microphoneDomains); | |||
$policy .= ';'; | |||
} | |||
if (empty($this->paymentDomains)) { | |||
$policy .= "payment 'none';"; | |||
} else { | |||
$policy .= 'payment ' . implode(' ', $this->paymentDomains); | |||
$policy .= ';'; | |||
} | |||
return rtrim($policy, ';'); | |||
} | |||
} |
@@ -0,0 +1,59 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, 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 FeaturePolicy is a simple helper which allows applications to | |||
* modify the Feature-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. | |||
* | |||
* @package OCP\AppFramework\Http | |||
* @since 17.0.0 | |||
*/ | |||
class FeaturePolicy extends EmptyFeaturePolicy { | |||
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 = []; | |||
} |
@@ -84,6 +84,9 @@ class Response { | |||
/** @var ContentSecurityPolicy|null Used Content-Security-Policy */ | |||
private $contentSecurityPolicy = null; | |||
/** @var FeaturePolicy */ | |||
private $featurePolicy; | |||
/** @var bool */ | |||
private $throttled = false; | |||
/** @var array */ | |||
@@ -96,6 +99,7 @@ class Response { | |||
*/ | |||
public function __construct() { | |||
$this->setContentSecurityPolicy(new EmptyContentSecurityPolicy()); | |||
$this->setFeaturePolicy(new EmptyFeaturePolicy()); | |||
} | |||
/** | |||
@@ -242,6 +246,7 @@ class Response { | |||
$this->setContentSecurityPolicy(new ContentSecurityPolicy()); | |||
} | |||
$this->headers['Content-Security-Policy'] = $this->contentSecurityPolicy->buildPolicy(); | |||
$this->headers['Feature-Policy'] = $this->featurePolicy->buildPolicy(); | |||
if($this->ETag) { | |||
$mergeWith['ETag'] = '"' . $this->ETag . '"'; | |||
@@ -295,6 +300,24 @@ class Response { | |||
} | |||
/** | |||
* @since 17.0.0 | |||
*/ | |||
public function getFeaturePolicy(): EmptyFeaturePolicy { | |||
return $this->featurePolicy; | |||
} | |||
/** | |||
* @since 17.0.0 | |||
*/ | |||
public function setFeaturePolicy(EmptyFeaturePolicy $featurePolicy): self { | |||
$this->featurePolicy = $featurePolicy; | |||
return $this; | |||
} | |||
/** | |||
* Get response status | |||
* @since 6.0.0 |
@@ -83,6 +83,7 @@ class TemplateResponse extends Response { | |||
$this->renderAs = $renderAs; | |||
$this->setContentSecurityPolicy(new ContentSecurityPolicy()); | |||
$this->setFeaturePolicy(new FeaturePolicy()); | |||
} | |||
@@ -0,0 +1,52 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/** | |||
* @copyright Copyright (c) 2019, 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\FeaturePolicy; | |||
use OC\Security\FeaturePolicy\FeaturePolicyManager; | |||
use OCP\AppFramework\Http\EmptyFeaturePolicy; | |||
use OCP\EventDispatcher\Event; | |||
/** | |||
* @since 17.0.0 | |||
*/ | |||
class AddFeaturePolicyEvent extends Event { | |||
/** @var FeaturePolicyManager */ | |||
private $policyManager; | |||
/** | |||
* @since 17.0.0 | |||
*/ | |||
public function __construct(FeaturePolicyManager $policyManager) { | |||
$this->policyManager = $policyManager; | |||
} | |||
/** | |||
* @since 17.0.0 | |||
*/ | |||
public function addPolicy(EmptyFeaturePolicy $policy) { | |||
$this->policyManager->addDefaultPolicy($policy); | |||
} | |||
} |
@@ -117,6 +117,7 @@ class ControllerTest extends \Test\TestCase { | |||
'Cache-Control' => 'no-cache, no-store, must-revalidate', | |||
'Content-Type' => 'application/json; charset=utf-8', | |||
'Content-Security-Policy' => "default-src 'none';base-uri 'none';manifest-src 'self'", | |||
'Feature-Policy' => "autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'", | |||
]; | |||
$response = $this->controller->customDataResponse(array('hi')); |
@@ -69,6 +69,7 @@ class DataResponseTest extends \Test\TestCase { | |||
$expectedHeaders = [ | |||
'Cache-Control' => 'no-cache, no-store, must-revalidate', | |||
'Content-Security-Policy' => "default-src 'none';base-uri 'none';manifest-src 'self'", | |||
'Feature-Policy' => "autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'", | |||
]; | |||
$expectedHeaders = array_merge($expectedHeaders, $headers); | |||
@@ -60,6 +60,7 @@ class ResponseTest extends \Test\TestCase { | |||
$this->childResponse->setHeaders($expected); | |||
$headers = $this->childResponse->getHeaders(); | |||
$expected['Content-Security-Policy'] = "default-src 'none';base-uri 'none';manifest-src 'self'"; | |||
$expected['Feature-Policy'] = "autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'"; | |||
$this->assertEquals($expected, $headers); | |||
} | |||
@@ -92,7 +93,7 @@ class ResponseTest extends \Test\TestCase { | |||
public function testAddHeaderValueNullDeletesIt(){ | |||
$this->childResponse->addHeader('hello', 'world'); | |||
$this->childResponse->addHeader('hello', null); | |||
$this->assertEquals(2, count($this->childResponse->getHeaders())); | |||
$this->assertEquals(3, count($this->childResponse->getHeaders())); | |||
} | |||