Browse Source
Add feature policy header
Add feature policy header
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>pull/16613/head
No known key found for this signature in database
GPG Key ID: F941078878347C0C
14 changed files with 559 additions and 1 deletions
-
6lib/composer/composer/autoload_classmap.php
-
6lib/composer/composer/autoload_static.php
-
3lib/private/AppFramework/DependencyInjection/DIContainer.php
-
70lib/private/AppFramework/Middleware/Security/FeaturePolicyMiddleware.php
-
76lib/private/Security/FeaturePolicy/FeaturePolicy.php
-
76lib/private/Security/FeaturePolicy/FeaturePolicyManager.php
-
183lib/public/AppFramework/Http/EmptyFeaturePolicy.php
-
59lib/public/AppFramework/Http/FeaturePolicy.php
-
23lib/public/AppFramework/Http/Response.php
-
1lib/public/AppFramework/Http/TemplateResponse.php
-
52lib/public/Security/FeaturePolicy/AddFeaturePolicyEvent.php
-
1tests/lib/AppFramework/Controller/ControllerTest.php
-
1tests/lib/AppFramework/Http/DataResponseTest.php
-
3tests/lib/AppFramework/Http/ResponseTest.php
@ -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 = []; |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue