Difference between revisions of "Extension:SimpleSecurity"
(base on current version to start with) |
|||
| Line 1: | Line 1: | ||
| − | # | + | <?php |
| + | # Simple security extension{{php}}{{Category:Extensions|SimpleSecurity4}} | ||
| + | # - See http://www.mediawiki.org/Extension:Simple_Security for installation and usage details | ||
| + | # - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html) | ||
| + | # - Needs apache's mod-rewrite for security on images, see code comments below | ||
| + | # - Version 4.0.0 started 2007-10-11 | ||
| + | |||
| + | if (!defined('MEDIAWIKI')) die('Not an entry point.'); | ||
| + | |||
| + | define('SIMPLESECURITY_VERSION','4.0.0, 2007-10-11'); | ||
| + | |||
| + | # Global security settings | ||
| + | $wgSecurityMagic = "security"; # the parser-function name for security directives | ||
| + | $wgSecurityMagicNoi = "!security"; # the name for non-inheriting security directives | ||
| + | $wgSecurityMagicIf = "ifusercan"; # the name for doing a permission-based conditional | ||
| + | $wgSecurityMagicGroup = "ifgroup"; # the name for doing a group-based conditional | ||
| + | $wgSecurityEnableInheritance = false; # specifies whether or not security directives in categories inherit to member articles | ||
| + | $wgSecurityEnableForImages = false; # specifies whether security directives in image/file articles also apply to the associated binary | ||
| + | $wgSecuritySysops = array('sysop'); # the list of groups whose members bypass all security (groups a all lowercase, user are ucfirst) | ||
| + | $wgSecurityDenyTemplate = 'Template:Action not permitted'; | ||
| + | $wgSecurityInfoTemplate = 'Template:Security info'; | ||
| + | $wgSecurityRuleTemplate = ''; # set to a template for the | ||
| + | $wgSecurityDenyImage = "$IP/skins/common/images/mediawiki.png"; # the image returned in place of requested image if read access denied | ||
| + | $wgSecurityParseInfo = isset($wgSecurityParseInfo) ? $wgSecurityParseInfo : false; | ||
| + | $wgSecurityGroupsArticle = ''; # Name of an article which contains a bullet list of available groups for Special:Userrights | ||
| + | $wgSecurityLogActions = array('download'); # Actions that should be logged | ||
| + | |||
| + | $wgExtensionFunctions[] = 'wfSetupSimpleSecurity'; | ||
| + | $wgHooks['LanguageGetMagic'][] = 'wfSimpleSecurityLanguageGetMagic'; | ||
| + | |||
| + | $wgExtensionCredits['parserhook'][] = array( | ||
| + | 'name' => "Simple Security", | ||
| + | 'author' => '[http://www.organicdesign.co.nz/User:Nad User:Nad]', | ||
| + | 'description' => 'A simple to implement security extension', | ||
| + | 'url' => 'http://www.mediawiki.org/wiki/Extension:Simple_Security', | ||
| + | 'version' => SIMPLESECURITY_VERSION | ||
| + | ); | ||
| + | |||
| + | class SimpleSecurity { | ||
| + | |||
| + | # Private internal data | ||
| + | var $initialised; | ||
| + | var $rules; | ||
| + | var $directives; | ||
| + | var $activeHooks; | ||
| + | var $title; | ||
| + | var $action; | ||
| + | var $allowed; | ||
| + | var $file; | ||
| + | var $path; | ||
| + | |||
| + | # Needed in some versions to prevent Special:Version from breaking | ||
| + | function __toString() { return 'SimpleSecurity'; } | ||
| + | |||
| + | # Constructor | ||
| + | function SimpleSecurity($inheritance = true) { | ||
| + | global $wgParser,$wgHooks,$wgLogTypes,$wgLogNames,$wgLogHeaders,$wgLogActions, | ||
| + | $wgSecurityMagic,$wgSecurityMagicNoi,$wgSecurityMagicIf,$wgSecurityMagicGroup, | ||
| + | $wgSecurityEnableInheritance,$wgGroupPermissions,$wgSecurityGroupsArticle; | ||
| + | |||
| + | # Add all our required event hooks | ||
| + | $wgHooks['userCan'][] = $this; | ||
| + | $wgHooks['ArticleAfterFetchContent'][] = $this; | ||
| + | $wgHooks['OutputPageBeforeHTML'][] = $this; | ||
| + | |||
| + | $wgParser->setFunctionHook($wgSecurityMagic,array($this,'processDirective')); | ||
| + | if ($wgSecurityEnableInheritance) $wgParser->setFunctionHook($wgSecurityMagicNoi,array($this,'processDirectiveNoi')); | ||
| + | $wgParser->setFunctionHook($wgSecurityMagicIf,array($this,'ifUserCan')); | ||
| + | $wgParser->setFunctionHook($wgSecurityMagicGroup,array($this,'ifGroup')); | ||
| + | |||
| + | # Specify which of the hooks are currently active because these apply to all parser objects | ||
| + | $this->activeHooks = array( | ||
| + | 'ArticleAfterFetchContent' => false, | ||
| + | 'ParserBeforeStrip' => false | ||
| + | ); | ||
| + | |||
| + | # Initialise internal data | ||
| + | $this->initialised = false; | ||
| + | $this->allowed = true; | ||
| + | $this->directives = array(); | ||
| + | $this->rules = array(); | ||
| + | $this->file = ''; | ||
| + | $this->path = ''; | ||
| + | |||
| + | # Add extra available groups if $wgSecurityGroupsArticle is set | ||
| + | if ($wgSecurityGroupsArticle) { | ||
| + | $groups = new Article(Title::newFromText($wgSecurityGroupsArticle)); | ||
| + | if (preg_match_all('/^\\*\\s*(.+?)\\s*$/m',$groups->getContent(),$match)) | ||
| + | foreach($match[1] as $group) $wgGroupPermissions[$group] = array(); | ||
| + | } | ||
| + | |||
| + | # Add a new log type | ||
| + | $wgLogTypes[] = 'security'; | ||
| + | $wgLogNames ['security'] = 'securitylogpage'; | ||
| + | $wgLogHeaders['security'] = 'securitylogpagetext'; | ||
| + | $wgLogActions['security/deny'] = 'securitylogentry'; | ||
| + | } | ||
| + | |||
| + | # Process a normal security directive | ||
| + | function processDirective(&$parser,$actions = '',$groups = '') { | ||
| + | $parser->mOutput->mCacheTime = -1; | ||
| + | if ($actions && $groups) $this->directives[] = array($actions,$groups); | ||
| + | return ''; | ||
| + | } | ||
| + | |||
| + | # Process a non-inheriting security directive | ||
| + | function processDirectiveNoi(&$parser,$actions = '',$groups = '') { | ||
| + | $parser->mOutput->mCacheTime = -1; | ||
| + | if ($actions && $groups) $this->directives[] = array($actions,$groups); | ||
| + | return ''; | ||
| + | } | ||
| + | |||
| + | # Process the ifUserCan conditional security directive | ||
| + | function ifUserCan(&$parser,$action,$title,$if,$else = '') { | ||
| + | return $this->validateTitle($action,Title::newFromText($title)) ? $if : $else; | ||
| + | } | ||
| + | |||
| + | # Process the ifGroup conditional security directive | ||
| + | # - evaluates to true if current uset belongs to any of the groups in $groups (csv) | ||
| + | function ifGroup(&$parser,$groups,$if,$else = '') { | ||
| + | global $wgUser; | ||
| + | $intersection = array_intersect( | ||
| + | array_map('strtolower',split(',',$groups)), | ||
| + | array_map('strtolower',$wgUser->getEffectiveGroups()) | ||
| + | ); | ||
| + | return count($intersection) > 0 ? $if : $else; | ||
| + | } | ||
| + | |||
| + | # ArticleAfterFetchContent hook: Asseses security after raw content fetched from database and clears if not readable | ||
| + | # - also fills the global $securityCache cache with info to append to the rendered article | ||
| + | function onArticleAfterFetchContent(&$article,&$text) { | ||
| + | global $wgSecurityDenyTemplate; | ||
| + | $title = $article->mTitle->getText(); | ||
| + | if (!$this->activeHooks['ArticleAfterFetchContent']) return true; | ||
| + | if (!$this->validateTitle('view',$article->mTitle)) $text = '{'.'{'."$wgSecurityDenyTemplate|fetch|$title}}"; | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | # Render any security info | ||
| + | function onOutputPageBeforeHTML(&$out,&$text) { | ||
| + | global $wgUser,$wgTitle,$wgSecurityInfoTemplate,$wgSecurityDenyTemplate, | ||
| + | $wgSiteNotice,$wgSecurityParseInfo,$wgSecurityRuleTemplate,$wgSecurityLogActions; | ||
| + | static $done = 0; | ||
| + | if ($done++) return true; | ||
| + | $psr = new Parser; | ||
| + | $psropt = ParserOptions::newFromUser($wgUser); | ||
| + | |||
| + | # Add rules information for this article if any | ||
| + | $rules = $this->rules[$this->title]; | ||
| + | if (count($rules)) { | ||
| + | |||
| + | # Construct wikitext for info | ||
| + | $info = '{'.'{'."$wgSecurityInfoTemplate|1=\n"; | ||
| + | foreach ($rules as $rule) { | ||
| + | $a = $rule[0] == '*' ? 'Every action' : ucfirst($rule[0]); | ||
| + | $b = $rule[1] == '*' ? 'anybody' : ($rule[1] == 'user' ? 'logged in' : $rule[1]); | ||
| + | $c = isset($rule[2]) ? $rule[2] : ''; | ||
| + | if ($wgSecurityRuleTemplate) $info .= '{'.'{'."$wgSecurityRuleTemplate|$a|$b|$c}}\n"; | ||
| + | else $info .= "*'''$a''' requires the user to be '''$b''''' $c''\n"; | ||
| + | } | ||
| + | $info .= "\n}}"; | ||
| + | |||
| + | # Parse the wikitext (depending on $wgSecurityParseInfo) and add to SiteNotice | ||
| + | if ($wgSecurityParseInfo) { | ||
| + | $psrout = $psr->parse($info,$wgTitle,$psropt,true,true); | ||
| + | $info = $psrout->getText(); | ||
| + | } | ||
| + | $wgSiteNotice .= $info; | ||
| + | } | ||
| + | |||
| + | # Replace main body with deny-message and append log if action allowed | ||
| + | if (!$this->allowed) { | ||
| + | $text = ''; | ||
| + | $psrout = $psr->parse('{'.'{'."$wgSecurityDenyTemplate|{$this->action}|{$this->title}}}",$wgTitle,$psropt,true,true); | ||
| + | $out->mBodytext = $psrout->getText(); | ||
| + | if (in_array($this->action,$wgSecurityLogActions)) { | ||
| + | $msg = wfMsgForContent('securitylogdeny',$this->action,$this->title); | ||
| + | $log = new LogPage('security',false); | ||
| + | $log->addEntry('deny',$wgUser->getUserPage(),$msg); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | return true; | ||
| + | } | ||
| + | |||
| + | # Main validation code for the whole request is done on the first call to userCan | ||
| + | function onuserCan(&$title, &$user, $ucaction, &$result) { | ||
| + | global $wgTitle,$action,$wgRequest,$wgUser,$wgUploadDirectory, | ||
| + | $wgSecurityEnableForImages,$wgSecurityDenyImage,$wgSecurityLogActions; | ||
| + | |||
| + | # Add IsAllowed hook to the $wgUser object | ||
| + | static $done = 0; | ||
| + | if ($done++ == 0) { | ||
| + | |||
| + | # Create a new User class ($User2) by extending the existing one with an overridden isAllowed method | ||
| + | $User = get_class($wgUser); | ||
| + | $User2 = $User.'2'; | ||
| + | eval("class $User2 extends $User".' { | ||
| + | function isAllowed($action = "") { | ||
| + | $result = NULL; | ||
| + | wfRunHooks("IsAllowed",array(&$this,$action,&$result)); | ||
| + | return $result === NULL ? $result = parent::isAllowed($action) : $result; | ||
| + | } | ||
| + | }'); | ||
| + | |||
| + | # Replace the $wgUser object with an identical $User2 instance | ||
| + | $oldUser = $wgUser; | ||
| + | $wgUser = new $User2(); | ||
| + | foreach(array_keys(get_class_vars($User)) as $k) $wgUser->$k = $oldUser->$k; | ||
| + | } | ||
| + | |||
| + | $this->title = $wgTitle->getPrefixedURL(); | ||
| + | $this->action = $action == 'submit' ? 'edit' : strtolower($action); | ||
| + | |||
| + | # Moves need to be handled differently | ||
| + | if ($this->title == 'Special:Movepage' && $this->action == 'submit') { | ||
| + | $this->action = 'move'; | ||
| + | $this->title = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target')); | ||
| + | } | ||
| + | |||
| + | # Handle security on files (needs apache mod-rewrite) | ||
| + | # - /wiki/images/... rewritten to article title Download/image-path... | ||
| + | if (ereg('^Download/(.+)/([^/]+)$',$this->title,$match)) { | ||
| + | $this->path = $match[1]; | ||
| + | $this->file = $image = $match[2]; | ||
| + | if (ereg('^thumb/.+/([^/]+)$',$this->path,$match)) $image = $match[1]; | ||
| + | $wgTitle = Title::newFromText($this->title = "Image:$image"); | ||
| + | $action = 'raw'; | ||
| + | } | ||
| + | |||
| + | if ($this->initialised) { | ||
| + | # we can call $this->validate in here so mediawiki can render links according to permissions | ||
| + | } | ||
| + | elseif (!empty($wgUser->mDataLoaded)) { | ||
| + | |||
| + | # Activate the hooks now that enough information is present to assess security | ||
| + | $this->initialised = true; | ||
| + | $this->activeHooks['ArticleAfterFetchContent'] = true; | ||
| + | |||
| + | # Validate current global action and set to view if not allowed | ||
| + | if (!$this->allowed = $this->validateTitle($this->action,$wgTitle)) { | ||
| + | $action = 'view'; | ||
| + | global $wgEnableParserCache,$wgOut; | ||
| + | $wgEnableParserCache = false; | ||
| + | $wgOut->enableClientCache(false); | ||
| + | } | ||
| + | |||
| + | # If the requested item is a file, return it to the client if security validates otherwise return the SecurityDenyImage | ||
| + | if ($wgSecurityEnableForImages && $this->file) { | ||
| + | if (in_array('download',$wgSecurityLogActions)) { | ||
| + | $msg = wfMsgForContent('securitylogdeny','download',$this->title); | ||
| + | $log = new LogPage('security',false); | ||
| + | $log->addEntry('deny',$wgUser->getUserPage(),$msg); | ||
| + | } | ||
| + | $pathname = $this->allowed ? "$wgUploadDirectory/".$this->path.'/'.$this->file : $wgSecurityDenyImage; | ||
| + | while (@ob_end_clean()); | ||
| + | header('Content-Type: application/octet-stream'); | ||
| + | header('Content-Disposition: attachment; filename="'.$this->file.'"'); | ||
| + | $content = implode('',file($pathname)); | ||
| + | if (in_array('Content-Encoding: gzip',headers_list())) $content = gzencode($content); | ||
| + | echo($content); | ||
| + | die; | ||
| + | } | ||
| + | } | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | # Return whether or not the passed action is permitted on the passed title | ||
| + | # - uses $this->rules[title] as a cache | ||
| + | function validateTitle($action,$title) { | ||
| + | global $extramsg,$wgUser,$wgSecurityMagic,$wgSecurityMagicNoi,$wgSecurityEnableInheritance; | ||
| + | $key = $title->getPrefixedURL(); | ||
| + | if (isset($this->rules[$key])) $rules = $this->rules[$key]; | ||
| + | else { | ||
| + | # Disable the AfterDatabaseFetch hook to avoid an infinite loop and get the article text | ||
| + | $this->activeHooks['ArticleAfterFetchContent'] = false; | ||
| + | $this->activeHooks['ParserBeforeStrip'] = false; | ||
| + | $article = new Article($title); | ||
| + | $text = $article->getContent(); | ||
| + | |||
| + | # Set up a new local parser object to process security directives independently of main rendering process | ||
| + | $psr = new Parser; | ||
| + | $psr->setFunctionHook($wgSecurityMagic,array($this,'processDirective')); | ||
| + | if ($wgSecurityEnableInheritance) $psr->setFunctionHook($wgSecurityMagicNoi,array($this,'processDirectiveNoi')); | ||
| + | $opt = ParserOptions::newFromUser($wgUser); | ||
| + | $this->directives = array(); | ||
| + | $out = $psr->parse($text,$title,$opt,false,true); | ||
| + | $rules = $this->directives; | ||
| + | |||
| + | # Get the security items from the cats by running the parser over the content of each | ||
| + | # - stop checking MagicNoi directives because they shouldn't inherit | ||
| + | if ($wgSecurityEnableInheritance) { | ||
| + | unset($psr->mFunctionHooks[$wgSecurityMagicNoi]); | ||
| + | foreach ($out->getCategoryLinks() as $cat) { | ||
| + | $ca = new Article($ct = Title::newFromText($cat = "Category:$cat")); | ||
| + | $this->directives = array(); | ||
| + | $psr->parse($ca->getContent(),$ct,$opt,false,true); | ||
| + | foreach ($this->directives as $i) $rules[] = array($i[0],$i[1],"this rule is inherited from [[:$cat]]"); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | # Re-enable AfterFetch hook and cache the security info | ||
| + | $this->activeHooks['ArticleAfterFetchContent'] = true; | ||
| + | $this->activeHooks['ParserBeforeStrip'] = true; | ||
| + | $this->rules[$key] = $rules; | ||
| + | } | ||
| + | |||
| + | # Return the result of validating the extracted rules | ||
| + | return $this->validateRules($action,$rules); | ||
| + | } | ||
| + | |||
| + | # Return whether or not a user is allowed to perform an action according to an array of security items | ||
| + | function validateRules($action,&$rules) { | ||
| + | global $wgUser,$wgSecuritySysops; | ||
| + | if (!is_array($rules)) return true; | ||
| + | |||
| + | # Resolve permission for this action from the extracted security links | ||
| + | $security = ''; | ||
| + | foreach ($rules as $i) { | ||
| + | #if ($i[1] == '') $i[1] = join(',',$wgSecuritySysops); | ||
| + | $actions = preg_split("/\\s*,\\s*/",strtolower($i[0])); | ||
| + | if (in_array($action,$actions) or (in_array('*',$actions) and $security == '')) $security = $i[1]; | ||
| + | } | ||
| + | |||
| + | # Get users group lists (add own username to groups) | ||
| + | $groups = array_map('strtolower',$wgUser->getEffectiveGroups()); | ||
| + | $groups[] = ucfirst($wgUser->mName); | ||
| + | $security = $security ? preg_split("/\\s*,\\s*/",$security) : array(); | ||
| + | |||
| + | # Calculate whether or not the action can be performed and return the result | ||
| + | return ( | ||
| + | count($security) == 0 | ||
| + | or in_array('*',$security) | ||
| + | or count(array_intersect($groups,$wgSecuritySysops)) > 0 | ||
| + | or count(array_intersect($groups,$security)) > 0 | ||
| + | ); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | |||
| + | # Called from $wgExtensionFunctions array when initialising extensions | ||
| + | function wfSetupSimpleSecurity() { | ||
| + | global $wgSimpleSecurity,$wgLanguageCode,$wgMessageCache; | ||
| + | |||
| + | $wgSimpleSecurity = new SimpleSecurity(); | ||
| + | |||
| + | # Add the messages used by the specialpage | ||
| + | if ($wgLanguageCode == 'en') { | ||
| + | $wgMessageCache->addMessages(array( | ||
| + | 'security' => "Security log", | ||
| + | 'securitylogpage' => "Security log", | ||
| + | 'securitylogpagetext' => "This is a log of actions blocked by the [[MW:Extension:SimpleSecurity|SimpleSecurity extension]].", | ||
| + | 'securitylogdeny' => "Attempt to '''$1''' [[$2]] was denied.", | ||
| + | 'securitylogentry' => "" | ||
| + | )); | ||
| + | } | ||
| + | |||
| + | } | ||
| + | |||
| + | # Needed in MediaWiki >1.8.0 for magic word hooks to work properly | ||
| + | function wfSimpleSecurityLanguageGetMagic(&$magicWords,$langCode = 0) { | ||
| + | global $wgSecurityMagic,$wgSecurityMagicNoi,$wgSecurityMagicIf,$wgSecurityMagicGroup,$wgSecurityEnableInheritance; | ||
| + | $magicWords[$wgSecurityMagic] = array(0,$wgSecurityMagic); | ||
| + | if ($wgSecurityEnableInheritance) $magicWords[$wgSecurityMagicNoi] = array(0,$wgSecurityMagicNoi); | ||
| + | $magicWords[$wgSecurityMagicIf] = array(0,$wgSecurityMagicIf); | ||
| + | $magicWords[$wgSecurityMagicGroup] = array(0,$wgSecurityMagicGroup); | ||
| + | return true; | ||
| + | } | ||
| + | |||
| + | ?> | ||
Revision as of 02:21, 11 October 2007
<?php
- Simple security extensionTemplate:Php
- - See http://www.mediawiki.org/Extension:Simple_Security for installation and usage details
- - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
- - Needs apache's mod-rewrite for security on images, see code comments below
- - Version 4.0.0 started 2007-10-11
if (!defined('MEDIAWIKI')) die('Not an entry point.');
define('SIMPLESECURITY_VERSION','4.0.0, 2007-10-11');
- Global security settings
$wgSecurityMagic = "security"; # the parser-function name for security directives $wgSecurityMagicNoi = "!security"; # the name for non-inheriting security directives $wgSecurityMagicIf = "ifusercan"; # the name for doing a permission-based conditional $wgSecurityMagicGroup = "ifgroup"; # the name for doing a group-based conditional $wgSecurityEnableInheritance = false; # specifies whether or not security directives in categories inherit to member articles $wgSecurityEnableForImages = false; # specifies whether security directives in image/file articles also apply to the associated binary $wgSecuritySysops = array('sysop'); # the list of groups whose members bypass all security (groups a all lowercase, user are ucfirst) $wgSecurityDenyTemplate = 'Template:Action not permitted'; $wgSecurityInfoTemplate = 'Template:Security info'; $wgSecurityRuleTemplate = ; # set to a template for the $wgSecurityDenyImage = "$IP/skins/common/images/mediawiki.png"; # the image returned in place of requested image if read access denied $wgSecurityParseInfo = isset($wgSecurityParseInfo) ? $wgSecurityParseInfo : false; $wgSecurityGroupsArticle = ; # Name of an article which contains a bullet list of available groups for Special:Userrights $wgSecurityLogActions = array('download'); # Actions that should be logged
$wgExtensionFunctions[] = 'wfSetupSimpleSecurity'; $wgHooks['LanguageGetMagic'][] = 'wfSimpleSecurityLanguageGetMagic';
$wgExtensionCredits['parserhook'][] = array( 'name' => "Simple Security", 'author' => 'User:Nad', 'description' => 'A simple to implement security extension', 'url' => 'http://www.mediawiki.org/wiki/Extension:Simple_Security', 'version' => SIMPLESECURITY_VERSION );
class SimpleSecurity {
# Private internal data var $initialised; var $rules; var $directives; var $activeHooks; var $title; var $action; var $allowed; var $file; var $path;
# Needed in some versions to prevent Special:Version from breaking function __toString() { return 'SimpleSecurity'; }
# Constructor function SimpleSecurity($inheritance = true) { global $wgParser,$wgHooks,$wgLogTypes,$wgLogNames,$wgLogHeaders,$wgLogActions, $wgSecurityMagic,$wgSecurityMagicNoi,$wgSecurityMagicIf,$wgSecurityMagicGroup, $wgSecurityEnableInheritance,$wgGroupPermissions,$wgSecurityGroupsArticle;
# Add all our required event hooks $wgHooks['userCan'][] = $this; $wgHooks['ArticleAfterFetchContent'][] = $this; $wgHooks['OutputPageBeforeHTML'][] = $this;
$wgParser->setFunctionHook($wgSecurityMagic,array($this,'processDirective')); if ($wgSecurityEnableInheritance) $wgParser->setFunctionHook($wgSecurityMagicNoi,array($this,'processDirectiveNoi')); $wgParser->setFunctionHook($wgSecurityMagicIf,array($this,'ifUserCan')); $wgParser->setFunctionHook($wgSecurityMagicGroup,array($this,'ifGroup'));
# Specify which of the hooks are currently active because these apply to all parser objects $this->activeHooks = array( 'ArticleAfterFetchContent' => false, 'ParserBeforeStrip' => false );
# Initialise internal data $this->initialised = false; $this->allowed = true; $this->directives = array(); $this->rules = array(); $this->file = ; $this->path = ;
# Add extra available groups if $wgSecurityGroupsArticle is set if ($wgSecurityGroupsArticle) { $groups = new Article(Title::newFromText($wgSecurityGroupsArticle)); if (preg_match_all('/^\\*\\s*(.+?)\\s*$/m',$groups->getContent(),$match)) foreach($match[1] as $group) $wgGroupPermissions[$group] = array(); }
# Add a new log type $wgLogTypes[] = 'security'; $wgLogNames ['security'] = 'securitylogpage'; $wgLogHeaders['security'] = 'securitylogpagetext'; $wgLogActions['security/deny'] = 'securitylogentry'; }
# Process a normal security directive function processDirective(&$parser,$actions = ,$groups = ) { $parser->mOutput->mCacheTime = -1; if ($actions && $groups) $this->directives[] = array($actions,$groups); return ; }
# Process a non-inheriting security directive function processDirectiveNoi(&$parser,$actions = ,$groups = ) { $parser->mOutput->mCacheTime = -1; if ($actions && $groups) $this->directives[] = array($actions,$groups); return ; }
# Process the ifUserCan conditional security directive function ifUserCan(&$parser,$action,$title,$if,$else = ) { return $this->validateTitle($action,Title::newFromText($title)) ? $if : $else; }
# Process the ifGroup conditional security directive # - evaluates to true if current uset belongs to any of the groups in $groups (csv) function ifGroup(&$parser,$groups,$if,$else = ) { global $wgUser; $intersection = array_intersect( array_map('strtolower',split(',',$groups)), array_map('strtolower',$wgUser->getEffectiveGroups()) ); return count($intersection) > 0 ? $if : $else; }
# ArticleAfterFetchContent hook: Asseses security after raw content fetched from database and clears if not readable # - also fills the global $securityCache cache with info to append to the rendered article function onArticleAfterFetchContent(&$article,&$text) { global $wgSecurityDenyTemplate; $title = $article->mTitle->getText(); if (!$this->activeHooks['ArticleAfterFetchContent']) return true; if (!$this->validateTitle('view',$article->mTitle)) $text = '{'.'{'."$wgSecurityDenyTemplate|fetch|$title}}"; return true; }
# Render any security info function onOutputPageBeforeHTML(&$out,&$text) { global $wgUser,$wgTitle,$wgSecurityInfoTemplate,$wgSecurityDenyTemplate, $wgSiteNotice,$wgSecurityParseInfo,$wgSecurityRuleTemplate,$wgSecurityLogActions; static $done = 0; if ($done++) return true; $psr = new Parser; $psropt = ParserOptions::newFromUser($wgUser);
# Add rules information for this article if any $rules = $this->rules[$this->title]; if (count($rules)) {
# Construct wikitext for info $info = '{'.'{'."$wgSecurityInfoTemplate|1=\n"; foreach ($rules as $rule) { $a = $rule[0] == '*' ? 'Every action' : ucfirst($rule[0]); $b = $rule[1] == '*' ? 'anybody' : ($rule[1] == 'user' ? 'logged in' : $rule[1]); $c = isset($rule[2]) ? $rule[2] : ; if ($wgSecurityRuleTemplate) $info .= '{'.'{'."$wgSecurityRuleTemplate|$a|$b|$c}}\n"; else $info .= "*$a requires the user to be $b $c\n"; } $info .= "\n}}";
# Parse the wikitext (depending on $wgSecurityParseInfo) and add to SiteNotice
if ($wgSecurityParseInfo) {
$psrout = $psr->parse($info,$wgTitle,$psropt,true,true);
$info = $psrout->getText();
}
$wgSiteNotice .= $info; }
# Replace main body with deny-message and append log if action allowed if (!$this->allowed) { $text = ; $psrout = $psr->parse('{'.'{'."$wgSecurityDenyTemplate|{$this->action}|{$this->title}}}",$wgTitle,$psropt,true,true); $out->mBodytext = $psrout->getText(); if (in_array($this->action,$wgSecurityLogActions)) { $msg = wfMsgForContent('securitylogdeny',$this->action,$this->title); $log = new LogPage('security',false); $log->addEntry('deny',$wgUser->getUserPage(),$msg); } }
return true; }
# Main validation code for the whole request is done on the first call to userCan function onuserCan(&$title, &$user, $ucaction, &$result) { global $wgTitle,$action,$wgRequest,$wgUser,$wgUploadDirectory, $wgSecurityEnableForImages,$wgSecurityDenyImage,$wgSecurityLogActions;
# Add IsAllowed hook to the $wgUser object static $done = 0; if ($done++ == 0) {
# Create a new User class ($User2) by extending the existing one with an overridden isAllowed method $User = get_class($wgUser); $User2 = $User.'2'; eval("class $User2 extends $User".' { function isAllowed($action = "") { $result = NULL; wfRunHooks("IsAllowed",array(&$this,$action,&$result)); return $result === NULL ? $result = parent::isAllowed($action) : $result; } }');
# Replace the $wgUser object with an identical $User2 instance $oldUser = $wgUser; $wgUser = new $User2(); foreach(array_keys(get_class_vars($User)) as $k) $wgUser->$k = $oldUser->$k; }
$this->title = $wgTitle->getPrefixedURL(); $this->action = $action == 'submit' ? 'edit' : strtolower($action);
# Moves need to be handled differently if ($this->title == 'Special:Movepage' && $this->action == 'submit') { $this->action = 'move'; $this->title = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target')); }
# Handle security on files (needs apache mod-rewrite) # - /wiki/images/... rewritten to article title Download/image-path... if (ereg('^Download/(.+)/([^/]+)$',$this->title,$match)) { $this->path = $match[1]; $this->file = $image = $match[2]; if (ereg('^thumb/.+/([^/]+)$',$this->path,$match)) $image = $match[1]; $wgTitle = Title::newFromText($this->title = "Image:$image"); $action = 'raw'; }
if ($this->initialised) { # we can call $this->validate in here so mediawiki can render links according to permissions } elseif (!empty($wgUser->mDataLoaded)) {
# Activate the hooks now that enough information is present to assess security $this->initialised = true; $this->activeHooks['ArticleAfterFetchContent'] = true;
# Validate current global action and set to view if not allowed if (!$this->allowed = $this->validateTitle($this->action,$wgTitle)) { $action = 'view'; global $wgEnableParserCache,$wgOut; $wgEnableParserCache = false; $wgOut->enableClientCache(false); }
# If the requested item is a file, return it to the client if security validates otherwise return the SecurityDenyImage if ($wgSecurityEnableForImages && $this->file) { if (in_array('download',$wgSecurityLogActions)) { $msg = wfMsgForContent('securitylogdeny','download',$this->title); $log = new LogPage('security',false); $log->addEntry('deny',$wgUser->getUserPage(),$msg); } $pathname = $this->allowed ? "$wgUploadDirectory/".$this->path.'/'.$this->file : $wgSecurityDenyImage; while (@ob_end_clean()); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.$this->file.'"'); $content = implode(,file($pathname)); if (in_array('Content-Encoding: gzip',headers_list())) $content = gzencode($content); echo($content); die; } } return true; }
# Return whether or not the passed action is permitted on the passed title # - uses $this->rules[title] as a cache function validateTitle($action,$title) { global $extramsg,$wgUser,$wgSecurityMagic,$wgSecurityMagicNoi,$wgSecurityEnableInheritance; $key = $title->getPrefixedURL(); if (isset($this->rules[$key])) $rules = $this->rules[$key]; else { # Disable the AfterDatabaseFetch hook to avoid an infinite loop and get the article text $this->activeHooks['ArticleAfterFetchContent'] = false; $this->activeHooks['ParserBeforeStrip'] = false; $article = new Article($title); $text = $article->getContent();
# Set up a new local parser object to process security directives independently of main rendering process $psr = new Parser; $psr->setFunctionHook($wgSecurityMagic,array($this,'processDirective')); if ($wgSecurityEnableInheritance) $psr->setFunctionHook($wgSecurityMagicNoi,array($this,'processDirectiveNoi')); $opt = ParserOptions::newFromUser($wgUser); $this->directives = array(); $out = $psr->parse($text,$title,$opt,false,true); $rules = $this->directives;
# Get the security items from the cats by running the parser over the content of each # - stop checking MagicNoi directives because they shouldn't inherit if ($wgSecurityEnableInheritance) { unset($psr->mFunctionHooks[$wgSecurityMagicNoi]); foreach ($out->getCategoryLinks() as $cat) { $ca = new Article($ct = Title::newFromText($cat = "Category:$cat")); $this->directives = array(); $psr->parse($ca->getContent(),$ct,$opt,false,true); foreach ($this->directives as $i) $rules[] = array($i[0],$i[1],"this rule is inherited from $cat"); } }
# Re-enable AfterFetch hook and cache the security info $this->activeHooks['ArticleAfterFetchContent'] = true; $this->activeHooks['ParserBeforeStrip'] = true; $this->rules[$key] = $rules; }
# Return the result of validating the extracted rules return $this->validateRules($action,$rules); }
# Return whether or not a user is allowed to perform an action according to an array of security items function validateRules($action,&$rules) { global $wgUser,$wgSecuritySysops; if (!is_array($rules)) return true;
# Resolve permission for this action from the extracted security links $security = ; foreach ($rules as $i) { #if ($i[1] == ) $i[1] = join(',',$wgSecuritySysops); $actions = preg_split("/\\s*,\\s*/",strtolower($i[0])); if (in_array($action,$actions) or (in_array('*',$actions) and $security == )) $security = $i[1]; }
# Get users group lists (add own username to groups) $groups = array_map('strtolower',$wgUser->getEffectiveGroups()); $groups[] = ucfirst($wgUser->mName); $security = $security ? preg_split("/\\s*,\\s*/",$security) : array();
# Calculate whether or not the action can be performed and return the result return ( count($security) == 0 or in_array('*',$security) or count(array_intersect($groups,$wgSecuritySysops)) > 0 or count(array_intersect($groups,$security)) > 0 ); } }
- Called from $wgExtensionFunctions array when initialising extensions
function wfSetupSimpleSecurity() { global $wgSimpleSecurity,$wgLanguageCode,$wgMessageCache;
$wgSimpleSecurity = new SimpleSecurity();
# Add the messages used by the specialpage if ($wgLanguageCode == 'en') { $wgMessageCache->addMessages(array( 'security' => "Security log", 'securitylogpage' => "Security log", 'securitylogpagetext' => "This is a log of actions blocked by the SimpleSecurity extension.", 'securitylogdeny' => "Attempt to $1 $2 was denied.", 'securitylogentry' => "" )); }
}
- Needed in MediaWiki >1.8.0 for magic word hooks to work properly
function wfSimpleSecurityLanguageGetMagic(&$magicWords,$langCode = 0) { global $wgSecurityMagic,$wgSecurityMagicNoi,$wgSecurityMagicIf,$wgSecurityMagicGroup,$wgSecurityEnableInheritance; $magicWords[$wgSecurityMagic] = array(0,$wgSecurityMagic); if ($wgSecurityEnableInheritance) $magicWords[$wgSecurityMagicNoi] = array(0,$wgSecurityMagicNoi); $magicWords[$wgSecurityMagicIf] = array(0,$wgSecurityMagicIf); $magicWords[$wgSecurityMagicGroup] = array(0,$wgSecurityMagicGroup); return true; }
?>



