Skip to content

Commit

Permalink
Use postMessage instead of directly making API calls in the overlay i…
Browse files Browse the repository at this point in the history
…frame. (matomo-org#13446)

* Use postMessage instead of directly making API calls in the overlay iframe.

* Make sure it will work when Matomo is on a subfolder.

* Increase overlay security with domain and method whitelists.

* Try to fix UI test.

* Fix tests + UI test blacklist check.

* broadcast.getValuesFromUrl does not decode URL params.
  • Loading branch information
diosmosis authored and InfinityVoid committed Oct 11, 2018
1 parent fba5562 commit c9fdf45
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 18 deletions.
6 changes: 2 additions & 4 deletions config/environment/ui-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@
}

$controllerActionblacklist = StaticContainer::get('tests.ui.url_normalizer_blacklist.controller');
if (!empty($request['module'])
&& !empty($request['action'])
) {
$controllerAction = $request['module'] . '.' . $request['action'];
if (!empty($request['module'])) {
$controllerAction = $request['module'] . '.' . (isset($request['action']) ? $request['action'] : 'index');
if (in_array($controllerAction, $controllerActionblacklist)) {
return;
}
Expand Down
2 changes: 2 additions & 0 deletions plugins/Overlay/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Piwik\Tracker\Action;
use Piwik\Tracker\PageUrl;
use Piwik\View;
use Piwik\Plugins\SitesManager;

class Controller extends \Piwik\Plugin\Controller
{
Expand Down Expand Up @@ -53,6 +54,7 @@ public function index()
$view->segment = Request::getRawSegmentFromRequest();

$view->ssl = ProxyHttp::isHttps();
$view->siteUrls = SitesManager\API::getInstance()->getSiteUrlsFromId($this->site->getId());

$this->outputCORSHeaders();
return $view->render();
Expand Down
70 changes: 58 additions & 12 deletions plugins/Overlay/client/client.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
var Piwik_Overlay_Client = (function () {

var DOMAIN_PARSE_REGEX = /^http(s)?:\/\/(www\.)?([^\/]*)/i;

/** jQuery */
var $;

/** Url of the Piwik root */
var piwikRoot;

/** protocol and domain of Piwik root */
var piwikOrigin;

/** Piwik idsite */
var idSite;

Expand All @@ -15,12 +20,18 @@ var Piwik_Overlay_Client = (function () {
/** Reference to the status bar DOM element */
var statusBar;

/** Counter for request IDs for postMessage based API requests. */
var lastRequestId = 0;

/** Map of callbacks for postMessage based API requests. */
var requestCallbacks = {};

/** Load the client CSS */
function loadCss() {
var css = c('link').attr({
rel: 'stylesheet',
type: 'text/css',
href: piwikRoot + 'plugins/Overlay/client/client.css'
href: piwikRoot + '/plugins/Overlay/client/client.css'
});
$('head').append(css);
}
Expand Down Expand Up @@ -51,7 +62,7 @@ var Piwik_Overlay_Client = (function () {
// check whether the session has been opened in a new tab (instead of an iframe)
if (window != window.top) {
var iframe = c('iframe', false, {
src: piwikRoot + 'index.php?module=Overlay&action=notifyParentIframe#' + window.location.href
src: piwikRoot + '/index.php?module=Overlay&action=notifyParentIframe#' + window.location.href
}).css({width: 0, height: 0, border: 0});

$('body').append(iframe);
Expand Down Expand Up @@ -81,11 +92,48 @@ var Piwik_Overlay_Client = (function () {
return el;
}

function nextRequestId() {
var nextId = lastRequestId + 1;
lastRequestId = nextId;
return nextId;
}

function handlePostMessages() {
window.addEventListener("message", function (event) {
if (event.origin !== piwikOrigin) {
return;
}

var strData = event.data.split(':', 3);
if (strData[0] !== 'overlay.response') {
return;
}

var requestId = strData[1];
if (!requestCallbacks[requestId]) {
return;
}

var callback = requestCallbacks[requestId];
delete requestCallbacks[requestId];

var data = JSON.parse(decodeURIComponent(strData[2]));
if (typeof data.result !== 'undefined'
&& data.result === 'error'
) {
alert('Error: ' + data.message);
} else {
callback(data);
}
}, false);
}

return {

/** Initialize in-site analytics */
initialize: function (pPiwikRoot, pIdSite, pPeriod, pDate, pSegment) {
piwikRoot = pPiwikRoot;
piwikOrigin = piwikRoot.match(DOMAIN_PARSE_REGEX)[0];
idSite = pIdSite;
period = pPeriod;
date = pDate;
Expand All @@ -95,6 +143,7 @@ var Piwik_Overlay_Client = (function () {
var loading = this.loadingNotification;

loadJQuery(function () {
handlePostMessages();
notifyPiwikOfLocation();
loadCss();

Expand Down Expand Up @@ -138,13 +187,13 @@ var Piwik_Overlay_Client = (function () {
};
script.onload = onLoad;

script.src = piwikRoot + relativePath + '?v=1';
script.src = piwikRoot + '/' + relativePath + '?v=1';
head.appendChild(script);
},

/** Piwik Overlay API Request */
api: function (method, callback, additionalParams) {
var url = piwikRoot + 'index.php?module=API&method=Overlay.' + method
var url = piwikRoot + '/index.php?module=API&method=Overlay.' + method
+ '&idSite=' + idSite + '&period=' + period + '&date=' + date + '&format=JSON&filter_limit=-1';

if (segment) {
Expand All @@ -155,14 +204,11 @@ var Piwik_Overlay_Client = (function () {
url += '&' + additionalParams;
}

$.getJSON(url + "&jsoncallback=?", function (data) {
if (typeof data.result != 'undefined' && data.result == 'error') {
alert('Error: ' + data.message);
}
else {
callback(data);
}
});
var requestId = nextRequestId();
requestCallbacks[requestId] = callback;

var matomoFrame = window.parent;
matomoFrame.postMessage('overlay.call:' + requestId + ':' + encodeURIComponent(url), piwikOrigin);
},

/**
Expand Down
2 changes: 2 additions & 0 deletions plugins/Overlay/config/ui-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
)),
'tests.ui.url_normalizer_blacklist.controller' => DI\add(array(
'Overlay.renderSidebar',
'Overlay.index',
'Overlay.startOverlaySession',
)),

);
83 changes: 81 additions & 2 deletions plugins/Overlay/javascripts/Piwik_Overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

var Piwik_Overlay = (function () {

var DOMAIN_PARSE_REGEX = /^http(s)?:\/\/(www\.)?([^\/]*)/i;
var ORIGIN_PARSE_REGEX = /^https?:\/\/[^\/]*/;
var ALLOWED_API_REQUEST_WHITELIST = [
'Overlay.getTranslations',
'Overlay.getExcludedQueryParameters',
'Overlay.getFollowingPages',
];

var $body, $iframe, $sidebar, $main, $location, $loading, $errorNotLoading;
var $rowEvolutionLink, $transitionsLink, $visitorLogLink;

Expand All @@ -18,6 +26,7 @@ var Piwik_Overlay = (function () {
var iframeCurrentPageNormalized = '';
var iframeCurrentActionLabel = '';
var updateComesFromInsideFrame = false;
var iframeOrigin = '';

/** Load the sidebar for a url */
function loadSidebar(currentUrl) {
Expand All @@ -26,7 +35,7 @@ var Piwik_Overlay = (function () {
$location.html(' ').unbind('mouseenter').unbind('mouseleave');

iframeCurrentPage = currentUrl;
iframeDomain = currentUrl.match(/http(s)?:\/\/(www\.)?([^\/]*)/i)[3];
iframeDomain = currentUrl.match(DOMAIN_PARSE_REGEX)[3];

var params = {
module: 'Overlay',
Expand Down Expand Up @@ -135,11 +144,31 @@ var Piwik_Overlay = (function () {
return location;
}

function setIframeOrigin(location) {
iframeOrigin = location.match(ORIGIN_PARSE_REGEX)[0];

// unset iframe origin if it is not one of the site URLs
var validSiteOrigins = Piwik_Overlay.siteUrls.map(function (url) {
return url.match(ORIGIN_PARSE_REGEX)[0];
});

if (iframeOrigin && validSiteOrigins.indexOf(iframeOrigin) === -1) {
try {
console.log('Found invalid iframe origin in hash URL: ' + iframeOrigin);
} catch (e) {
// ignore
}
iframeOrigin = null;
}
}

/** $.history callback for hash change */
function hashChangeCallback(urlHash) {
var location = getOverlayLocationFromHash(urlHash);
location = Overlay_Helper.decodeFrameUrl(location);

setIframeOrigin(location);

if (location == iframeCurrentPageNormalized) {
return;
}
Expand All @@ -158,6 +187,54 @@ var Piwik_Overlay = (function () {
updateComesFromInsideFrame = false;
}

function handleApiRequests() {
window.addEventListener("message", function (event) {
if (event.origin !== iframeOrigin || !iframeOrigin) {
return;
}

var strData = event.data.split(':', 3);
if (strData[0] !== 'overlay.call') {
return;
}

var requestId = strData[1];
var url = decodeURIComponent(strData[2]);

var params = broadcast.getValuesFromUrl(url);
Object.keys(params).forEach(function (name) {
params[name] = decodeURIComponent(params[name]);
});
params.module = 'API';
params.action = 'index';

if (ALLOWED_API_REQUEST_WHITELIST.indexOf(params.method) === -1) {
sendResponse({
result: 'error',
message: "'" + params.method + "' method is not allowed.",
});
return;
}

angular.element(document).injector().invoke(['piwikApi', function (piwikApi) {
piwikApi.fetch(params)
.then(function (response) {
sendResponse(response);
}).catch(function (err) {
sendResponse({
result: 'error',
message: err.message,
});
});
}]);

function sendResponse(data) {
var message = 'overlay.response:' + requestId + ':' + encodeURIComponent(JSON.stringify(data));
$iframe[0].contentWindow.postMessage(message, iframeOrigin);
}
}, false);
}

return {

/** This method is called when Overlay loads */
Expand All @@ -181,7 +258,6 @@ var Piwik_Overlay = (function () {
$visitorLogLink = $('#overlaySegmentedVisitorLog');

adjustDimensions();

showLoading();

// apply initial dimensions
Expand All @@ -206,6 +282,8 @@ var Piwik_Overlay = (function () {
hashChangeCallback('');
}

handleApiRequests();

// handle date selection
var $select = $('select#overlayDateRangeSelect').change(function () {
var parts = $(this).val().split(';');
Expand Down Expand Up @@ -286,6 +364,7 @@ var Piwik_Overlay = (function () {
window.location.replace(newLocation);
} else {
// happens when the url is changed by hand or when the l parameter is there on page load
setIframeOrigin(currentUrl);
loadSidebar(currentUrl);
}
}
Expand Down
2 changes: 2 additions & 0 deletions plugins/Overlay/templates/index.twig
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
<script type="text/javascript">
broadcast._isInit = true;
$(function () {
Piwik_Overlay.siteUrls = {{ siteUrls|json_encode|raw }};
var iframeSrc = 'index.php?module=Overlay&action=startOverlaySession&idSite={{ idSite }}&period={{ period }}&date={{ rawDate }}&segment={{ segment }}';
Piwik_Overlay.init(iframeSrc, '{{ idSite }}', '{{ period }}', '{{ rawDate }}', '{{ segment }}');
Expand Down

0 comments on commit c9fdf45

Please sign in to comment.