Difference between revisions of "Extension:SimpleSecurity"

From Organic Design wiki
m
m
Line 11: Line 11:
  
 
# Global security settings
 
# Global security settings
$wgSecurityMagic            = "security";     # the parser-function name for security directives
+
$wgSecurityMagic            = "security";                         # the parser-function name for security directives
$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
$wgSecuritySysops            = array('sysop'); # the list of groups whose members bypass all security (groups a all lowercase, user are ucfirst)
+
$wgSecurityDenyTemplate      = 'Template:Action not permitted';   # Template used for displaying error when user violates permissions
$wgSecurityDenyTemplate      = 'Template:Action not permitted';
+
$wgSecurityInfoTemplate      = 'Template:Security info';           # Template used to layout the security information
$wgSecurityDenyReadTemplate  = 'Template:Not readable';
+
$wgSecuritySysops            = array('sysop');                     # the list of users or groups whose members bypass all security
$wgSecurityInfoTemplate      = 'Template:Security info';
+
$wgSecurityLogActions        = array('download');                 # Actions that should be logged
$wgSecurityRuleTemplate      = '';            # set to a template for the  
+
$wgSecurityDenyImage        = "$IP/skins/monobook/lock_icon.gif"; # the image returned in place of requested image if read access denied
$wgSecurityDenyImage        = dirname(__FILE__).'/deny.png'; # the image returned in place of requested image if read access denied
 
$wgNamespacePermissions      = array();
 
$wgSecurityLogActions        = array('download'); # Actions that should be logged
 
  
 
array_unshift($wgExtensionFunctions,'wfSetupSimpleSecurity');
 
array_unshift($wgExtensionFunctions,'wfSetupSimpleSecurity');
Line 115: Line 112:
 
# Validate the passed database row and replace any invalidate content
 
# Validate the passed database row and replace any invalidate content
 
function validateDatabaseRow(&$row) {
 
function validateDatabaseRow(&$row) {
global $wgUser,$wgSecuritySysops,$wgSecurityDenyReadTemplate;
+
global $wgUser,$wgSecuritySysops;
  
 
# todo: find the current revision associated with the old_id
 
# todo: find the current revision associated with the old_id
Line 123: Line 120:
 
$this->setDirectiveProcessing(false);
 
$this->setDirectiveProcessing(false);
  
# todo: validate the row and replace with $wgSecurityDenyReadTemplate if invalid
+
# todo: validate the row and replace with wfMsg('securityreaddeny') if invalid
  
 
}
 
}
Line 241: Line 238:
 
if ($wgLanguageCode == 'en') {
 
if ($wgLanguageCode == 'en') {
 
$wgMessageCache->addMessages(array(
 
$wgMessageCache->addMessages(array(
'security'               => "Security log",
+
'security'           => "Security log",
'securitylogpage'       => "Security log",
+
'securitylogpage'     => "Security log",
'securitylogpagetext'   => "This is a log of actions blocked by the [[MW:Extension:SimpleSecurity|SimpleSecurity extension]].",
+
'securitylogpagetext' => "This is a log of actions blocked by the [[MW:Extension:SimpleSecurity|SimpleSecurity extension]].",
'securitylogdeny'       => "Attempt to '''$1''' [[$2]] was denied.",
+
'securitylogentry'    => "",
'securitylogentry'      => ""
+
'securityreaddeny'    => "!!Permission denied!!",
 +
'securitylogdeny'     => "Attempt to '''$1''' [[$2]] was denied."
 
));
 
));
 
}
 
}

Revision as of 09:14, 13 October 2007

<?php

  1. 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.
  1. - See http://www.mediawiki.org/Extension:Simple_Security for installation and usage details
  2. - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
  3. - Needs apache's mod-rewrite for security on images, see code comments below
  4. - Version 4.0.0 started 2007-10-11

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

define('SIMPLESECURITY_VERSION','4.0.3, 2007-10-13');

  1. Global security settings

$wgSecurityMagic = "security"; # the parser-function name for security directives $wgSecurityMagicIf = "ifusercan"; # the name for doing a permission-based conditional $wgSecurityMagicGroup = "ifgroup"; # the name for doing a group-based conditional $wgSecurityDenyTemplate = 'Template:Action not permitted'; # Template used for displaying error when user violates permissions $wgSecurityInfoTemplate = 'Template:Security info'; # Template used to layout the security information $wgSecuritySysops = array('sysop'); # the list of users or groups whose members bypass all security $wgSecurityLogActions = array('download'); # Actions that should be logged $wgSecurityDenyImage = "$IP/skins/monobook/lock_icon.gif"; # the image returned in place of requested image if read access denied

array_unshift($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 {

var $processDirectives = false; var $directives = array();

# Constructor function SimpleSecurity() { global $wgParser,$wgHooks,$wgLogTypes,$wgLogNames,$wgLogHeaders,$wgLogActions, $wgSecurityMagic,$wgSecurityMagicIf,$wgSecurityMagicGroup;

# Add all our required event hooks $wgHooks['UserGetRights'][] = $this; $wgHooks['UserEffectiveGroups'][] = $this;

# Add our parser-hooks $wgParser->setFunctionHook($wgSecurityMagic,array($this,'processDirective')); $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'; }

# Enable/disable directive processing function setDirectiveProcessing($enabled = true) { $this->processDirectives = $enabled; if ($enabled) $this->directives = array(); }

# Process and store a security directive function processDirective(&$parser,$actions = ,$groups = ) { $parser->mOutput->mCacheTime = -1; if ($this->processDirectives && $actions && $groups) $this->directives[] = array($actions,$groups); return ; }

# Process the ifUserCan conditional security directive function ifUserCan(&$parser,$action,$title,$then,$else = ) { return $this->validateTitle($action,Title::newFromText($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 function ifGroup(&$parser,$groups,$then,$else = ) { global $wgUser; $intersection = array_intersect(array_map('strtolower',split(',',$groups)),$wgUser->getEffectiveGroups()); return count($intersection) > 0 ? $then : $else; }

# Ensure all groups are lowercase and add the username with first letter capitalised function onUserEffectiveGroups(&$user,&$groups) { $groups = array_map('strtolower',$groups); $groups[] = ucfirst($user->getName()); return true; }

# Intercept the user rights and filter based on $wgGroupPermissions and $wgTitle function onUserGetRights(&$user,&$rights) { global $wgGroupPermissions,$wgTitle;

# The first time this is called we need to validate $wgTitle static $first = 0; if ($first++ == 0) { $this->setDirectiveProcessing(true); # todo: read and parse $wgTitle's content $this->setDirectiveProcessing(false);

# todo: validate $wgTitle from directive state and $wgGroupPermissions

# todo: store the validation results to use for future calls }

# todo: filter $rights based on stored validation results

return true; }

# Validate the passed database row and replace any invalidate content function validateDatabaseRow(&$row) { global $wgUser,$wgSecuritySysops;

# todo: find the current revision associated with the old_id

$this->setDirectiveProcessing(true); # todo: read and parse the revision text $this->setDirectiveProcessing(false);

# todo: validate the row and replace with wfMsg('securityreaddeny') if invalid

}

# Needed in some versions to prevent Special:Version from breaking function __toString() { return 'SimpleSecurity'; } }

  1. Adds UserGetRights and UserEffectiveGroups for MediaWiki versions prior to 1.11 which don't have them

function wfSimpleSecurityAddUserHooks() { global $wgVersion,$wgUser; if (version_compare($wgVersion,"1.11.0") < 0) return;

# todo: need to hook StubUser::_newObject if $wgUser still a stub at this point

# 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 getRights() { parent::getRights(); wfRunHooks("UserGetRights",array($this,&$this->mRights)); }

function getEffectiveGroups($recache = false) { $groups = parent::getEffectiveGroups($recache); wfRunHooks("UserEffectiveGroups",array(&$this,&$groups); return $groups; } }');

# 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; }

  1. 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 $type = ucfirst($wgDBtype); eval("class Database{$type}2 extends Database{$type}".' {

public function query($sql,$fname = "",$tempIgnore = false) { $sql = preg_replace_callback("/(?<=SELECT ).+?(?= FROM)/","wfSimpleSecurityPatchSQL",$sql,1); return parent::query($sql,$fname,$tempIgnore); }

function fetchObject(&$res) { global $wgSimpleSecurity; $row = parent::fetchObject($res); $wgSimpleSecurity->validateDatabaseRow($row); return $row; }

}');

# Create a replica of the LoadBalancer class which uses the new Database subclass for its connection objects $LoadBalancer = get_class($wgLoadBalancer); $LoadBalancer2 = $LoadBalancer."2"; eval("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;

}

  1. 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"; }

  1. Register magic words

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

  1. Called from $wgExtensionFunctions array when initialising extensions

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

# Hooks into Database::query and Database::fetchObject via the LoadBalancer class wfSimpleSecurityAddDatabaseHooks();

# Adds UserGetRights and UserEffectiveGroups for MediaWiki versions prior to 1.11 which don't have them wfSimpleSecurityAddUserHooks();

# 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' => "", 'securityreaddeny' => "!!Permission denied!!", 'securitylogdeny' => "Attempt to $1 $2 was denied." )); } }

?>