From e82af4b680c00f0d148a0e52fd11baa826a4297c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Czo=C5=82nowski?= <marcin@czolnowski.net> Date: Sun, 3 Aug 2014 20:53:24 +0200 Subject: [PATCH] Add feature to persist cors hosts in config, edit in general settings tab and append to API headers if any is set. --- config/global.ini.php | 7 +++ core/Url.php | 60 +++++++++++++------ lang/en.json | 1 + plugins/API/Controller.php | 7 +++ plugins/CoreAdminHome/Controller.php | 8 +++ .../javascripts/generalSettings.js | 24 +++++++- .../stylesheets/generalSettings.less | 5 +- .../templates/generalSettings.twig | 30 ++++++++++ .../Morpheus/stylesheets/general/_forms.less | 3 +- plugins/Morpheus/stylesheets/ui/_popups.less | 4 +- 10 files changed, 126 insertions(+), 23 deletions(-) diff --git a/config/global.ini.php b/config/global.ini.php index 76b16ca454..5d6d138f9b 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 bf1e267e10..5d3071d7d5 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 43346c184b..e101f7fa3d 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 670bf25b7f..f713bf3203 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 0722b45a8e..6b28d9a024 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 eb622e909c..34cbe2afe0 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 9e4d954c06..2a056e070d 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 1a1854339e..13de3fd831 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 2372f4d92b..bac73f074a 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 7cd8d6b1af..dc2b2140de 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 +} -- GitLab