diff --git a/api/src/Header/ContentSecurityPolicy.php b/api/src/Header/ContentSecurityPolicy.php new file mode 100644 index 0000000000..581250fd74 --- /dev/null +++ b/api/src/Header/ContentSecurityPolicy.php @@ -0,0 +1,164 @@ + + * @package api + * @subpackage header + * @access public + * @version $Id$ + */ + +namespace EGroupware\Api\Header; + +/** + * Content Security Policy headers + */ +class ContentSecurityPolicy +{ + /** + * Additional attributes or urls for CSP beside always added "self" + * + * - "script-src 'self' 'unsafe-eval'" allows only self and eval (eg. ckeditor), but forbids inline scripts, onchange, etc + * - "connect-src 'self'" allows ajax requests only to self + * - "style-src 'self' 'unsafe-inline'" allows only self and inline style, which we need + * - "frame-src 'self' manual.egroupware.org" allows frame and iframe content only for self or manual.egroupware.org + * + * @var array + */ + private static $sources = array( + 'script-src' => array("'unsafe-eval'"), + 'style-src' => array("'unsafe-inline'"), + 'connect-src' => array(), + 'frame-src' => null, // NOT array(), to allow setting no default frame-src! + ); + + /** + * Add Content-Security-Policy sources + * + * Calling this method with an empty array for frame-src, sets no defaults but "'self'"! + * + * @param string|array $set =array() URL (incl. protocol!) + * @param string $source valid CSP source types like 'script-src', 'style-src', 'connect-src', 'frame-src', ... + * @param string|array $attrs 'unsafe-eval' and/or 'unsafe-inline' (without quotes!) or URL (incl. protocol!) + */ + public static function add($source, $attrs) + { + if (!isset(self::$sources[$source])) + { + // set frame-src attrs of API and apps via hook + if ($source == 'frame-src' && !isset($attrs)) + { + $attrs = array('manual.egroupware.org', 'www.egroupware.org'); + if (($app_additional = $GLOBALS['egw']->hooks->process('csp-frame-src'))) + { + foreach($app_additional as $addtional) + { + if ($addtional) $attrs = array_unique(array_merge($attrs, $addtional)); + } + } + } + self::$sources[$source] = array(); + } + foreach((array)$attrs as $attr) + { + if (in_array($attr, array('none', 'self', 'unsafe-eval', 'unsafe-inline'))) + { + $attr = "'$attr'"; // automatic add quotes + } + if (!in_array($attr, self::$sources[$source])) + { + self::$sources[$source][] = $attr; + //error_log(__METHOD__."() setting CSP script-src $attr ".function_backtrace()); + } + } + } + + /** + * Set Content-Security-Policy attributes for script-src: 'unsafe-eval' and/or 'unsafe-inline' + * + * Using CK-Editor currently requires both to be set :( + * + * Old pre-et2 apps might need to call Api\Headers::script_src_attrs(array('unsafe-eval','unsafe-inline')) + * + * EGroupware itself currently still requires 'unsafe-eval'! + * + * @param string|array $set =array() 'unsafe-eval' and/or 'unsafe-inline' (without quotes!) or URL (incl. protocol!) + */ + public static function add_script_src($set=null) + { + self::add('script-src', $set); + } + + /** + * Set Content-Security-Policy attributes for style-src: 'unsafe-inline' + * + * EGroupware itself currently still requires 'unsafe-inline'! + * + * @param string|array $set =array() 'unsafe-inline' (without quotes!) and/or URL (incl. protocol!) + */ + public static function add_style_src($set=null) + { + self::add('style-src', $set); + } + + /** + * Set Content-Security-Policy attributes for connect-src: + * + * @param string|array $set =array() URL (incl. protocol!) + */ + public static function add_connect_src($set=null) + { + self::add('connect-src', $set); + } + + /** + * Set/get Content-Security-Policy attributes for frame-src: + * + * Calling this method with an empty array sets no frame-src, but "'self'"! + * + * @param string|array $set =array() URL (incl. protocol!) + * @return string with attributes eg. "'unsafe-inline'" + */ + public static function add_frame_src($set=null) + { + self::add('frame-src', $set); + } + + /** + * Send Content-Security-Policy header + * + * @link http://content-security-policy.com/ + */ + public static function send() + { + self::add('frame-src', null); // set defaults for frame-src + + $policies = array(); + foreach(self::$sources as $source => $urls) + { + $policies[] = "$source 'self' ".implode(' ', $urls); + } + $csp = implode('; ', $policies); + + //$csp = "default-src * 'unsafe-eval' 'unsafe-inline'"; // allow everything + + $user_agent = UserAgent::type(); + $version = UserAgent::version(); + + // recommendaton ist to not send regular AND deprecated headers together, as they can cause unexpected behavior + if ($user_agent == 'chrome' && $version < 25 || $user_agent == 'safari' && $version < 7) + { + header("X-Webkit-CSP: $csp"); // Chrome: <= 24, Safari incl. iOS + } + elseif ($user_agent == 'firefox' && $version < 23 || $user_agent == 'msie') // Edge is reported as 'edge'! + { + header("X-Content-Security-Policy: $csp"); + } + else + { + header("Content-Security-Policy: $csp"); + } + } +} diff --git a/api/src/Header/UserAgent.php b/api/src/Header/UserAgent.php index 7cdf273b4d..514e0b93b5 100644 --- a/api/src/Header/UserAgent.php +++ b/api/src/Header/UserAgent.php @@ -50,7 +50,7 @@ class UserAgent } /** - * user-agent: 'firefox', 'msie', 'safari' (incl. iPhone), 'chrome', 'opera', 'konqueror', 'mozilla' + * user-agent: 'firefox', 'msie', 'edge', 'safari' (incl. iPhone), 'chrome', 'opera', 'konqueror', 'mozilla' * * @var string */ diff --git a/api/src/Html.php b/api/src/Html.php index 391e5720e2..6416410840 100644 --- a/api/src/Html.php +++ b/api/src/Html.php @@ -16,7 +16,6 @@ namespace EGroupware\Api; // explicitly import classes not yet imported into Api use egw_framework; // validate_file, includeCSS -use egw_ckeditor_config; use egw; // link /** @@ -437,7 +436,7 @@ class Html // User preferences $font = $GLOBALS['egw_info']['user']['preferences']['common']['rte_font']; - $font_size = egw_ckeditor_config::font_size_from_prefs(); + $font_size = Html\CkEditorConfing::font_size_from_prefs(); $font_span = ''; if (empty($font) && empty($font_size)) $font_span = ''; @@ -449,7 +448,7 @@ class Html