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