1*ef5bb1f3SLORTET<?php 2*ef5bb1f3SLORTETif (!defined('DOKU_INC')) die(); 3*ef5bb1f3SLORTET 4*ef5bb1f3SLORTETclass helper_plugin_extranet extends DokuWiki_Plugin 5*ef5bb1f3SLORTET{ 6*ef5bb1f3SLORTET private function getConfiguredString(array $keys, string $fallback = ''): string 7*ef5bb1f3SLORTET { 8*ef5bb1f3SLORTET foreach ($keys as $key) { 9*ef5bb1f3SLORTET $value = trim((string)$this->getConf($key)); 10*ef5bb1f3SLORTET if ($value !== '') return $value; 11*ef5bb1f3SLORTET } 12*ef5bb1f3SLORTET 13*ef5bb1f3SLORTET return $fallback; 14*ef5bb1f3SLORTET } 15*ef5bb1f3SLORTET 16*ef5bb1f3SLORTET public function getDefaultPolicy(): string 17*ef5bb1f3SLORTET { 18*ef5bb1f3SLORTET $mode = strtolower(trim((string)$this->getConf('default_policy'))); 19*ef5bb1f3SLORTET if (in_array($mode, ['allow', 'block', 'force_allow', 'force_block'], true)) { 20*ef5bb1f3SLORTET return $mode; 21*ef5bb1f3SLORTET } 22*ef5bb1f3SLORTET return 'allow'; 23*ef5bb1f3SLORTET } 24*ef5bb1f3SLORTET 25*ef5bb1f3SLORTET private function hasMacro(string $content, string $macro): bool 26*ef5bb1f3SLORTET { 27*ef5bb1f3SLORTET return (bool)preg_match('/~~\s*' . preg_quote($macro, '/') . '\s*~~/i', $content); 28*ef5bb1f3SLORTET } 29*ef5bb1f3SLORTET 30*ef5bb1f3SLORTET public function parseRuleList($raw): array 31*ef5bb1f3SLORTET { 32*ef5bb1f3SLORTET $parts = preg_split('/[\r\n,;]+/', (string)$raw); 33*ef5bb1f3SLORTET $rules = []; 34*ef5bb1f3SLORTET foreach ($parts as $part) { 35*ef5bb1f3SLORTET $rule = trim((string)$part); 36*ef5bb1f3SLORTET if ($rule !== '') $rules[] = $rule; 37*ef5bb1f3SLORTET } 38*ef5bb1f3SLORTET return $rules; 39*ef5bb1f3SLORTET } 40*ef5bb1f3SLORTET 41*ef5bb1f3SLORTET public function idMatchesRule(string $id, string $rule): bool 42*ef5bb1f3SLORTET { 43*ef5bb1f3SLORTET $rule = trim($rule); 44*ef5bb1f3SLORTET if ($rule === '') return false; 45*ef5bb1f3SLORTET 46*ef5bb1f3SLORTET if (strlen($rule) > 1 && $rule[0] === '/' && substr($rule, -1) === '/') { 47*ef5bb1f3SLORTET return (bool)@preg_match($rule, $id); 48*ef5bb1f3SLORTET } 49*ef5bb1f3SLORTET 50*ef5bb1f3SLORTET if (substr($rule, -1) === ':') { 51*ef5bb1f3SLORTET return strpos($id, $rule) === 0; 52*ef5bb1f3SLORTET } 53*ef5bb1f3SLORTET 54*ef5bb1f3SLORTET if (strpos($rule, '*') !== false) { 55*ef5bb1f3SLORTET $regex = '/^' . str_replace('\\*', '.*', preg_quote($rule, '/')) . '$/'; 56*ef5bb1f3SLORTET return (bool)preg_match($regex, $id); 57*ef5bb1f3SLORTET } 58*ef5bb1f3SLORTET 59*ef5bb1f3SLORTET return $id === $rule; 60*ef5bb1f3SLORTET } 61*ef5bb1f3SLORTET 62*ef5bb1f3SLORTET public function matchesConfiguredList(string $id, string $confKey): bool 63*ef5bb1f3SLORTET { 64*ef5bb1f3SLORTET $rules = $this->parseRuleList($this->getConf($confKey)); 65*ef5bb1f3SLORTET foreach ($rules as $rule) { 66*ef5bb1f3SLORTET if ($this->idMatchesRule($id, $rule)) return true; 67*ef5bb1f3SLORTET } 68*ef5bb1f3SLORTET return false; 69*ef5bb1f3SLORTET } 70*ef5bb1f3SLORTET 71*ef5bb1f3SLORTET public function hasConfiguredList(string $confKey): bool 72*ef5bb1f3SLORTET { 73*ef5bb1f3SLORTET return !empty($this->parseRuleList($this->getConf($confKey))); 74*ef5bb1f3SLORTET } 75*ef5bb1f3SLORTET 76*ef5bb1f3SLORTET private function hasFilter(): bool 77*ef5bb1f3SLORTET { 78*ef5bb1f3SLORTET if ($this->hasConfiguredList('filter_list')) return true; 79*ef5bb1f3SLORTET return trim((string)$this->getConf('filter_regex')) !== ''; 80*ef5bb1f3SLORTET } 81*ef5bb1f3SLORTET 82*ef5bb1f3SLORTET private function isInFilter(string $id): bool 83*ef5bb1f3SLORTET { 84*ef5bb1f3SLORTET // filter_list: exact pages, namespace prefixes and wildcards only — no regex entries 85*ef5bb1f3SLORTET foreach ($this->parseRuleList($this->getConf('filter_list')) as $rule) { 86*ef5bb1f3SLORTET $isRegex = strlen($rule) > 1 && $rule[0] === '/' && substr($rule, -1) === '/'; 87*ef5bb1f3SLORTET if (!$isRegex && $this->idMatchesRule($id, $rule)) return true; 88*ef5bb1f3SLORTET } 89*ef5bb1f3SLORTET 90*ef5bb1f3SLORTET // filter_regex: dedicated regex field 91*ef5bb1f3SLORTET $regex = trim((string)$this->getConf('filter_regex')); 92*ef5bb1f3SLORTET if ($regex !== '' && @preg_match($regex, $id)) return true; 93*ef5bb1f3SLORTET 94*ef5bb1f3SLORTET return false; 95*ef5bb1f3SLORTET } 96*ef5bb1f3SLORTET 97*ef5bb1f3SLORTET public function getRequestMatchKey(): string 98*ef5bb1f3SLORTET { 99*ef5bb1f3SLORTET return $this->getConfiguredString(['request_match_key', 'server_ip_key'], 'REMOTE_ADDR'); 100*ef5bb1f3SLORTET } 101*ef5bb1f3SLORTET 102*ef5bb1f3SLORTET public function getExtranetMatchList(): string 103*ef5bb1f3SLORTET { 104*ef5bb1f3SLORTET return $this->getConfiguredString(['extranet_match_list', 'extranet_ip_list']); 105*ef5bb1f3SLORTET } 106*ef5bb1f3SLORTET 107*ef5bb1f3SLORTET public function getExtranetMatchRegex(): string 108*ef5bb1f3SLORTET { 109*ef5bb1f3SLORTET return $this->getConfiguredString(['extranet_match_regex', 'extranet_ip_regex']); 110*ef5bb1f3SLORTET } 111*ef5bb1f3SLORTET 112*ef5bb1f3SLORTET /** 113*ef5bb1f3SLORTET * Build candidate values from the configured request marker. 114*ef5bb1f3SLORTET * 115*ef5bb1f3SLORTET * A proxy header may contain a single marker ("extranet"), a hostname, 116*ef5bb1f3SLORTET * or a comma-separated list such as X-Forwarded-For. We accept both the 117*ef5bb1f3SLORTET * full raw value and each comma-separated token. 118*ef5bb1f3SLORTET * 119*ef5bb1f3SLORTET * @return string[] 120*ef5bb1f3SLORTET */ 121*ef5bb1f3SLORTET private function getRequestMatchCandidates(): array 122*ef5bb1f3SLORTET { 123*ef5bb1f3SLORTET $requestKey = $this->getRequestMatchKey(); 124*ef5bb1f3SLORTET $rawValue = trim((string)($_SERVER[$requestKey] ?? '')); 125*ef5bb1f3SLORTET if ($rawValue === '') return []; 126*ef5bb1f3SLORTET 127*ef5bb1f3SLORTET $candidates = [$rawValue]; 128*ef5bb1f3SLORTET if (strpos($rawValue, ',') !== false) { 129*ef5bb1f3SLORTET foreach (explode(',', $rawValue) as $part) { 130*ef5bb1f3SLORTET $part = trim((string)$part); 131*ef5bb1f3SLORTET if ($part !== '') $candidates[] = $part; 132*ef5bb1f3SLORTET } 133*ef5bb1f3SLORTET } 134*ef5bb1f3SLORTET 135*ef5bb1f3SLORTET return array_values(array_unique($candidates)); 136*ef5bb1f3SLORTET } 137*ef5bb1f3SLORTET 138*ef5bb1f3SLORTET public function isExtranetRequest(): bool 139*ef5bb1f3SLORTET { 140*ef5bb1f3SLORTET $candidates = $this->getRequestMatchCandidates(); 141*ef5bb1f3SLORTET if ($candidates === []) return false; 142*ef5bb1f3SLORTET 143*ef5bb1f3SLORTET $matchList = $this->getExtranetMatchList(); 144*ef5bb1f3SLORTET if ($matchList !== '') { 145*ef5bb1f3SLORTET foreach (explode(',', $matchList) as $configuredValue) { 146*ef5bb1f3SLORTET $configuredValue = trim((string)$configuredValue); 147*ef5bb1f3SLORTET if ($configuredValue !== '' && in_array($configuredValue, $candidates, true)) { 148*ef5bb1f3SLORTET return true; 149*ef5bb1f3SLORTET } 150*ef5bb1f3SLORTET } 151*ef5bb1f3SLORTET } 152*ef5bb1f3SLORTET 153*ef5bb1f3SLORTET $matchRegex = $this->getExtranetMatchRegex(); 154*ef5bb1f3SLORTET if ($matchRegex !== '') { 155*ef5bb1f3SLORTET foreach ($candidates as $candidate) { 156*ef5bb1f3SLORTET if ((bool)@preg_match($matchRegex, $candidate)) return true; 157*ef5bb1f3SLORTET } 158*ef5bb1f3SLORTET } 159*ef5bb1f3SLORTET 160*ef5bb1f3SLORTET return false; 161*ef5bb1f3SLORTET } 162*ef5bb1f3SLORTET 163*ef5bb1f3SLORTET public function isClientFromExtranet(): bool 164*ef5bb1f3SLORTET { 165*ef5bb1f3SLORTET return $this->isExtranetRequest(); 166*ef5bb1f3SLORTET } 167*ef5bb1f3SLORTET 168*ef5bb1f3SLORTET public function isMediaVisibleFromExtranet(string $mediaID): bool 169*ef5bb1f3SLORTET { 170*ef5bb1f3SLORTET if (!$this->hasFilter()) return true; 171*ef5bb1f3SLORTET 172*ef5bb1f3SLORTET $inFilter = $this->isInFilter($mediaID); 173*ef5bb1f3SLORTET $defaultPolicy = $this->getDefaultPolicy(); 174*ef5bb1f3SLORTET 175*ef5bb1f3SLORTET if ($defaultPolicy === 'force_allow' || $defaultPolicy === 'allow') { 176*ef5bb1f3SLORTET return !$inFilter; 177*ef5bb1f3SLORTET } 178*ef5bb1f3SLORTET 179*ef5bb1f3SLORTET return $inFilter; 180*ef5bb1f3SLORTET } 181*ef5bb1f3SLORTET 182*ef5bb1f3SLORTET public function isPageVisibleFromExtranet(string $id, ?string $content = null): bool 183*ef5bb1f3SLORTET { 184*ef5bb1f3SLORTET if ($content === null) { 185*ef5bb1f3SLORTET $content = rawWiki($id); 186*ef5bb1f3SLORTET } 187*ef5bb1f3SLORTET $content = (string)$content; 188*ef5bb1f3SLORTET 189*ef5bb1f3SLORTET $hasFilter = $this->hasFilter(); 190*ef5bb1f3SLORTET $inFilter = $hasFilter && $this->isInFilter($id); 191*ef5bb1f3SLORTET 192*ef5bb1f3SLORTET $defaultPolicy = $this->getDefaultPolicy(); 193*ef5bb1f3SLORTET 194*ef5bb1f3SLORTET if ($defaultPolicy === 'force_allow') { 195*ef5bb1f3SLORTET return !$inFilter; 196*ef5bb1f3SLORTET } 197*ef5bb1f3SLORTET 198*ef5bb1f3SLORTET if ($defaultPolicy === 'force_block') { 199*ef5bb1f3SLORTET return $inFilter; 200*ef5bb1f3SLORTET } 201*ef5bb1f3SLORTET 202*ef5bb1f3SLORTET $hasExtranet = $this->hasMacro($content, 'EXTRANET'); 203*ef5bb1f3SLORTET $hasNoExtranet = $this->hasMacro($content, 'NOEXTRANET'); 204*ef5bb1f3SLORTET 205*ef5bb1f3SLORTET if ($defaultPolicy === 'allow') { 206*ef5bb1f3SLORTET return !$inFilter && !$hasNoExtranet; 207*ef5bb1f3SLORTET } 208*ef5bb1f3SLORTET 209*ef5bb1f3SLORTET // block 210*ef5bb1f3SLORTET return $inFilter || $hasExtranet; 211*ef5bb1f3SLORTET } 212*ef5bb1f3SLORTET 213*ef5bb1f3SLORTET public function isPageAllowed(string $id, ?string $content = null): bool 214*ef5bb1f3SLORTET { 215*ef5bb1f3SLORTET if (!$this->isExtranetRequest()) return true; 216*ef5bb1f3SLORTET return $this->isPageVisibleFromExtranet($id, $content); 217*ef5bb1f3SLORTET } 218*ef5bb1f3SLORTET 219*ef5bb1f3SLORTET public function isMediaAllowed(string $mediaID): bool 220*ef5bb1f3SLORTET { 221*ef5bb1f3SLORTET if (!$this->isExtranetRequest()) return true; 222*ef5bb1f3SLORTET return $this->isMediaVisibleFromExtranet($mediaID); 223*ef5bb1f3SLORTET } 224*ef5bb1f3SLORTET} 225