Difference between revisions of "Extension:SimpleSecurity"
(Move directive processing into validateTitle() and call from onGetUserRights()) |
(Slim down to new road map (may need some code from history again later tho)) |
||
| Line 1: | Line 1: | ||
<?php | <?php | ||
| + | /** | ||
| + | * Simple Security extension | ||
| + | * - Extends the MediaWiki article protection to allow restricting viewing of article content | ||
| + | * - Also adds #ifusercan and #ifgroup parser functions for rendering restriction-based content | ||
| + | * | ||
| + | * See http://www.mediawiki.org/Extension:Simple_Security for installation and usage details | ||
| + | * See http://www.organicdesign.co.nz/Extension_talk:SimpleSecurity4.php for development notes and disucssion | ||
| + | * Version 4.0.0 started 2007-10-11 | ||
| + | * Version 4.1.0 started 2008-06-12 (development funded for a slimmed down functional version) | ||
| + | * | ||
| + | * @package MediaWiki | ||
| + | * @subpackage Extensions | ||
| + | * @author Aran Dunkley [http://www.organicdesign.co.nz/nad User:Nad] | ||
| + | * @copyright © 2007 Aran Dunkley | ||
| + | * @licence GNU General Public Licence 2.0 or later | ||
| + | */ | ||
# Simple security extension{{php}}{{Category:Extensions|SimpleSecurity4}}{{Category:Extensions in progress|SimpleSecurity4}} | # Simple security extension{{php}}{{Category:Extensions|SimpleSecurity4}}{{Category:Extensions in progress|SimpleSecurity4}} | ||
# - See http://www.mediawiki.org/Extension:Simple_Security for installation and usage details | # - See http://www.mediawiki.org/Extension:Simple_Security for installation and usage details | ||
| Line 5: | Line 21: | ||
# - Needs apache's mod-rewrite for security on images, see code comments below | # - Needs apache's mod-rewrite for security on images, see code comments below | ||
# - Version 4.0.0 started 2007-10-11 | # - Version 4.0.0 started 2007-10-11 | ||
| + | # | ||
if (!defined('MEDIAWIKI')) die('Not an entry point.'); | if (!defined('MEDIAWIKI')) die('Not an entry point.'); | ||
| − | define('SIMPLESECURITY_VERSION','4.0 | + | define('SIMPLESECURITY_VERSION','4.1.0, 2008-06-12'); |
# Global security settings | # Global security settings | ||
| − | |||
| − | |||
$wgSecurityMagicIf = "ifusercan"; # the name for doing a permission-based conditional | $wgSecurityMagicIf = "ifusercan"; # the name for doing a permission-based conditional | ||
$wgSecurityMagicGroup = "ifgroup"; # the name for doing a group-based conditional | $wgSecurityMagicGroup = "ifgroup"; # the name for doing a group-based conditional | ||
| − | + | $wgSecurityLogActions = array('edit', 'download'); # Actions that should be logged | |
| − | + | $wgSecurityUseDBHook = false; # Add the DatabaseFetchHook to validate database access | |
| − | $wgSecurityLogActions = array('download'); | + | $wgSecurityAllowUser = false; # Allow restrictions based on user not just group |
| − | $ | + | |
| − | $ | + | |
| + | array_unshift($wgExtensionFunctions, 'wfSetupSimpleSecurity'); # Put SimpleSecurity's setup function before all others | ||
| − | |||
$wgHooks['LanguageGetMagic'][] = 'wfSimpleSecurityLanguageGetMagic'; | $wgHooks['LanguageGetMagic'][] = 'wfSimpleSecurityLanguageGetMagic'; | ||
$wgExtensionCredits['parserhook'][] = array( | $wgExtensionCredits['parserhook'][] = array( | ||
'name' => "Simple Security", | 'name' => "Simple Security", | ||
'author' => '[http://www.organicdesign.co.nz/User:Nad User:Nad]', | 'author' => '[http://www.organicdesign.co.nz/User:Nad User:Nad]', | ||
| − | 'description' => ' | + | 'description' => 'Extends the MediaWiki article protection to allow restricting viewing of article content', |
'url' => 'http://www.mediawiki.org/wiki/Extension:Simple_Security', | 'url' => 'http://www.mediawiki.org/wiki/Extension:Simple_Security', | ||
'version' => SIMPLESECURITY_VERSION | 'version' => SIMPLESECURITY_VERSION | ||
| Line 33: | Line 48: | ||
class SimpleSecurity { | class SimpleSecurity { | ||
| − | + | /** | |
| − | + | * Constructor | |
| − | + | */ | |
| − | |||
| − | |||
| − | |||
function __construct() { | function __construct() { | ||
| − | global $wgParser,$wgHooks,$wgLogTypes,$wgLogNames,$wgLogHeaders,$wgLogActions, | + | global $wgParser, $wgHooks, $wgLogTypes, $wgLogNames, $wgLogHeaders, $wgLogActions, |
| − | + | $wgSecurityMagicIf, $wgSecurityMagicGroup; | |
# Add our parser-hooks | # Add our parser-hooks | ||
| − | + | $wgParser->setFunctionHook($wgSecurityMagicIf, array($this,'ifUserCan')); | |
| − | + | $wgParser->setFunctionHook($wgSecurityMagicGroup, array($this,'ifGroup')); | |
| − | $wgParser->setFunctionHook($wgSecurityMagicIf,array($this,'ifUserCan')); | ||
| − | $wgParser->setFunctionHook($wgSecurityMagicGroup,array($this,'ifGroup')) | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
# Add a new log type | # Add a new log type | ||
| Line 59: | Line 64: | ||
$wgLogHeaders['security'] = 'securitylogpagetext'; | $wgLogHeaders['security'] = 'securitylogpagetext'; | ||
$wgLogActions['security/deny'] = 'securitylogentry'; | $wgLogActions['security/deny'] = 'securitylogentry'; | ||
| − | + | } | |
| − | + | /** | |
| − | + | * Process the ifUserCan conditional security directive | |
| − | + | */ | |
| − | + | public function ifUserCan(&$parser, $action, $title, $then, $else = '') { | |
| − | + | return $this->validateTitle($action, $title) ? $then : $else; | |
| − | + | } | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | public function ifUserCan(&$parser,$action,$title,$then,$else = '') { | ||
| − | return $this->validateTitle($action,$title) ? $then : $else; | ||
| − | |||
| − | + | /** | |
| − | + | * Process the ifGroup conditional security directive | |
| − | public function ifGroup(&$parser,$groups,$then,$else = '') { | + | * - evaluates to true if current uset belongs to any of the comma-separated users and/or groups in the first parameter |
| + | */ | ||
| + | public function ifGroup(&$parser, $groups, $then, $else = '') { | ||
global $wgUser; | global $wgUser; | ||
| − | $intersection = array_intersect(array_map('strtolower',split(',',$groups)),$wgUser->getEffectiveGroups()); | + | $intersection = array_intersect(array_map('strtolower', split(',', $groups)), $wgUser->getEffectiveGroups()); |
return count($intersection) > 0 ? $then : $else; | return count($intersection) > 0 ? $then : $else; | ||
| − | + | } | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | /** | |
| − | + | * Validate the passed database row and replace any invalid content | |
| − | + | * - this does not get called if already processing directives | |
| − | + | */ | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
private function validateDatabaseRow(&$row) { | private function validateDatabaseRow(&$row) { | ||
| − | + | # TODO | |
| − | |||
| − | # | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
} | } | ||
| − | + | /** | |
| − | + | * Needed in some versions to prevent Special:Version from breaking | |
| − | + | */ | |
| − | + | public function __toString() { | |
| − | + | return __CLASS__; | |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
} | } | ||
| + | } | ||
| − | + | /** | |
| + | * Hooks into Database::query and Database::fetchObject via the LoadBalancer class | ||
| + | */ | ||
function wfSimpleSecurityAddDatabaseHooks() { | function wfSimpleSecurityAddDatabaseHooks() { | ||
global $wgLoadBalancer,$wgDBtype; | global $wgLoadBalancer,$wgDBtype; | ||
| Line 308: | Line 116: | ||
eval("class Database{$type}2 extends Database{$type}".' { | eval("class Database{$type}2 extends Database{$type}".' { | ||
| − | public function query($sql,$fname = "",$tempIgnore = false) { | + | public function query($sql, $fname = "", $tempIgnore = false) { |
global $wgSimpleSecurity; | global $wgSimpleSecurity; | ||
$count = false; | $count = false; | ||
if (!$wgSimpleSecurity->processDirectives) | if (!$wgSimpleSecurity->processDirectives) | ||
| − | $patched = preg_replace_callback("/(?<=SELECT ).+?(?= FROM)/","wfSimpleSecurityPatchSQL",$sql,1,$count); | + | $patched = preg_replace_callback("/(?<=SELECT ).+?(?= FROM)/", "wfSimpleSecurityPatchSQL", $sql, 1, $count); |
return parent::query($count ? $patched : $sql,$fname,$tempIgnore); | return parent::query($count ? $patched : $sql,$fname,$tempIgnore); | ||
} | } | ||
| Line 331: | Line 139: | ||
$db =& parent::reallyOpenConnection($server); | $db =& parent::reallyOpenConnection($server); | ||
return $db; | return $db; | ||
| − | |||
} | } | ||
| + | } | ||
# Replace the $wgLoadBalancer object with an identical instance of the new LoadBalancer2 class | # Replace the $wgLoadBalancer object with an identical instance of the new LoadBalancer2 class | ||
| Line 340: | Line 148: | ||
foreach (array_keys(get_class_vars('LoadBalancer')) as $k) $wgLoadBalancer->$k = $oldLoadBalancer->$k; | foreach (array_keys(get_class_vars('LoadBalancer')) as $k) $wgLoadBalancer->$k = $oldLoadBalancer->$k; | ||
| − | + | } | |
| − | + | /** | |
| + | * Patches SQL queries to ensure that the old_id field is present in all requests for the old_text field so permissions can be validated | ||
| + | */ | ||
function wfSimpleSecurityPatchSQL($match) { | function wfSimpleSecurityPatchSQL($match) { | ||
| − | if (!preg_match("/old_text/",$match[0])) return $match[0]; | + | if (!preg_match("/old_text/", $match[0])) return $match[0]; |
| − | $fields = str_replace(" ","",$match[0]); | + | $fields = str_replace(" ", "", $match[0]); |
| − | return ($fields == "*" || preg_match("/old_id/",$fields)) ? $fields : "$fields,old_id"; | + | return ($fields == "*" || preg_match("/old_id/", $fields)) ? $fields : "$fields,old_id"; |
} | } | ||
| − | + | /** | |
| + | * Called from $wgExtensionFunctions array when initialising extensions | ||
| + | */ | ||
function wfSetupSimpleSecurity() { | function wfSetupSimpleSecurity() { | ||
| − | global $wgSimpleSecurity,$wgLanguageCode,$wgMessageCache,$wgSecurityMagic,$wgSecurityUseDBHook; | + | global $wgSimpleSecurity, $wgLanguageCode, $wgMessageCache, $wgSecurityMagic, $wgSecurityUseDBHook; |
# Hooks into Database::query and Database::fetchObject via the LoadBalancer class | # Hooks into Database::query and Database::fetchObject via the LoadBalancer class | ||
if ($wgSecurityUseDBHook) wfSimpleSecurityAddDatabaseHooks(); | if ($wgSecurityUseDBHook) wfSimpleSecurityAddDatabaseHooks(); | ||
| − | |||
| − | |||
| − | |||
# Instantiate the SimpleSecurity singleton now that the environment is prepared | # Instantiate the SimpleSecurity singleton now that the environment is prepared | ||
| Line 377: | Line 186: | ||
} | } | ||
| − | + | /** | |
| − | function wfSimpleSecurityLanguageGetMagic(&$magicWords,$langCode = 0) { | + | * Register magic words |
| − | global | + | */ |
| − | $magicWords[$ | + | function wfSimpleSecurityLanguageGetMagic(&$magicWords, $langCode = 0) { |
| − | + | global $wgSecurityMagicIf,$wgSecurityMagicGroup; | |
| − | + | $magicWords[$wgSecurityMagicIf] = array($langCode, $wgSecurityMagicIf); | |
| − | $magicWords[$wgSecurityMagicGroup] | + | $magicWords[$wgSecurityMagicGroup] = array($langCode, $wgSecurityMagicGroup); |
return true; | return true; | ||
| − | + | } | |
Revision as of 07:04, 12 June 2008
<?php /**
* Simple Security extension * - Extends the MediaWiki article protection to allow restricting viewing of article content * - Also adds #ifusercan and #ifgroup parser functions for rendering restriction-based content * * See http://www.mediawiki.org/Extension:Simple_Security for installation and usage details * See http://www.organicdesign.co.nz/Extension_talk:SimpleSecurity4.php for development notes and disucssion * Version 4.0.0 started 2007-10-11 * Version 4.1.0 started 2008-06-12 (development funded for a slimmed down functional version) * * @package MediaWiki * @subpackage Extensions * @author Aran Dunkley User:Nad * @copyright © 2007 Aran Dunkley * @licence GNU General Public Licence 2.0 or later */
- Simple security extensionTemplate:Php
Category:Extensions in progress
- - 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.1.0, 2008-06-12');
- Global security settings
$wgSecurityMagicIf = "ifusercan"; # the name for doing a permission-based conditional $wgSecurityMagicGroup = "ifgroup"; # the name for doing a group-based conditional $wgSecurityLogActions = array('edit', 'download'); # Actions that should be logged $wgSecurityUseDBHook = false; # Add the DatabaseFetchHook to validate database access $wgSecurityAllowUser = false; # Allow restrictions based on user not just group
array_unshift($wgExtensionFunctions, 'wfSetupSimpleSecurity'); # Put SimpleSecurity's setup function before all others
$wgHooks['LanguageGetMagic'][] = 'wfSimpleSecurityLanguageGetMagic'; $wgExtensionCredits['parserhook'][] = array( 'name' => "Simple Security", 'author' => 'User:Nad', 'description' => 'Extends the MediaWiki article protection to allow restricting viewing of article content', 'url' => 'http://www.mediawiki.org/wiki/Extension:Simple_Security', 'version' => SIMPLESECURITY_VERSION );
class SimpleSecurity {
/** * Constructor */ function __construct() { global $wgParser, $wgHooks, $wgLogTypes, $wgLogNames, $wgLogHeaders, $wgLogActions, $wgSecurityMagicIf, $wgSecurityMagicGroup;
# Add our parser-hooks $wgParser->setFunctionHook($wgSecurityMagicIf, array($this,'ifUserCan')); $wgParser->setFunctionHook($wgSecurityMagicGroup, array($this,'ifGroup'));
# Add a new log type $wgLogTypes[] = 'security'; $wgLogNames ['security'] = 'securitylogpage'; $wgLogHeaders['security'] = 'securitylogpagetext'; $wgLogActions['security/deny'] = 'securitylogentry'; }
/** * Process the ifUserCan conditional security directive */ public function ifUserCan(&$parser, $action, $title, $then, $else = ) { return $this->validateTitle($action, $title) ? $then : $else; }
/** * Process the ifGroup conditional security directive * - evaluates to true if current uset belongs to any of the comma-separated users and/or groups in the first parameter */ public function ifGroup(&$parser, $groups, $then, $else = ) { global $wgUser; $intersection = array_intersect(array_map('strtolower', split(',', $groups)), $wgUser->getEffectiveGroups()); return count($intersection) > 0 ? $then : $else; }
/** * Validate the passed database row and replace any invalid content * - this does not get called if already processing directives */ private function validateDatabaseRow(&$row) { # TODO }
/** * Needed in some versions to prevent Special:Version from breaking */ public function __toString() { return __CLASS__; } }
/**
* Hooks into Database::query and Database::fetchObject via the LoadBalancer class */
function wfSimpleSecurityAddDatabaseHooks() { global $wgLoadBalancer,$wgDBtype;
# This ensures that $wgLoadBalancer is not a stub object when we subclass it # todo: this should be able to work in the case of it being a stub object wfGetDB();
# Create a replica of the Database class # - query method is overriden to ensure that old_id field is returned for all queries which read old_text field # - fetchObject method is overridden to validate row content based on old_id # - the changes to this class are only active for SELECT statements and while not processing security directives $type = ucfirst($wgDBtype); eval("class Database{$type}2 extends Database{$type}".' {
public function query($sql, $fname = "", $tempIgnore = false) { global $wgSimpleSecurity; $count = false; if (!$wgSimpleSecurity->processDirectives) $patched = preg_replace_callback("/(?<=SELECT ).+?(?= FROM)/", "wfSimpleSecurityPatchSQL", $sql, 1, $count); return parent::query($count ? $patched : $sql,$fname,$tempIgnore); }
function fetchObject(&$res) { global $wgSimpleSecurity; $row = parent::fetchObject($res); if (!$wgSimpleSecurity->processDirectives) $wgSimpleSecurity->validateDatabaseRow($row); return $row; }
}');
# Create a replica of the LoadBalancer class which uses the new Database subclass for its connection objects class LoadBalancer2 extends LoadBalancer { function reallyOpenConnection(&$server) { $server['type'] .= '2'; $db =& parent::reallyOpenConnection($server); return $db; } }
# Replace the $wgLoadBalancer object with an identical instance of the new LoadBalancer2 class $wgLoadBalancer->closeAll(); # Close any open connections as they will be of the original Database class $oldLoadBalancer = $wgLoadBalancer; $wgLoadBalancer = new LoadBalancer2($oldLoadBalancer->mServers); foreach (array_keys(get_class_vars('LoadBalancer')) as $k) $wgLoadBalancer->$k = $oldLoadBalancer->$k;
}
/**
* Patches SQL queries to ensure that the old_id field is present in all requests for the old_text field so permissions can be validated */
function wfSimpleSecurityPatchSQL($match) { if (!preg_match("/old_text/", $match[0])) return $match[0]; $fields = str_replace(" ", "", $match[0]); return ($fields == "*" || preg_match("/old_id/", $fields)) ? $fields : "$fields,old_id"; }
/**
* Called from $wgExtensionFunctions array when initialising extensions */
function wfSetupSimpleSecurity() { global $wgSimpleSecurity, $wgLanguageCode, $wgMessageCache, $wgSecurityMagic, $wgSecurityUseDBHook;
# Hooks into Database::query and Database::fetchObject via the LoadBalancer class if ($wgSecurityUseDBHook) wfSimpleSecurityAddDatabaseHooks();
# Instantiate the SimpleSecurity singleton now that the environment is prepared $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.", 'securitylogentry' => "", 'securityinfotoggle' => "This article exhibits security restrictions. Click this icon for more detail.", 'securityreaddeny' => "!!Permission denied!!", 'securitysyntaxerror' => "{{#$wgSecurityMagic:Error: Both action and group must be specified!}}", 'securitylogdeny' => "Attempt to $1 $2 was denied." )); } }
/**
* Register magic words */
function wfSimpleSecurityLanguageGetMagic(&$magicWords, $langCode = 0) { global $wgSecurityMagicIf,$wgSecurityMagicGroup; $magicWords[$wgSecurityMagicIf] = array($langCode, $wgSecurityMagicIf); $magicWords[$wgSecurityMagicGroup] = array($langCode, $wgSecurityMagicGroup); return true; }



