XCL Web Application Platform 2.5.0
The XoopsCube Legacy Project
Loading...
Searching...
No Matches
ContentSecurityPolicy.class.php
1<?php
10
12{
13 public function preBlockFilter()
14 {
15 // Register with high priority to ensure it runs before output starts
16 $this->mRoot->mDelegateManager->add('Legacy_RenderSystem.SetupXoopsTpl', array($this, 'addCSPHeaders'), XCUBE_DELEGATE_PRIORITY_FIRST);
17
18 // Also hook into the header output specifically
19 $this->mRoot->mDelegateManager->add('Legacy_RenderSystem.RenderTheme', array($this, 'ensureCSPHeaders'), XCUBE_DELEGATE_PRIORITY_FIRST);
20
21 // Don't try to start a session - XCube already handles this
22 // if (!isset($_SESSION)) {
23 // @session_start();
24 // }
25
26 // Create log directory if it doesn't exist
27 $log_dir = XOOPS_CACHE_PATH . '/protector/logs';
28 if (!is_dir($log_dir)) {
29 @mkdir($log_dir, 0755, true);
30 }
31 }
32
33 public function addCSPHeaders(&$xoopsTpl)
34 {
35 // Skip if headers already sent
36 if (headers_sent()) {
37 $this->logDebug('Headers already sent when addCSPHeaders was called');
38 return;
39 }
40
41 // Get CSP configuration
42 $moduleHandler = xoops_gethandler('module');
43 $configHandler = xoops_gethandler('config');
44
45 $module = $moduleHandler->getByDirname('protector');
46 if (!is_object($module)) {
47 $this->logDebug('Protector module not found');
48 return;
49 }
50
51 $configs = $configHandler->getConfigsByCat(0, $module->getVar('mid'));
52
53 // Check if CSP is enabled
54 if (empty($configs['enable_csp'])) {
55 $this->logDebug('CSP is disabled in Protector settings');
56 return;
57 }
58
59 // Build CSP header
60 $policy = $this->buildCSPPolicy($configs);
61
62 // Add CSP header
63 header("Content-Security-Policy: " . $policy);
64
65 // Add report-only header if configured
66 if (!empty($configs['csp_report_only'])) {
67 header("Content-Security-Policy-Report-Only: " . $policy);
68 }
69
70 // Add CSP meta tag for older browsers
71 if (!empty($configs['csp_legacy_support']) && is_object($xoopsTpl)) {
72 $meta_tag = '<meta http-equiv="Content-Security-Policy" content="' . htmlspecialchars($policy, ENT_QUOTES) . '">';
73 $xoopsTpl->assign('xoops_csp_meta', $meta_tag);
74
75 // Also add to mета array for theme compatibility
76 $metas = $xoopsTpl->get_template_vars('xoops_meta');
77 if (!is_array($metas)) {
78 $metas = array();
79 }
80 $metas['csp'] = array('http-equiv' => 'Content-Security-Policy', 'content' => $policy);
81 $xoopsTpl->assign('xoops_meta', $metas);
82 }
83
84 $this->logDebug('CSP headers added: ' . $policy);
85 }
86
87 // Ensure CSP headers are set even if the normal hook fails
88 public function ensureCSPHeaders(&$xoopsTpl)
89 {
90 // Only proceed if we haven't already set headers and they haven't been sent yet
91 if (!headers_sent() && !isset($GLOBALS['CSP_HEADERS_ADDED'])) {
92 $this->addCSPHeaders($xoopsTpl);
93 $GLOBALS['CSP_HEADERS_ADDED'] = true;
94 }
95 }
96
97 private function buildCSPPolicy($configs)
98 {
99 $policy = array();
100
101 // Default sources
102 if (!empty($configs['csp_default_src'])) {
103 $policy[] = "default-src " . $configs['csp_default_src'];
104 } else {
105 $policy[] = "default-src 'self'";
106 }
107
108 // Script sources
109 if (!empty($configs['csp_script_src'])) {
110 $policy[] = "script-src " . $configs['csp_script_src'];
111 }
112
113 // Style sources
114 if (!empty($configs['csp_style_src'])) {
115 $policy[] = "style-src " . $configs['csp_style_src'];
116 }
117
118 // Image sources
119 if (!empty($configs['csp_img_src'])) {
120 $policy[] = "img-src " . $configs['csp_img_src'];
121 }
122
123 // Connect sources
124 if (!empty($configs['csp_connect_src'])) {
125 $policy[] = "connect-src " . $configs['csp_connect_src'];
126 }
127
128 // Font sources
129 if (!empty($configs['csp_font_src'])) {
130 $policy[] = "font-src " . $configs['csp_font_src'];
131 }
132
133 // Object sources
134 if (!empty($configs['csp_object_src'])) {
135 $policy[] = "object-src " . $configs['csp_object_src'];
136 }
137
138 // Media sources
139 if (!empty($configs['csp_media_src'])) {
140 $policy[] = "media-src " . $configs['csp_media_src'];
141 }
142
143 // Frame sources
144 if (!empty($configs['csp_frame_src'])) {
145 $policy[] = "frame-src " . $configs['csp_frame_src'];
146 }
147
148 // Always add report-uri to collect violations
149 $report_uri = !empty($configs['csp_report_uri'])
150 ? $configs['csp_report_uri']
151 : XOOPS_URL . '/modules/protector/csp-report.php';
152
153 $policy[] = "report-uri " . $report_uri;
154
155 return implode('; ', $policy);
156 }
157
158 // Helper function for debugging
159 private function logDebug($message)
160 {
161 // Get module config
162 $moduleHandler = xoops_gethandler('module');
163 $configHandler = xoops_gethandler('config');
164 $module = $moduleHandler->getByDirname('protector');
165
166 if (is_object($module)) {
167 $configs = $configHandler->getConfigsByCat(0, $module->getVar('mid'));
168
169 // Only log if debug is enabled
170 if (!empty($configs['csp_debug'])) {
171 $log_file = XOOPS_CACHE_PATH . '/protector/logs/csp_debug.log';
172 $log_entry = date('Y-m-d H:i:s') . ' - ' . $message . "\n";
173 @file_put_contents($log_file, $log_entry, FILE_APPEND);
174
175 // Also log to PHP error log
176 error_log('CSP: ' . $message);
177 }
178 }
179 }
180}
preBlockFilter()
[Abstract] Executes the logic, when the controller executes preBlockFilter().