Difference between revisions of "Extension:SimpleSecurity"

From Organic Design wiki
(Fetch hook working properly)
Line 19: Line 19:
 
if (!defined('MEDIAWIKI')) die('Not an entry point.');
 
if (!defined('MEDIAWIKI')) die('Not an entry point.');
  
define('SIMPLESECURITY_VERSION', '4.1.0, 2008-06-12');
+
define('SIMPLESECURITY_VERSION', '4.1.1, 2008-07-21');
  
 
# Global security settings
 
# Global security settings
Line 25: Line 25:
 
$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
 
$wgSecurityLogActions    = array('edit', 'download');          # Actions that should be logged
$wgSecurityUseDBHook    = true;                              # Add the DatabaseFetchHook to validate database access
+
$wgSecurityUseDBHook    = true;                              # Use the DatabaseFetchHook to validate database access
 
$wgSecurityAllowUser    = false;                              # Allow restrictions based on user not just group
 
$wgSecurityAllowUser    = false;                              # Allow restrictions based on user not just group
  
Line 34: Line 34:
 
'history' => 'History'
 
'history' => 'History'
 
);
 
);
 +
$wgSecurityExtraActions  = array('read' => 'Read');
  
 
# Extra groups available in protection form
 
# Extra groups available in protection form
Line 42: Line 43:
 
$wgHooks['LanguageGetMagic'][] = 'wfSimpleSecurityLanguageGetMagic';
 
$wgHooks['LanguageGetMagic'][] = 'wfSimpleSecurityLanguageGetMagic';
 
$wgExtensionCredits['parserhook'][] = array(
 
$wgExtensionCredits['parserhook'][] = array(
'name'        => "SimpleSecurity4",
+
'name'        => "SimpleSecurity",
 
'author'      => '[http://www.organicdesign.co.nz/User:Nad User:Nad]',
 
'author'      => '[http://www.organicdesign.co.nz/User:Nad User:Nad]',
 
'description' => 'Extends the MediaWiki article protection to allow restricting viewing of article content',
 
'description' => 'Extends the MediaWiki article protection to allow restricting viewing of article content',
Line 50: Line 51:
  
 
class SimpleSecurity {
 
class SimpleSecurity {
 +
 +
var $enabled = true;
  
 
/**
 
/**
Line 57: Line 60:
 
global $wgParser, $wgHooks, $wgLogTypes, $wgLogNames, $wgLogHeaders, $wgLogActions, $wgMessageCache,
 
global $wgParser, $wgHooks, $wgLogTypes, $wgLogNames, $wgLogHeaders, $wgLogActions, $wgMessageCache,
 
$wgSecurityMagicIf, $wgSecurityMagicGroup, $wgSecurityExtraActions, $wgSecurityExtraGroups,
 
$wgSecurityMagicIf, $wgSecurityMagicGroup, $wgSecurityExtraActions, $wgSecurityExtraGroups,
$wgRestrictionTypes, $wgRestrictionLevels;
+
$wgRestrictionTypes, $wgRestrictionLevels, $wgGroupPermissions;
 +
 
 +
# $wgGroupPermissions has to have its default read entry removed because Title::userCanRead checks it directly
 +
if ($this->default_read = isset($wgGroupPermissions['*']['read']) && $wgGroupPermissions['*']['read'])
 +
$wgGroupPermissions['*']['read'] = false;
  
 
# Add our parser-hooks
 
# Add our parser-hooks
Line 72: Line 79:
 
# Extend protection form groups, actions and messages
 
# Extend protection form groups, actions and messages
 
$wgMessageCache->addMessages(array( 'protect-unchain' => "Modify actions individually" ));
 
$wgMessageCache->addMessages(array( 'protect-unchain' => "Modify actions individually" ));
 +
#$wgMessageCache->addMessages(array('loginreqpagetext' => "Sorry, you'll need to $1 to an account with sufficient permissions to view this page."));
 
foreach ($wgSecurityExtraActions as $k => $v) {
 
foreach ($wgSecurityExtraActions as $k => $v) {
 
if (empty($v)) $v = ucfirst($k);
 
if (empty($v)) $v = ucfirst($k);
 
$wgRestrictionTypes[] = $k;
 
$wgRestrictionTypes[] = $k;
 
$wgMessageCache->addMessages(array( "restriction-$k" => $v ));
 
$wgMessageCache->addMessages(array( "restriction-$k" => $v ));
 +
#$wgGroupPermissions['sysop'][$k] = true; # Ensure sysops have the right to perform this extra action
 
}
 
}
 +
 
foreach ($wgSecurityExtraGroups as $k => $v) {
 
foreach ($wgSecurityExtraGroups as $k => $v) {
 
if (empty($v)) $v = ucfirst($k);
 
if (empty($v)) $v = ucfirst($k);
 
$wgRestrictionLevels[] = $k;
 
$wgRestrictionLevels[] = $k;
 
$wgMessageCache->addMessages(array( "protect-level-$k" => $v ));
 
$wgMessageCache->addMessages(array( "protect-level-$k" => $v ));
 +
$wgGroupPermissions[$k]['not an action'] = true; # Ensure the new groups show up in rights management
 
}
 
}
 
}
 
}
Line 101: Line 112:
 
}
 
}
  
    # User::getRights returns a list of rights (allowed actions) based on the current users group membership
+
/**
    # Title::getRestrictions returns a list of groups who can perform a particular action
+
* User::getRights returns a list of rights (allowed actions) based on the current users group membership
    # So getRights should filter out any title-based restriction's actions which require groups that the user is not a member of
+
* Title::getRestrictions returns a list of groups who can perform a particular action
public function onUserGetRights(&$user, &$rights) {
+
* So getRights should filter out any title-based restriction's actions which require groups that the user is not a member of
global $wgSecurityExtraActions, $wgTitle;
+
* Allows sysop access
if (is_object($wgTitle)) {
+
*/
foreach ($wgSecurityExtraActions as $action => $name) {
+
public function onUserGetRights(&$user, &$rights, &$title = NULL) {
print_r($wgTitle->getRestrictions($action));
+
global $wgGroupPermissions, $wgTitle, $wgRequest;
print_r($user->getEffectiveGroups());
+
if (!is_object($title)) $title = $wgTitle;
if (count(array_intersect($wgTitle->getRestrictions($action), $user->getEffectiveGroups())) < 1) {
+
$groups = $user->getEffectiveGroups();
if ($i = array_search($action, $rights)) unset($rights[$i]);
+
 
}
+
# If current title is valid and we're not a sysop, process permissions
 +
if (is_object($title) && !in_array('sysop', $groups)) {
 +
 
 +
# Get list of groups required to read this title (empty list means no read restriction)
 +
$ns = $title->getNamespace();
 +
if ($ns == NS_SPECIAL) {
 +
 
 +
# Ugly hack to prevent specialpage operations on unreadable pages
 +
list($name, $par) = explode('/', $title->getDBkey(), 2);
 +
if ($par) $t = Title::newFromText($par);
 +
elseif ($wgRequest->getVal('target')) $t = Title::newFromText($wgRequest->getVal('target'));
 +
elseif ($wgRequest->getVal('oldtitle')) $t = Title::newFromText($wgRequest->getVal('oldtitle'));
 +
if (is_object($t)) $restrictions = $t->getRestrictions('read');
 
}
 
}
 +
else $restrictions = $title->getRestrictions('read');
 +
 +
# If there are read restrictions in place, then check if we're a member of any groups required for read access
 +
if (count($restrictions) && count(array_intersect($restrictions, $groups)) < 1)
 +
foreach ($rights as $i => $right) if ($right == 'read' || $right == 'move') unset($rights[$i]);
 +
elseif ($this->default_read) $wgGroupPermissions['*']['read'] = $this->default_read;
 
}
 
}
 
return true;
 
return true;
}
+
}
 
+
# Update a rights-list based on passed user and permissions array
+
/**
# - pre-existing rights in the list can be removed since each permission has group, action and allow
+
* 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
static function mergeRights(&$user, &$rights, &$permissions, $comment = false) {
+
*/
$groups = $this->getEffectiveGroups();
+
static function patchSQL($match) {
foreach ($groups as $group)
+
if (!preg_match("/old_text/", $match[0])) return $match[0];
if (isset($permissions[$group]))
+
$fields = str_replace(" ", "", $match[0]);
foreach ($permissions[$group] as $action => $allow) {
+
return ($fields == "*" || preg_match("/old_id/", $fields)) ? $fields : "$fields,old_id";
# Add or remove a right from the list
+
}
if ($allow && !in_array($action, $rights)) $rights[] = $action;
 
elseif (!$allow && in_array($action, $rights)) array_splice($rights, array_search($action, $rights), 1);
 
$this->info[] = array($group, $action, $allow, $comment);
 
}
 
}
 
  
 
/**
 
/**
 
* Validate the passed database row and replace any invalid content
 
* Validate the passed database row and replace any invalid content
* - this does not get called if already processing directives
+
* - called from DatabaseFetchHook whenever a row contains old_text
 +
* - old_id is guaranteed to exist due to patchSQL method
 
*/
 
*/
public function validateDatabaseRow(&$row) {
+
static function validateRow(&$row) {
# TODO
+
global $wgUser, $wgSimpleSecurity;
 +
$wgSimpleSecurity->enabled = false;
 +
 +
$groups = $wgUser->getEffectiveGroups();
 +
if (in_array('sysop', $groups)) return;
 +
 
 +
# Obtain a title object from the old_id
 +
$dbr =& wfGetDB(DB_SLAVE);
 +
$tbl = $dbr->tableName('revision');
 +
$rev = $dbr->selectRow($tbl, 'rev_page', "rev_text_id = {$row->old_id}", __METHOD__);
 +
$title = Title::newFromID($rev->rev_page);
 +
 
 +
# Validate title
 +
if (is_object($title) && !in_array('sysop', $groups)) {
 +
$restrictions = $title->getRestrictions('read');
 +
if (count($restrictions) && count(array_intersect($restrictions, $groups)) < 1)
 +
$row->old_text = wfMsg('badaccess-read');
 +
}
 +
 
 +
$wgSimpleSecurity->enabled = true;
 
}
 
}
  
Line 150: Line 194:
 
/**
 
/**
 
  * Hooks into Database::query and Database::fetchObject via the LoadBalancer class
 
  * Hooks into Database::query and Database::fetchObject via the LoadBalancer class
 +
* - this is a global because PHP doesn't like nested class definitions
 
  */
 
  */
function wfSimpleSecurityAddDatabaseHooks() {
+
function wfAddDatabaseHooks() {
 
global $wgLoadBalancer, $wgDBtype;
 
global $wgLoadBalancer, $wgDBtype;
  
Line 164: Line 209:
 
$type = ucfirst($wgDBtype);
 
$type = ucfirst($wgDBtype);
 
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;
$patched = preg_replace_callback("/(?<=SELECT ).+?(?= FROM)/", "wfSimpleSecurityPatchSQL", $sql, 1, $count);
+
if ($wgSimpleSecurity->enabled)
 +
$patched = preg_replace_callback("/(?<=SELECT ).+?(?= FROM)/", "SimpleSecurity::patchSQL", $sql, 1, $count);
 
return parent::query($count ? $patched : $sql, $fname, $tempIgnore);
 
return parent::query($count ? $patched : $sql, $fname, $tempIgnore);
}
+
}
 
 
 
function fetchObject(&$res) {
 
function fetchObject(&$res) {
 
global $wgSimpleSecurity;
 
global $wgSimpleSecurity;
 
$row = parent::fetchObject($res);
 
$row = parent::fetchObject($res);
$wgSimpleSecurity->validateDatabaseRow($row);
+
if ($wgSimpleSecurity->enabled && isset($row->old_text)) SimpleSecurity::validateRow($row);
 
return $row;
 
return $row;
}
+
}
 
+
}');
}');
 
  
 
# Create a replica of the LoadBalancer class which uses the new Database subclass for its connection objects
 
# Create a replica of the LoadBalancer class which uses the new Database subclass for its connection objects
Line 195: Line 238:
 
$wgLoadBalancer  = new LoadBalancer2($oldLoadBalancer->mServers);
 
$wgLoadBalancer  = new LoadBalancer2($oldLoadBalancer->mServers);
 
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) {
 
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";
 
 
}
 
}
  
Line 211: Line 244:
 
  */
 
  */
 
function wfSetupSimpleSecurity() {
 
function wfSetupSimpleSecurity() {
global $wgSimpleSecurity, $wgLanguageCode, $wgMessageCache, $wgSecurityMagic, $wgSecurityUseDBHook;
+
global $wgSimpleSecurity, $wgLanguageCode, $wgMessageCache, $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) wfAddDatabaseHooks();
  
 
# Instantiate the SimpleSecurity singleton now that the environment is prepared
 
# Instantiate the SimpleSecurity singleton now that the environment is prepared
Line 227: Line 260:
 
'securitylogentry'    => "",
 
'securitylogentry'    => "",
 
'securityinfotoggle'  => "This article exhibits security restrictions. Click this icon for more detail.",
 
'securityinfotoggle'  => "This article exhibits security restrictions. Click this icon for more detail.",
'securityreaddeny'   => "!!Permission denied!!",
+
'badaccess-read'     => "!!Permission denied!!",
'securitysyntaxerror' => "<nowiki>{{#$wgSecurityMagic</nowiki>:'''Error:''' Both ''action'' and ''group'' must be specified!}}",
+
'badaccess-special'   => "You do not have permission to access or perform special page actions on '''$1'''"
'securitylogdeny'    => "Attempt to '''$1''' [[$2]] was denied."
+
));
));
 
 
}
 
}
 
}
 
}
Line 238: Line 270:
 
  */
 
  */
 
function wfSimpleSecurityLanguageGetMagic(&$magicWords, $langCode = 0) {
 
function wfSimpleSecurityLanguageGetMagic(&$magicWords, $langCode = 0) {
global $wgSecurityMagicIf,$wgSecurityMagicGroup;
+
global $wgSecurityMagicIf, $wgSecurityMagicGroup;
 
$magicWords[$wgSecurityMagicIf]    = array($langCode, $wgSecurityMagicIf);
 
$magicWords[$wgSecurityMagicIf]    = array($langCode, $wgSecurityMagicIf);
 
$magicWords[$wgSecurityMagicGroup] = array($langCode, $wgSecurityMagicGroup);
 
$magicWords[$wgSecurityMagicGroup] = array($langCode, $wgSecurityMagicGroup);
 
return true;
 
return true;
 
}
 
}

Revision as of 05:53, 21 July 2008

<?php /**

* Simple Security extensionTemplate:Php
Info.svg These are the MediaWiki extensions we're using and/or developing. Please refer to the information on the mediawiki.org wiki for installation and usage details. Extensions here which have no corresponding mediawiki article are either not ready for use or have been superseded. You can also browse our extension code in our local Subversion repository or our GitHub mirror.
* - 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
*/

if (!defined('MEDIAWIKI')) die('Not an entry point.');

define('SIMPLESECURITY_VERSION', '4.1.1, 2008-07-21');

  1. 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 = true; # Use the DatabaseFetchHook to validate database access $wgSecurityAllowUser = false; # Allow restrictions based on user not just group

  1. Extra actions to allow control over in protection form

$wgSecurityExtraActions = array( 'read' => 'Read', 'source' => 'Source', 'history' => 'History' ); $wgSecurityExtraActions = array('read' => 'Read');

  1. Extra groups available in protection form

$wgSecurityExtraGroups = array();

array_unshift($wgExtensionFunctions, 'wfSetupSimpleSecurity'); # Put SimpleSecurity's setup function before all others

$wgHooks['LanguageGetMagic'][] = 'wfSimpleSecurityLanguageGetMagic'; $wgExtensionCredits['parserhook'][] = array( 'name' => "SimpleSecurity", '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 {

var $enabled = true;

/** * Constructor */ function __construct() { global $wgParser, $wgHooks, $wgLogTypes, $wgLogNames, $wgLogHeaders, $wgLogActions, $wgMessageCache, $wgSecurityMagicIf, $wgSecurityMagicGroup, $wgSecurityExtraActions, $wgSecurityExtraGroups, $wgRestrictionTypes, $wgRestrictionLevels, $wgGroupPermissions;

# $wgGroupPermissions has to have its default read entry removed because Title::userCanRead checks it directly if ($this->default_read = isset($wgGroupPermissions['*']['read']) && $wgGroupPermissions['*']['read']) $wgGroupPermissions['*']['read'] = false;

# Add our parser-hooks $wgParser->setFunctionHook($wgSecurityMagicIf, array($this, 'ifUserCan')); $wgParser->setFunctionHook($wgSecurityMagicGroup, array($this, 'ifGroup')); $wgHooks['UserGetRights'][] = $this;

# Add a new log type $wgLogTypes[] = 'security'; $wgLogNames ['security'] = 'securitylogpage'; $wgLogHeaders['security'] = 'securitylogpagetext'; $wgLogActions['security/deny'] = 'securitylogentry';

# Extend protection form groups, actions and messages $wgMessageCache->addMessages(array( 'protect-unchain' => "Modify actions individually" )); #$wgMessageCache->addMessages(array('loginreqpagetext' => "Sorry, you'll need to $1 to an account with sufficient permissions to view this page.")); foreach ($wgSecurityExtraActions as $k => $v) { if (empty($v)) $v = ucfirst($k); $wgRestrictionTypes[] = $k; $wgMessageCache->addMessages(array( "restriction-$k" => $v )); #$wgGroupPermissions['sysop'][$k] = true; # Ensure sysops have the right to perform this extra action }

foreach ($wgSecurityExtraGroups as $k => $v) { if (empty($v)) $v = ucfirst($k); $wgRestrictionLevels[] = $k; $wgMessageCache->addMessages(array( "protect-level-$k" => $v )); $wgGroupPermissions[$k]['not an action'] = true; # Ensure the new groups show up in rights management } }

/** * Process the ifUserCan conditional security directive */ public function ifUserCan(&$parser, $action, $title, $then, $else = ) { return $title->userCan($action) ? $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; }

/** * User::getRights returns a list of rights (allowed actions) based on the current users group membership * Title::getRestrictions returns a list of groups who can perform a particular action * So getRights should filter out any title-based restriction's actions which require groups that the user is not a member of * Allows sysop access */ public function onUserGetRights(&$user, &$rights, &$title = NULL) { global $wgGroupPermissions, $wgTitle, $wgRequest; if (!is_object($title)) $title = $wgTitle; $groups = $user->getEffectiveGroups();

# If current title is valid and we're not a sysop, process permissions if (is_object($title) && !in_array('sysop', $groups)) {

# Get list of groups required to read this title (empty list means no read restriction) $ns = $title->getNamespace(); if ($ns == NS_SPECIAL) {

# Ugly hack to prevent specialpage operations on unreadable pages list($name, $par) = explode('/', $title->getDBkey(), 2); if ($par) $t = Title::newFromText($par); elseif ($wgRequest->getVal('target')) $t = Title::newFromText($wgRequest->getVal('target')); elseif ($wgRequest->getVal('oldtitle')) $t = Title::newFromText($wgRequest->getVal('oldtitle')); if (is_object($t)) $restrictions = $t->getRestrictions('read'); } else $restrictions = $title->getRestrictions('read');

# If there are read restrictions in place, then check if we're a member of any groups required for read access if (count($restrictions) && count(array_intersect($restrictions, $groups)) < 1) foreach ($rights as $i => $right) if ($right == 'read' || $right == 'move') unset($rights[$i]); elseif ($this->default_read) $wgGroupPermissions['*']['read'] = $this->default_read; } return true; }

/** * 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 */ static function patchSQL($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"; }

/** * Validate the passed database row and replace any invalid content * - called from DatabaseFetchHook whenever a row contains old_text * - old_id is guaranteed to exist due to patchSQL method */ static function validateRow(&$row) { global $wgUser, $wgSimpleSecurity; $wgSimpleSecurity->enabled = false;

$groups = $wgUser->getEffectiveGroups(); if (in_array('sysop', $groups)) return;

# Obtain a title object from the old_id $dbr =& wfGetDB(DB_SLAVE); $tbl = $dbr->tableName('revision'); $rev = $dbr->selectRow($tbl, 'rev_page', "rev_text_id = {$row->old_id}", __METHOD__); $title = Title::newFromID($rev->rev_page);

# Validate title if (is_object($title) && !in_array('sysop', $groups)) { $restrictions = $title->getRestrictions('read'); if (count($restrictions) && count(array_intersect($restrictions, $groups)) < 1) $row->old_text = wfMsg('badaccess-read'); }

$wgSimpleSecurity->enabled = true; }

/** * 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
* - this is a global because PHP doesn't like nested class definitions
*/

function wfAddDatabaseHooks() { 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->enabled) $patched = preg_replace_callback("/(?<=SELECT ).+?(?= FROM)/", "SimpleSecurity::patchSQL", $sql, 1, $count); return parent::query($count ? $patched : $sql, $fname, $tempIgnore); } function fetchObject(&$res) { global $wgSimpleSecurity; $row = parent::fetchObject($res); if ($wgSimpleSecurity->enabled && isset($row->old_text)) SimpleSecurity::validateRow($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; }

/**

* Called from $wgExtensionFunctions array when initialising extensions
*/

function wfSetupSimpleSecurity() { global $wgSimpleSecurity, $wgLanguageCode, $wgMessageCache, $wgSecurityUseDBHook;

# Hooks into Database::query and Database::fetchObject via the LoadBalancer class if ($wgSecurityUseDBHook) wfAddDatabaseHooks();

# 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.", 'badaccess-read' => "!!Permission denied!!", 'badaccess-special' => "You do not have permission to access or perform special page actions on $1" )); } }

/**

* Register magic words
*/

function wfSimpleSecurityLanguageGetMagic(&$magicWords, $langCode = 0) { global $wgSecurityMagicIf, $wgSecurityMagicGroup; $magicWords[$wgSecurityMagicIf] = array($langCode, $wgSecurityMagicIf); $magicWords[$wgSecurityMagicGroup] = array($langCode, $wgSecurityMagicGroup); return true; }