Support CSP in Matomo/Piwik plugin

Change-Id: Ie80c6ffca714460c310e2b55ad9b3b63f5ae71ad
diff --git a/Changes b/Changes
index 80b1bc0..1c6bb51 100755
--- a/Changes
+++ b/Changes
@@ -17,6 +17,9 @@
         - Support CSP in notifications framework.
         - Fetch plugin configs from JSON file to be
           CSP compliant.
+        - Support CSP in Matomo/Piwik plugin.
+        - Removed deprecated default behaviour
+          of the Piwik/Matomo plugin.
 
 0.40 2020-12-17
         - Modernize ES and fix in-loops.
diff --git a/Makefile.PL b/Makefile.PL
index d2dca03..8aaee56 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -33,7 +33,7 @@
     'JSON' => 4.02,
 
     # Required for bundled plugins
-    'Mojolicious::Plugin::Piwik' => 0.26,
+    'Mojolicious::Plugin::Piwik' => 0.28,
 
     # Currently on GitHub only (github.com/akron)
     'Mojolicious::Plugin::Localize' => 0.20,
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index 1dbc373..59d2695 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -164,16 +164,17 @@
   });
 
   # Establish content security policy
+  # This needs to be defined prior to Kalamar::Plugin::Piwik!
   $self->plugin(CSP => {
     'default-src' => 'self',
-    'style-src' => ['self','unsafe-inline'],
-    'script-src' => 'self',
+    'style-src'   => ['self','unsafe-inline'],
+    'script-src'  => 'self',
     'connect-src' => 'self',
-    'frame-src' => '*',
-    'media-src' => 'none',
-    'object-src' => 'self',
-    'font-src' => 'self',
-    'img-src' => ['self', 'data:'],
+    'frame-src'   => '*',
+    'media-src'   => 'none',
+    'object-src'  => 'self',
+    'font-src'    => 'self',
+    'img-src'     => ['self', 'data:'],
     -with_nonce => 1
   });
 
@@ -251,15 +252,6 @@
   };
 
   # Deprecated Legacy code
-  if ($self->config('Piwik') &&
-        none { $_ eq 'Piwik' } @{$conf->{plugins} // []}) {
-
-    # 2018-11-12
-    deprecated 'Piwik is no longer considered a mandatory plugin';
-    $self->plugin('Kalamar::Plugin::Piwik');
-  };
-
-  # Deprecated Legacy code
   if ($self->config('Kalamar')->{auth_support} &&
         none { $_ eq 'Auth' } @{$conf->{plugins} // []}) {
 
diff --git a/lib/Kalamar/Plugin/Piwik.pm b/lib/Kalamar/Plugin/Piwik.pm
index e991250..ca414ff 100644
--- a/lib/Kalamar/Plugin/Piwik.pm
+++ b/lib/Kalamar/Plugin/Piwik.pm
@@ -14,6 +14,28 @@
     };
   };
 
+  # Add event handler for korap requests
+  my $piwik_conf = $mojo->config('Piwik');
+  if ($piwik_conf) {
+    $piwik_conf->{append} //= '';
+  }
+  else {
+    $piwik_conf = { append => '' };
+    $mojo->config(Piwik => $piwik_conf);
+  };
+
+  my $url = $piwik_conf->{url};
+
+  $piwik_conf->{append} .= <<APPEND;
+;window.addEventListener('korapRequest', function(e) {
+    let _paq=window._paq=window._paq||[];
+    _paq.push(['setDocumentTitle', e.detail.title]);
+    _paq.push(['setReferrerUrl', location.href]);
+    _paq.push(['setCustomUrl', e.detail.url]);
+    _paq.push(['trackPageView']);
+})
+APPEND
+
   # Load Piwik if not yet loaded
   unless (exists $mojo->renderer->helpers->{piwik_tag}) {
     $mojo->plugin('Piwik');
@@ -37,28 +59,20 @@
       }
   );
 
+  # Add tracking code as <script/> instead of inline
+
+  $mojo->csp->add('script-src' => $url);
+  $mojo->csp->add('connect-src' => $url);
+  $mojo->csp->add('img-src' => $url);
+
+  # Set track script for CSP compliant tracking
+  $mojo->routes->any('/js/tracking.js')->piwik('track_script');
+
   # Add piwik tag to scripts
   $mojo->content_block(scripts => {
-    inline => '<%= piwik_tag %>'
+    inline => q!<%= piwik_tag 'as-script' %>!
   });
 
-  # Add event handler for korap requests
-  $mojo->content_block(scripts => {
-    inline => <<'SCRIPT'
-% if (stash('piwik.embed')) {
-  %= javascript begin
-window.addEventListener('korapRequest', function(e) {
-  _paq.push(['setDocumentTitle', e.detail.title]);
-  _paq.push(['setReferrerUrl', location.href]);
-  _paq.push(['setCustomUrl', e.detail.url]);
-  _paq.push(['trackPageView']);
-});
-  % end
-% }
-SCRIPT
-  });
-
-
   # If all requests should be pinged,
   # establish this hook
   if ($param->{ping_requests}) {
diff --git a/t/plugin/piwik.t b/t/plugin/piwik.t
index 5559cc6..a2ad05c 100644
--- a/t/plugin/piwik.t
+++ b/t/plugin/piwik.t
@@ -3,12 +3,12 @@
 use Test::Mojo;
 
 # Test the documentation
-my $t = Test::Mojo->new('Kalamar');
-
-$t->app->plugin('Piwik' => {
-  url => 'https://piwik.korap.ids-mannheim.de/',
-  site_id => 1,
-  embed => 1
+my $t = Test::Mojo->new('Kalamar' => {
+  'Piwik' => {
+    url => 'https://piwik.korap.ids-mannheim.de/',
+    site_id => 1,
+    embed => 1
+  }
 });
 
 # Load piwik
@@ -18,27 +18,63 @@
   ->status_is(200)
   ->text_like('section[name=piwik-opt-out] h3', qr!can I opt-out!)
   ->element_exists('section[name=piwik-opt-out] iframe')
+  ->content_unlike(qr!var _paq!)
+  ->content_unlike(qr!window\.addEventListener\('korapRequest!)
+  ->content_unlike(qr!setDocumentTitle!)
+  ->content_unlike(qr!setCustomUrl!)
+  ->content_unlike(qr!trackPageView!)
+  ->element_exists('script[src$="/js/tracking.js"]')
+  ;
+
+$t = Test::Mojo->new('Kalamar' => {
+  'Piwik' => {
+    url => 'https://piwik.korap.ids-mannheim.de/',
+    site_id => 1,
+    embed => 1,
+    append => 'console.log("fun")'
+  }
+});
+
+$t->app->plugin('Kalamar::Plugin::Piwik');
+
+is($t->app->piwik_tag('as-script'), '<script src="/js/tracking.js"></script>' .
+     '<script src="https://piwik.korap.ids-mannheim.de/piwik.js" async defer></script>');
+
+$t->get_ok('/doc/faq')
+  ->status_is(200)
+  ->text_like('section[name=piwik-opt-out] h3', qr!can I opt-out!)
+  ->element_exists('section[name=piwik-opt-out] iframe')
+  ->element_exists('script[src$="/js/tracking.js"]')
+  ->content_unlike(qr!_paq!)
+  ->header_like('Content-Security-Policy',qr!connect-src 'self' [^;]*?https://piwik\.korap\.ids-mannheim\.de/!)
+  ->header_like('Content-Security-Policy',qr!img-src 'self' [^;]*?https://piwik\.korap\.ids-mannheim\.de/!)
+  ->header_like('Content-Security-Policy',qr!script-src 'self' [^;]*?https://piwik.korap.ids-mannheim.de/!)
+  ;
+
+$t->get_ok('/js/tracking.js')
+  ->status_is(200)
   ->content_like(qr!var _paq!)
-  ->content_like(qr!window\.addEventListener\('korapRequest!)
-  ->content_like(qr!setDocumentTitle!)
-  ->content_like(qr!setCustomUrl!)
-  ->content_like(qr!trackPageView!)
+  ->content_like(qr!;console\.log\("fun"\)!)
+  ->content_like(qr!;window\.addEventListener\('korapRequest!)
   ;
 
 # No embedding
-$t->app->plugin('Piwik' => {
-  url => 'https://piwik.korap.ids-mannheim.de/',
-  site_id => 1,
-  embed => 0
+$t = Test::Mojo->new('Kalamar' => {
+  'Piwik' => {
+    url => 'https://piwik.korap.ids-mannheim.de/',
+    site_id => 1,
+    embed => 0
+  }
 });
+$t->app->plugin('Kalamar::Plugin::Piwik');
 
 $t->get_ok('/doc/faq')
   ->status_is(200)
   ->text_like('section[name=piwik-opt-out] h3', qr!can I opt-out!)
   ->element_exists_not('section[name=piwik-opt-out] iframe')
-  ->content_unlike(qr!var _paq!)
+  ->content_unlike(qr!_paq!)
   ->content_unlike(qr!window\.addEventListener\('korapRequest!)
+  ->element_exists_not('script[src$="/js/tracking.js"]')
   ;
 
-
-done_testing();
+done_testing;