From bc34291aa61b37dde51b74888163686ac3e1b7a8 Mon Sep 17 00:00:00 2001 From: Thomas Lehmann Date: Wed, 18 Dec 2024 19:40:23 +0100 Subject: [PATCH 1/3] [WIP] refactor(Application): config CSP + scripts via events -- adapt tests --- lib/AppInfo/Application.php | 63 +++++-------------------------------- lib/Listener/AddCsp.php | 49 +++++++++++++++++++++++++++++ lib/Listener/LoadScript.php | 40 +++++++++++++++++++++++ 3 files changed, 96 insertions(+), 56 deletions(-) create mode 100644 lib/Listener/AddCsp.php create mode 100644 lib/Listener/LoadScript.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index ac0d46b..02ddcce 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -4,15 +4,14 @@ namespace OCA\NCGoogleAnalytics\AppInfo; -use OC\Security\CSP\ContentSecurityPolicyManager; -use OC\Security\CSP\ContentSecurityPolicyNonceManager; +use OCA\NCGoogleAnalytics\Listener\LoadScript; +use OCA\NCGoogleAnalytics\Listener\AddCsp; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; -use OCP\AppFramework\Http\ContentSecurityPolicy; -use OCP\IURLGenerator; -use OCP\Util; +use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; +use OCP\Security\CSP\AddContentSecurityPolicyEvent; class Application extends App implements IBootstrap { @@ -23,60 +22,12 @@ public function __construct() parent::__construct(self::APP_ID); } - public function register(IRegistrationContext $context): void - { + public function register(IRegistrationContext $context): void { + $context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadScript::class); + $context->registerEventListener(AddContentSecurityPolicyEvent::class, AddCsp::class); } public function boot(IBootContext $context): void { - $context->injectFn([$this, 'addTrackingScript']); - $context->injectFn([$this, 'addContentSecurityPolicy']); - } - - public function addTrackingScript(IURLGenerator $urlGenerator, ContentSecurityPolicyNonceManager $nonceManager): void - { - Util::addHeader( - 'script', - [ - 'src' => $urlGenerator->linkToRoute('googleanalytics.JavaScript.tracking'), - 'nonce' => $nonceManager->getNonce(), - ], - '' - ); - } - - /** - * Add the Content Security Policy for the Google Analytics tracking according - * to https://developers.google.com/tag-platform/security/guides/csp - * - * @param ContentSecurityPolicyManager $policyManager - * @return void - */ - public function addContentSecurityPolicy(ContentSecurityPolicyManager $policyManager): void - { - $policy = new ContentSecurityPolicy(); - - $policy->addAllowedScriptDomain("*.googletagmanager.com"); - $policy->addAllowedImageDomain("*.googletagmanager.com"); - $policy->addAllowedConnectDomain("*.googletagmanager.com"); - - $policy->addAllowedScriptDomain("tagmanager.google.com"); - $policy->addAllowedImageDomain("tagmanager.google.com"); - $policy->addAllowedConnectDomain("tagmanager.google.com"); - - $policy->addAllowedScriptDomain("*.google-analytics.com"); - $policy->addAllowedImageDomain("*.google-analytics.com"); - $policy->addAllowedConnectDomain("*.google-analytics.com"); - - // additional SCP for GTM preview mode - $policy->addAllowedStyleDomain("https://www.googletagmanager.com"); - $policy->addAllowedStyleDomain("https://fonts.googleapis.com"); - - $policy->addAllowedFontDomain("https://fonts.gstatic.com"); - - $policy->addAllowedImageDomain("https://fonts.gstatic.com"); - $policy->addAllowedImageDomain("https://fonts.googleapis.com"); - - $policyManager->addDefaultPolicy($policy); } } diff --git a/lib/Listener/AddCsp.php b/lib/Listener/AddCsp.php new file mode 100644 index 0000000..67fc460 --- /dev/null +++ b/lib/Listener/AddCsp.php @@ -0,0 +1,49 @@ +addAllowedScriptDomain("*.googletagmanager.com"); + $policy->addAllowedImageDomain("*.googletagmanager.com"); + $policy->addAllowedConnectDomain("*.googletagmanager.com"); + + $policy->addAllowedScriptDomain("tagmanager.google.com"); + $policy->addAllowedImageDomain("tagmanager.google.com"); + $policy->addAllowedConnectDomain("tagmanager.google.com"); + + $policy->addAllowedScriptDomain("*.google-analytics.com"); + $policy->addAllowedImageDomain("*.google-analytics.com"); + $policy->addAllowedConnectDomain("*.google-analytics.com"); + + // additional SCP for GTM preview mode + $policy->addAllowedStyleDomain("https://www.googletagmanager.com"); + $policy->addAllowedStyleDomain("https://fonts.googleapis.com"); + + $policy->addAllowedFontDomain("https://fonts.gstatic.com"); + + $policy->addAllowedImageDomain("https://fonts.gstatic.com"); + $policy->addAllowedImageDomain("https://fonts.googleapis.com"); + + $event->addPolicy($policy); + } +} diff --git a/lib/Listener/LoadScript.php b/lib/Listener/LoadScript.php new file mode 100644 index 0000000..a9c9b46 --- /dev/null +++ b/lib/Listener/LoadScript.php @@ -0,0 +1,40 @@ + $this->urlGenerator->linkToRoute('googleanalytics.JavaScript.tracking'), + 'nonce' => $this->nonceManager->getNonce(), + ], + '' + ); + } +} From 4b9f10f0c0a7868c93ae78aed46ca73bf6191c65 Mon Sep 17 00:00:00 2001 From: Thomas Lehmann Date: Wed, 18 Dec 2024 13:34:11 +0100 Subject: [PATCH 2/3] [WIP] feat: add ConsentDetection service stub -- add testst Note: this could also be implenented in nc_ionos_processes and be provided as a service to the dependency container. --- lib/Listener/AddCsp.php | 9 +++++++++ lib/Listener/LoadScript.php | 7 +++++++ lib/Service/ConsentDetection.php | 26 ++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 lib/Service/ConsentDetection.php diff --git a/lib/Listener/AddCsp.php b/lib/Listener/AddCsp.php index 67fc460..ce0c361 100644 --- a/lib/Listener/AddCsp.php +++ b/lib/Listener/AddCsp.php @@ -6,13 +6,18 @@ namespace OCA\NCGoogleAnalytics\Listener; +use OCA\NCGoogleAnalytics\Service\ConsentDetection; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\Security\CSP\AddContentSecurityPolicyEvent; +/** + * Configure Google sites for content security policy (CSP). + */ class AddCsp implements IEventListener { public function __construct( + private ConsentDetection $consentDetection ) { } @@ -21,6 +26,10 @@ public function handle(Event $event): void { return; } + if (!$this->consentDetection->isConsentGiven()) { + return; + } + $policy = new ContentSecurityPolicy(); $policy->addAllowedScriptDomain("*.googletagmanager.com"); diff --git a/lib/Listener/LoadScript.php b/lib/Listener/LoadScript.php index a9c9b46..9d244e8 100644 --- a/lib/Listener/LoadScript.php +++ b/lib/Listener/LoadScript.php @@ -7,6 +7,8 @@ namespace OCA\NCGoogleAnalytics\Listener; +use OC\Security\CSP\ContentSecurityPolicyNonceManager; +use OCA\NCGoogleAnalytics\Service\ConsentDetection; use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; @@ -20,6 +22,7 @@ class LoadScript implements IEventListener { public function __construct( private IURLGenerator $urlGenerator, private ContentSecurityPolicyNonceManager $nonceManager, + private ConsentDetection $consentDetection, ) { } @@ -28,6 +31,10 @@ public function handle(Event $event): void { return; } + if (!$this->consentDetection->isConsentGiven()) { + return; + } + Util::addHeader( 'script', [ diff --git a/lib/Service/ConsentDetection.php b/lib/Service/ConsentDetection.php new file mode 100644 index 0000000..bf0682a --- /dev/null +++ b/lib/Service/ConsentDetection.php @@ -0,0 +1,26 @@ + Date: Wed, 18 Dec 2024 19:54:08 +0100 Subject: [PATCH 3/3] [WIP] feat: implement ConsentDetection to inspect cookie -- tests --- lib/Service/ConsentDetection.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/Service/ConsentDetection.php b/lib/Service/ConsentDetection.php index bf0682a..22ff669 100644 --- a/lib/Service/ConsentDetection.php +++ b/lib/Service/ConsentDetection.php @@ -15,12 +15,17 @@ * The implementation is IONOS specific. */ class ConsentDetection { + const CONSENT_COOKIE_NAME = "PRIVACY_CONSENT"; + public function __construct( private IRequest $request, ) { } public function isConsentGiven(): bool { - return false; + $codedJsonStr = $this->request->getCookie(self::CONSENT_COOKIE_NAME); + $jsonStr = base64_decode($codedJsonStr); + $settings = json_decode($jsonStr); + return $settings->statistics ?? false; } }