xref: /template/strap/action/metacsp.php (revision c3437056399326d621a01da73b649707fbb0ae69)
121913ab3SNickeau<?php
221913ab3SNickeau
3e8b2ff59SNickeauuse ComboStrap\LogUtility;
4531e725cSNickeauuse ComboStrap\Site;
521913ab3SNickeauuse ComboStrap\StringUtility;
621913ab3SNickeau
721913ab3SNickeauif (!defined('DOKU_INC')) die();
821913ab3SNickeau
921913ab3SNickeau/**
1021913ab3SNickeau *
1121913ab3SNickeau * Adding security directive
1221913ab3SNickeau *
1321913ab3SNickeau */
1421913ab3SNickeauclass action_plugin_combo_metacsp extends DokuWiki_Action_Plugin
1521913ab3SNickeau{
1621913ab3SNickeau
1721913ab3SNickeau
1821913ab3SNickeau    function __construct()
1921913ab3SNickeau    {
2021913ab3SNickeau        // enable direct access to language strings
2121913ab3SNickeau        // ie $this->lang
2221913ab3SNickeau        $this->setupLocale();
2321913ab3SNickeau    }
2421913ab3SNickeau
2521913ab3SNickeau    public function register(Doku_Event_Handler $controller)
2621913ab3SNickeau    {
27e8b2ff59SNickeau
28e8b2ff59SNickeau        $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'httpHeaderCsp', array());
29e8b2ff59SNickeau        $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'htmlMetaCsp', array());
30e8b2ff59SNickeau
3121913ab3SNickeau    }
3221913ab3SNickeau
3321913ab3SNickeau    /**
3421913ab3SNickeau     * Dokuwiki has already a canonical methodology
3521913ab3SNickeau     * https://www.dokuwiki.org/canonical
3621913ab3SNickeau     *
3721913ab3SNickeau     * @param $event
3821913ab3SNickeau     */
39e8b2ff59SNickeau    function htmlMetaCsp($event)
4021913ab3SNickeau    {
4121913ab3SNickeau
42531e725cSNickeau
43531e725cSNickeau        /**
44531e725cSNickeau         * HTML meta directives
45531e725cSNickeau         */
4621913ab3SNickeau        $directives = [
4721913ab3SNickeau            'block-all-mixed-content', // no http, https
4821913ab3SNickeau        ];
4921913ab3SNickeau
5021913ab3SNickeau        // Search if the CSP property is already present
5121913ab3SNickeau        $cspKey = null;
5221913ab3SNickeau        foreach ($event->data['meta'] as $key => $meta) {
5321913ab3SNickeau            if (isset($meta["http-equiv"])) {
5485e82846SNickeau                if ($meta["http-equiv"] == "content-security-policy") {
5521913ab3SNickeau                    $cspKey = $key;
5621913ab3SNickeau                }
5721913ab3SNickeau            }
5821913ab3SNickeau        }
5921913ab3SNickeau        if ($cspKey != null) {
6021913ab3SNickeau            $actualDirectives = StringUtility::explodeAndTrim($event->data['meta'][$cspKey]["content"], ",");
6121913ab3SNickeau            $directives = array_merge($actualDirectives, $directives);
6221913ab3SNickeau            $event->data['meta'][$cspKey] = [
6385e82846SNickeau                "http-equiv" => "content-security-policy",
6421913ab3SNickeau                "content" => join(", ", $directives)
6521913ab3SNickeau            ];
6621913ab3SNickeau        } else {
6721913ab3SNickeau            $event->data['meta'][] = [
6885e82846SNickeau                "http-equiv" => "content-security-policy",
6921913ab3SNickeau                "content" => join(",", $directives)
7021913ab3SNickeau            ];
7121913ab3SNickeau        }
7221913ab3SNickeau
73e8b2ff59SNickeau    }
74e8b2ff59SNickeau
75e8b2ff59SNickeau    function httpHeaderCsp($event)
76e8b2ff59SNickeau    {
77531e725cSNickeau        /**
78531e725cSNickeau         * Http header CSP directives
79531e725cSNickeau         */
80531e725cSNickeau        $httpHeaderReferer = $_SERVER['HTTP_REFERER'];
81531e725cSNickeau        $httpDirectives = [];
82*c3437056SNickeau        if (strpos($httpHeaderReferer, Site::getBaseUrl()) === false) {
83531e725cSNickeau            // not same origin
8421913ab3SNickeau            $httpDirectives = [
85*c3437056SNickeau                // the page cannot be used in a iframe (clickjacking),
86*c3437056SNickeau                "content-security-policy: frame-ancestors 'none'",
87*c3437056SNickeau                // the page cannot be used in a iframe (clickjacking) - deprecated for frame ancestores
88*c3437056SNickeau                "X-Frame-Options: SAMEORIGIN",
89*c3437056SNickeau                // stops a browser from trying to MIME-sniff the content type and forces it to stick with the declared content-type
90*c3437056SNickeau                "X-Content-Type-Options: nosniff",
91*c3437056SNickeau                // sends the origin if cross origin otherwise the full refer for same origin
92*c3437056SNickeau                "Referrer-Policy: strict-origin-when-cross-origin",
93*c3437056SNickeau                // controls DNS prefetching, allowing browsers to proactively perform domain name resolution on external links, images, CSS, JavaScript, and more. This prefetching is performed in the background, so the DNS is more likely to be resolved by the time the referenced items are needed. This reduces latency when the user clicks a link.
94*c3437056SNickeau                "X-DNS-Prefetch-Control: on",
95*c3437056SNickeau                // This header stops pages from loading when they detect reflected cross-site scripting (XSS) attacks. Although this protection is not necessary when sites implement a strong Content-Security-Policy disabling the use of inline JavaScript ('unsafe-inline'), it can still provide protection for older web browsers that don't support CSP.
96*c3437056SNickeau                "X-XSS-Protection: 1; mode=block"
9721913ab3SNickeau            ];
98531e725cSNickeau        }
99e8b2ff59SNickeau        if (!headers_sent()) {
10021913ab3SNickeau            foreach ($httpDirectives as $httpDirective) {
10121913ab3SNickeau                header($httpDirective);
10221913ab3SNickeau            }
103e8b2ff59SNickeau        } else {
104e8b2ff59SNickeau            LogUtility::msg("HTTP Headers have already ben sent. We couldn't add the CSP security header", LogUtility::LVL_MSG_WARNING,"security");
105e8b2ff59SNickeau        }
10621913ab3SNickeau    }
10721913ab3SNickeau
10821913ab3SNickeau}
109