diff --git a/config/global.ini.php b/config/global.ini.php index 76b16ca454254d16968a7b3c28399fa76f65351f..5d6d138f9bcd4fcaae6dc94dd3766d35548e5710 100644 --- a/config/global.ini.php +++ b/config/global.ini.php @@ -385,6 +385,13 @@ enable_trusted_host_check = 1 ;trusted_hosts[] = example.com ;trusted_hosts[] = stats.example.com +; List of Cross-origin resource sharing hosts (eg domain or subdomain names) when generating absolute URLs. +; Described here: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing +; +; Examples: +;cors_hosts[] = example.com +;cors_hosts[] = stats.example.com + ; If you use this Piwik instance over multiple hostnames, Piwik will need to know ; a unique instance_id for this instance, so that Piwik can serve the right custom logo and tmp/* assets, ; independantly of the hostname Piwik is currently running under. diff --git a/core/Url.php b/core/Url.php index bf1e267e10aab0f6e05ab9dadf09170cf1d8da7b..5d3071d7d5f5e2c2b6628821d646c1a489547318 100644 --- a/core/Url.php +++ b/core/Url.php @@ -258,11 +258,21 @@ class Url * @return bool */ public static function saveTrustedHostnameInConfig($host) + { + return self::saveHostsnameInConfig($host, 'General', 'trusted_hosts'); + } + + public static function saveCORSHostnameInConfig($host) + { + return self::saveHostsnameInConfig($host, 'General', 'cors_hosts'); + } + + protected static function saveHostsnameInConfig($host, $domain, $key) { if (Piwik::hasUserSuperUserAccess() && file_exists(Config::getLocalConfigPath()) ) { - $general = Config::getInstance()->General; + $config = Config::getInstance()->$domain; if (!is_array($host)) { $host = array($host); } @@ -270,8 +280,8 @@ class Url if (empty($host)) { return false; } - $general['trusted_hosts'] = $host; - Config::getInstance()->General = $general; + $config[$key] = $host; + Config::getInstance()->$domain = $config; Config::getInstance()->forceSave(); return true; } @@ -535,24 +545,14 @@ class Url $parsedUrl = @parse_url($url); $host = IP::sanitizeIp(@$parsedUrl['host']); return !empty($host) - && ($disableHostCheck || in_array($host, $hosts)) - && !empty($parsedUrl['scheme']) - && in_array($parsedUrl['scheme'], array('http', 'https')); + && ($disableHostCheck || in_array($host, $hosts)) + && !empty($parsedUrl['scheme']) + && in_array($parsedUrl['scheme'], array('http', 'https')); } public static function getTrustedHostsFromConfig() { - $trustedHosts = @Config::getInstance()->General['trusted_hosts']; - if (!is_array($trustedHosts)) { - return array(); - } - foreach ($trustedHosts as &$trustedHost) { - // Case user wrote in the config, http://example.com/test instead of example.com - if (UrlHelper::isLookLikeUrl($trustedHost)) { - $trustedHost = parse_url($trustedHost, PHP_URL_HOST); - } - } - return $trustedHosts; + return self::getHostsFromConfig('General', 'trusted_hosts'); } public static function getTrustedHosts() @@ -560,6 +560,11 @@ class Url return self::getTrustedHostsFromConfig(); } + public static function getCorsHostsFromConfig() + { + return self::getHostsFromConfig('General', 'cors_hosts', false); + } + /** * Returns hostname, without port numbers * @@ -570,4 +575,25 @@ class Url { return IP::sanitizeIp($host); } + + protected static function getHostsFromConfig($domain, $key, $parseToHost = true) + { + $config = @Config::getInstance()->$domain; + + if (!isset($config[$key])) { + return array(); + } + + $hosts = $config[$key]; + if (!is_array($hosts)) { + return array(); + } + foreach ($hosts as &$host) { + // Case user wrote in the config, http://example.com/test instead of example.com + if ($parseToHost && UrlHelper::isLookLikeUrl($host)) { + $host = parse_url($host, PHP_URL_HOST); + } + } + return $hosts; + } } diff --git a/lang/en.json b/lang/en.json index 78748c464d9d2268beee05e6ca21bbdfdc5913e2..2b5e1ad3de3e2695b17e1c33819be9ee5d566298 100644 --- a/lang/en.json +++ b/lang/en.json @@ -167,6 +167,7 @@ "TrackingCode": "Tracking Code", "TrustedHostConfirm": "Are you sure you want to change the trusted Piwik hostname?", "TrustedHostSettings": "Trusted Piwik Hostname", + "CORSSettings": "Cross-origin resource sharing hostnames", "UpdateSettings": "Update settings", "UseCustomLogo": "Use a custom logo", "ValidPiwikHostname": "Valid Piwik Hostname", diff --git a/plugins/API/Controller.php b/plugins/API/Controller.php index 670bf25b7f2067c8904a74306457a5a7177892f8..f713bf3203c08cd3d65e4fdd22d98839f2af64db 100644 --- a/plugins/API/Controller.php +++ b/plugins/API/Controller.php @@ -14,6 +14,7 @@ use Piwik\API\Request; use Piwik\Common; use Piwik\Config; use Piwik\Piwik; +use Piwik\Url; use Piwik\View; /** @@ -27,6 +28,12 @@ class Controller extends \Piwik\Plugin\Controller if (!isset($_GET['filter_limit'])) { $_GET['filter_limit'] = Config::getInstance()->General['API_datatable_default_limit']; } + + $corsHosts = Url::getCorsHostsFromConfig(); + if (!empty($corsHosts)) { + header('Access-Control-Allow-Origin: ' . implode(',', $corsHosts)); + } + $request = new Request('token_auth=' . Common::getRequestVar('token_auth', 'anonymous', 'string')); return $request->process(); } diff --git a/plugins/CoreAdminHome/Controller.php b/plugins/CoreAdminHome/Controller.php index 0722b45a8eb215e6174266f6bf58deaeba0122d2..6b28d9a02448a5178503f255629c94f19387b982 100644 --- a/plugins/CoreAdminHome/Controller.php +++ b/plugins/CoreAdminHome/Controller.php @@ -51,6 +51,7 @@ class Controller extends \Piwik\Plugin\ControllerAdmin $this->handleGeneralSettingsAdmin($view); $view->trustedHosts = Url::getTrustedHostsFromConfig(); + $view->corsHosts = Url::getCorsHostsFromConfig(); $logo = new CustomLogo(); $view->branding = array('use_custom_logo' => $logo->isEnabled()); @@ -313,6 +314,13 @@ class Controller extends \Piwik\Plugin\ControllerAdmin if ($trustedHosts !== false) { Url::saveTrustedHostnameInConfig($trustedHosts); } + + // update cors host settings + $corsHosts = Common::getRequestVar('corsHosts', false, 'json'); + if ($corsHosts !== false) { + Url::saveCORSHostnameInConfig($corsHosts); + } + Config::getInstance()->forceSave(); $pluginUpdateCommunication = new UpdateCommunication(); diff --git a/plugins/CoreAdminHome/javascripts/generalSettings.js b/plugins/CoreAdminHome/javascripts/generalSettings.js index eb622e909c97fc3d6d8f1cb6fd411abdf2e4570b..34cbe2afe0108f6439fc25bb790db356dde46d60 100644 --- a/plugins/CoreAdminHome/javascripts/generalSettings.js +++ b/plugins/CoreAdminHome/javascripts/generalSettings.js @@ -16,6 +16,11 @@ function sendGeneralSettingsAJAX() { trustedHosts.push($(this).val()); }); + var corsHosts = []; + $('input[name=cors_host]').each(function () { + corsHosts.push($(this).val()); + }); + var ajaxHandler = new ajaxHelper(); ajaxHandler.setLoadingElement(); ajaxHandler.addParams({ @@ -32,7 +37,8 @@ function sendGeneralSettingsAJAX() { mailPassword: $('#mailPassword').val(), mailEncryption: $('#mailEncryption').val(), useCustomLogo: isCustomLogoEnabled(), - trustedHosts: JSON.stringify(trustedHosts) + trustedHosts: JSON.stringify(trustedHosts), + corsHosts: JSON.stringify(corsHosts) }, 'POST'); ajaxHandler.addParams({ module: 'CoreAdminHome', @@ -146,4 +152,20 @@ $(document).ready(function () { trustedHostSettings.find('li:last input').val(''); return false; }); + + // cors hosts event handling + var corsHostSettings = $('#corsSettings'); + corsHostSettings.on('click', '.remove-cors-host', function (e) { + e.preventDefault(); + $(this).parent('li').remove(); + return false; + }); + corsHostSettings.find('.add-cors-host').click(function (e) { + e.preventDefault(); + + // append new row to the table + corsHostSettings.find('ul').append($("#add-cors-host-template").html()); +// corsHostSettings.find('li:last input').val(''); + return false; + }); }); diff --git a/plugins/CoreAdminHome/stylesheets/generalSettings.less b/plugins/CoreAdminHome/stylesheets/generalSettings.less index 9e4d954c0662206980453527e0ebbe2898461303..2a056e070db199345d90d39c7b5c661431eda583 100644 --- a/plugins/CoreAdminHome/stylesheets/generalSettings.less +++ b/plugins/CoreAdminHome/stylesheets/generalSettings.less @@ -171,6 +171,7 @@ table.admin tbody td:hover, table.admin tbody th:hover { width: 238px; } -#trustedHostSettings .add-trusted-host-container { +#trustedHostSettings .add-trusted-host-container, +#corsSettings .add-cors-host-container{ padding: 12px 24px; -} \ No newline at end of file +} diff --git a/plugins/CoreAdminHome/templates/generalSettings.twig b/plugins/CoreAdminHome/templates/generalSettings.twig index 1a1854339ec13d930f8737b37f8ca0beb9b0ae8b..13de3fd83140ccbfad5e43692070e6391dbd4cc3 100644 --- a/plugins/CoreAdminHome/templates/generalSettings.twig +++ b/plugins/CoreAdminHome/templates/generalSettings.twig @@ -319,6 +319,36 @@ {% endif %} </div> + <h2 id="corsSection">{{ 'CoreAdminHome_CORSSettings'|translate }}</h2> + <div id='corsSettings'> + + {% if not isGeneralSettingsAdminEnabled %} + {{ 'CoreAdminHome_PiwikIsInstalledAt'|translate }}: {{ corsHosts|join(", ") }} + {% else %} + <script type="template/javascript" id="add-cors-host-template"> + <li> + <input name="cors_host" type="text" value=""/> + <a href="#" class="remove-cors-host" title="{{ 'General_Delete'|translate }}"> + <img alt="{{ 'General_Delete'|translate }}" src="plugins/Morpheus/images/ico_delete.png" /> + </a> + </li> + </script> + <ul> + {% for hostIdx, host in corsHosts %} + <li> + <input name="cors_host" type="text" value="{{ host }}"/> + <a href="#" class="remove-cors-host" title="{{ 'General_Delete'|translate }}"> + <img alt="{{ 'General_Delete'|translate }}" src="plugins/Morpheus/images/ico_delete.png" /> + </a> + </li> + {% endfor %} + </ul> + <div class="add-cors-host-container"> + <a href="#" class="add-cors-host"><em>{{ 'General_Add'|translate }}</em></a> + </div> + {% endif %} + </div> + <input type="submit" value="{{ 'General_Save'|translate }}" id="generalSettingsSubmit" class="submit"/> <br/> <br/> diff --git a/plugins/Morpheus/stylesheets/general/_forms.less b/plugins/Morpheus/stylesheets/general/_forms.less index 2372f4d92bb370207d5fc26572f01e7409000cc9..bac73f074ada2851428e77e1ec631d859888fc81 100644 --- a/plugins/Morpheus/stylesheets/general/_forms.less +++ b/plugins/Morpheus/stylesheets/general/_forms.less @@ -9,6 +9,7 @@ input:not([type="checkbox"]), select, textarea { } button, .add-trusted-host, +.add-cors-host, input[type="submit"], button[type="button"], .submit { @@ -295,4 +296,4 @@ label { .small-form-description { // for tracking code generator clear:both; -} \ No newline at end of file +} diff --git a/plugins/Morpheus/stylesheets/ui/_popups.less b/plugins/Morpheus/stylesheets/ui/_popups.less index 7cd8d6b1afdd0295d334ca7eef6ec4c2265527b9..dc2b2140de5b271f118399b2786f7a0795d9a786 100644 --- a/plugins/Morpheus/stylesheets/ui/_popups.less +++ b/plugins/Morpheus/stylesheets/ui/_popups.less @@ -41,6 +41,6 @@ button.ui-state-default, .ui-widget-content button.ui-state-default, .ui-widget- background: @color-silver-l90; } -button:hover, .add-trusted-host:hover, input[type="submit"]:hover, button[type="button"]:hover, .submit:hover { +button:hover, .add-trusted-host:hover, .add-cors-host:hover, input[type="submit"]:hover, button[type="button"]:hover, .submit:hover { background-color: @theme-color-brand !important; -} \ No newline at end of file +}