diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index fcbd45f..e232184 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -9,17 +9,15 @@ namespace OCA\JSLoader\AppInfo; -use OC\Security\CSP\ContentSecurityPolicy; -use OC\Security\CSP\ContentSecurityPolicyNonceManager; +use OCA\JSLoader\Listeners\AddContentSecurityPolicyListener; +use OCA\JSLoader\Listeners\BeforeTemplateRenderedListener; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; -use OCP\IAppConfig; -use OCP\IURLGenerator; -use OCP\Security\IContentSecurityPolicyManager; -use OCP\Server; -use OCP\Util; +use OCP\AppFramework\Http\Events\BeforeLoginTemplateRenderedEvent; +use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; +use OCP\Security\CSP\AddContentSecurityPolicyEvent; class Application extends App implements IBootstrap { public const APP_ID = 'jsloader'; @@ -29,38 +27,12 @@ public function __construct(array $urlParams = []) { } public function register(IRegistrationContext $context): void { - // nothing to do here + $context->registerEventListener(AddContentSecurityPolicyEvent::class, AddContentSecurityPolicyListener::class); + $context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); + $context->registerEventListener(BeforeLoginTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class); } public function boot(IBootContext $context): void { - $appConfig = Server::get(IAppConfig::class); - $urlGenerator = Server::get(IURLGenerator::class); - $contentSecurityPolicyManager = Server::get(IContentSecurityPolicyManager::class); - $contentSecurityPolicyNonceManager = Server::get(ContentSecurityPolicyNonceManager::class); - - $snippet = $appConfig->getValueString(self::APP_ID, 'snippet', ''); - if ($snippet !== '') { - $linkToJs = $urlGenerator->linkToRoute('jsloader.JS.script', [ - 'v' => $appConfig->getValueString(self::APP_ID, 'cachebuster', '0'), - ]); - - Util::addHeader( - 'script', - [ - 'src' => $linkToJs, - 'nonce' => $contentSecurityPolicyNonceManager->getNonce() - ], '' - ); - - // whitelist the URL to allow loading JS from this external domain - $url = $appConfig->getValueString(self::APP_ID, 'url'); - if ($url !== '') { - $policy = new ContentSecurityPolicy(); - $policy->addAllowedScriptDomain($url); - $policy->addAllowedImageDomain($url); - $policy->addAllowedConnectDomain($url); - $contentSecurityPolicyManager->addDefaultPolicy($policy); - } - } + // nop } } diff --git a/lib/Listeners/AddContentSecurityPolicyListener.php b/lib/Listeners/AddContentSecurityPolicyListener.php new file mode 100644 index 0000000..9c1e148 --- /dev/null +++ b/lib/Listeners/AddContentSecurityPolicyListener.php @@ -0,0 +1,45 @@ + + */ +class AddContentSecurityPolicyListener implements IEventListener { + + public function __construct( + private readonly \OCP\IAppConfig $appConfig, + ) { + } + + public function handle(Event $event): void { + if (!$event instanceof AddContentSecurityPolicyEvent) { + return; + } + + // whitelist the URL to allow loading JS from this external domain + $url = $this->appConfig->getValueString(Application::APP_ID, 'url'); + if ($url !== '') { + $policy = new ContentSecurityPolicy(); + $policy->addAllowedScriptDomain($url); + $policy->addAllowedImageDomain($url); + $policy->addAllowedConnectDomain($url); + $event->addPolicy($policy); + } + } +} diff --git a/lib/Listeners/BeforeTemplateRenderedListener.php b/lib/Listeners/BeforeTemplateRenderedListener.php new file mode 100644 index 0000000..05fa176 --- /dev/null +++ b/lib/Listeners/BeforeTemplateRenderedListener.php @@ -0,0 +1,58 @@ + + */ +class BeforeTemplateRenderedListener implements IEventListener { + + public function __construct( + private readonly IAppConfig $appConfig, + private readonly IURLGenerator $urlGenerator, + private readonly ContentSecurityPolicyNonceManager $contentSecurityPolicyNonceManager, + ) { + } + + public function handle(Event $event): void { + if (!($event instanceof BeforeTemplateRenderedEvent) && !($event instanceof BeforeLoginTemplateRenderedEvent)) { + return; + } + + $snippet = $this->appConfig->getValueString(Application::APP_ID, 'snippet', ''); + if ($snippet === '') { + return; + } + + $linkToJs = $this->urlGenerator->linkToRoute('jsloader.JS.script', [ + 'v' => $this->appConfig->getValueString(Application::APP_ID, 'cachebuster', '0'), + ]); + + Util::addHeader( + 'script', + [ + 'src' => $linkToJs, + 'nonce' => $this->contentSecurityPolicyNonceManager->getNonce() + ], '' + ); + } +} diff --git a/psalm.xml b/psalm.xml index d736243..47c58da 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,6 +1,6 @@ - - + + + diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index 5792e59..5c064bb 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -1,9 +1,2 @@ - - - - - - - - + diff --git a/tests/stubs/oc_security_csp_ContentSecurityPolicyNonceManager.php b/tests/stubs/oc_security_csp_ContentSecurityPolicyNonceManager.php new file mode 100644 index 0000000..b3d46ed --- /dev/null +++ b/tests/stubs/oc_security_csp_ContentSecurityPolicyNonceManager.php @@ -0,0 +1,25 @@ +