Difference between revisions of "Extension:SimpleSecurity2.1.php"

From Organic Design wiki
m
(Updated to work with MW1.9/PHP5.2)
Line 1: Line 1:
 
<?
 
<?
 
# Simple security extension
 
# Simple security extension
# - Version 2.0
+
# - Version 2.1 (2007-03-09)
# - See [[[[MediaWiki Security]]]] article for installation and usage details
+
# - See http://www.organicdesign/MediaWiki_Security for installation and usage details
 
# - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
 
# - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
 
# - 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
 +
# - The following directives allows everyone view-only access to this article
 +
#  {{Security:*|sysop}}
 +
#  {{Security:view|*}}
  
 
# These globals can be set in LocalSettings to adjust the Simple Security attributes
 
# These globals can be set in LocalSettings to adjust the Simple Security attributes
Line 15: Line 18:
 
if ($title == 'Special:Movepage' && $action == 'submit') {
 
if ($title == 'Special:Movepage' && $action == 'submit') {
 
$securityAction = 'move';
 
$securityAction = 'move';
$t = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target'));
+
$securityTitle = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target'));
} else $t = $title;
+
} else $securityTitle = str_replace(' ','_',$title);
  
 
# If title exists, activate the security
 
# If title exists, activate the security
if ($t) {
+
if ($securityTitle) {
  
 
# Globals holding security info
 
# Globals holding security info
 +
$securityDenyRevisionID = 2575; # the current-revision-id of the Template:Action not permitted
 
$securityCache = array();
 
$securityCache = array();
 
$securityItems = array();
 
$securityItems = array();
Line 30: Line 34:
 
#  RewriteCond %{REQUEST_URI} ^/wiki/images/.+
 
#  RewriteCond %{REQUEST_URI} ^/wiki/images/.+
 
#  RewriteRule ^/wiki/images/(.+) /wiki/index.php/Download/$1 [L]
 
#  RewriteRule ^/wiki/images/(.+) /wiki/index.php/Download/$1 [L]
if (ereg('^Download/(.+)/([^/]+)$',$t,$m)) {
+
if (ereg('^Download/(.+)/([^/]+)$',$securityTitle,$m)) {
 
$path = $m[1];
 
$path = $m[1];
 
$file = $image = $m[2];
 
$file = $image = $m[2];
 
if (ereg('^thumb/.+/([^/]+)$',$path,$m)) $image = $m[1];
 
if (ereg('^thumb/.+/([^/]+)$',$path,$m)) $image = $m[1];
$t = "Image:$image";
+
$securityTitle = "Image:$image";
 
} else $file = '';
 
} else $file = '';
 +
if (0) {
 +
# Create a new secureLoadBalancer class by extending the existing one
 +
$LoadBalancer = get_class($wgLoadBalancer);
 +
$secureLoadBalancer = "secure$LoadBalancer";
 +
eval('class '.$secureLoadBalancer.' extends '.$LoadBalancer.' {
  
# Hook1: Asseses security after raw content fetched from database and clears if not readable
+
# Override the getConnection method to return a secureDatabase connection
 +
function &getConnection($i, $fail = true, $groups = array()) {
 +
$db =& parent::getConnection($i,$fail,$groups);
 +
 
 +
# Create a secure subclass of the database class with extended fetchObject method
 +
$dbClass = get_class($db);
 +
$dbSecure = "secure$dbClass";
 +
if (!class_exists($dbSecure)) {
 +
eval(\'class \'.$dbSecure.\' extends \'.$dbClass.\' {
 +
function fetchObject($res) {
 +
$row = parent::fetchObject($res);
 +
wfRunHooks("DatabaseFetchObject", array(&$this,&$row));
 +
return $row;
 +
}
 +
}\');
 +
}
 +
 
 +
# Replace the DatabaseXXX connection with an identical secureDatabaseXXX connection
 +
$sdb = new $dbSecure;
 +
foreach(array_keys(get_class_vars($dbClass)) as $k) $sdb->$k = $db->$k;
 +
unset($db);
 +
 
 +
return $sdb;
 +
}
 +
 
 +
}');
 +
 
 +
# Replace the global LoadBalancer object with an identical secureLoadBalancer
 +
$oldLoadBalancer = $wgLoadBalancer;
 +
$wgLoadBalancer = new $secureLoadBalancer;
 +
foreach(array_keys(get_class_vars($LoadBalancer)) as $k) $wgLoadBalancer->$k = $oldLoadBalancer->$k;
 +
 
 +
# Add our security function to the new hook
 +
# - also fills the global $securityCache cache with info to append to the rendered article
 +
#$wgHooks['DatabaseFetchObject'][] = 'securityAfterFetchObject';
 +
function securityAfterFetchObject(&$db,&$row) {
 +
if (is_object($row) && $row->rev_text_id) {
 +
if ($row->rev_text_id == 2638) { $row->rev_text_id = $securityDenyRevisionID; msg($db->lastQuery()); }
 +
}
 +
}
 +
}
 +
# Hook2: 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
 
# - also fills the global $securityCache cache with info to append to the rendered article
$wgHooks['ArticleAfterFetchContent'][] = 'securityAfterFetch';
+
$wgHooks['ArticleAfterFetchContent'][] = 'securityAfterFetchContent';
 
$securityAfterFetchActive = true;
 
$securityAfterFetchActive = true;
function securityAfterFetch(&$this,&$text) {
+
function securityAfterFetchContent(&$article,&$text) {
 
global $wgUser,$wgTitle,$securityItems,$securityCache,$securityAfterFetchActive,
 
global $wgUser,$wgTitle,$securityItems,$securityCache,$securityAfterFetchActive,
 
$wgSecurityName,$wgSecurityDontInheritName,$wgSecurityInheritable;
 
$wgSecurityName,$wgSecurityDontInheritName,$wgSecurityInheritable;
Line 47: Line 97:
  
 
# Get the security info straight from cache if already exists
 
# Get the security info straight from cache if already exists
$key = $this->mTitle->getPrefixedText();
+
$key = $article->mTitle->getPrefixedURL();
 +
 
 
if (isset($securityCache[$key])) $items = $securityCache[$key]; else {
 
if (isset($securityCache[$key])) $items = $securityCache[$key]; else {
  
Line 68: Line 119:
 
# Get the security items from the cats by running the parser over the content of each
 
# Get the security items from the cats by running the parser over the content of each
 
if ($wgSecurityInheritable) foreach ($output->getCategoryLinks() as $cat) {
 
if ($wgSecurityInheritable) foreach ($output->getCategoryLinks() as $cat) {
$article = new Article($title = Title::newFromText($cat = "Category:$cat"));
+
$ca = new Article($ct = Title::newFromText($cat = "Category:$cat"));
 
$securityItems = array();
 
$securityItems = array();
$parser->parse($article->fetchContent(0,false,false),$title,$options,false,false);
+
$parser->parse($ca->fetchContent(0,false,false),$ct,$options,false,false);
 
foreach ($securityItems as $i) $items[] = array($i[0],$i[1],"this rule is inherited from [[:$cat]]");
 
foreach ($securityItems as $i) $items[] = array($i[0],$i[1],"this rule is inherited from [[:$cat]]");
 
}
 
}
Line 84: Line 135:
 
}
 
}
  
# Hook2: Block content if action not allowed, and append any security info before parsing
+
# Hook3: Block content if action not allowed, and append any security info before parsing
 
$wgHooks['ParserBeforeStrip'][] = 'securityBeforeParsing';
 
$wgHooks['ParserBeforeStrip'][] = 'securityBeforeParsing';
 
function securityBeforeParsing(&$parser,&$text,&$strip_state) {
 
function securityBeforeParsing(&$parser,&$text,&$strip_state) {
Line 91: Line 142:
 
# Only process this hook if it's the main article
 
# Only process this hook if it's the main article
 
if ($parser != $wgParser) return true;
 
if ($parser != $wgParser) return true;
$key = $parser->mTitle->getPrefixedText();
+
$key = $parser->mTitle->getPrefixedURL();
  
 
# Replace the content with the "Action not permitted" template if action not allowed
 
# Replace the content with the "Action not permitted" template if action not allowed
Line 115: Line 166:
  
 
# Accumulates security information in the global $securityItems whenever a security item is parsed
 
# Accumulates security information in the global $securityItems whenever a security item is parsed
function securityProcessItem(&$this,$a,$b) {
+
function securityProcessItem(&$parser,$a,$b) {
 
global $securityItems;
 
global $securityItems;
 
$securityItems[] = array($a,$b);
 
$securityItems[] = array($a,$b);
Line 123: Line 174:
 
# Return whether or not a user is allowed to perform an action according to an array of security items
 
# Return whether or not a user is allowed to perform an action according to an array of security items
 
function securityValidate(&$user,&$items,$action) {
 
function securityValidate(&$user,&$items,$action) {
 +
if (!is_array($items)) return true;
  
 
# Resolve permission for this action from the extracted security links
 
# Resolve permission for this action from the extracted security links
Line 148: Line 200:
  
 
# Read the article content to update security info cache
 
# Read the article content to update security info cache
$tmp = new Article(Title::newFromText($t));
+
$tmp = new Article(Title::newFromText($securityTitle));
 
$tmp->fetchContent(0,false,false);
 
$tmp->fetchContent(0,false,false);
$items = $securityCache[$t];
+
$items = $securityCache[$securityTitle];
  
 
# Validate current action and set to view if not allowed
 
# Validate current action and set to view if not allowed
Line 181: Line 233:
 
@readfile("$wgUploadDirectory/$path/$file");
 
@readfile("$wgUploadDirectory/$path/$file");
 
die;
 
die;
} else $title = $t;
+
} else $title = $securityTitle;
 
}
 
}
 
}
 
}
  
 
?>
 
?>

Revision as of 08:54, 9 March 2007

<?

  1. Simple security extension
  2. - Version 2.1 (2007-03-09)
  3. - See http://www.organicdesign/MediaWiki_Security for installation and usage details
  4. - Licenced under LGPL (http://www.gnu.org/copyleft/lesser.html)
  5. - Needs apache's mod-rewrite for security on images, see code comments below
  6. - The following directives allows everyone view-only access to this article
  7. Template:Security:*
  8. Template:Security:view
  1. These globals can be set in LocalSettings to adjust the Simple Security attributes

if (!isset($wgSecurityName)) $wgSecurityName = "Security"; if (!isset($wgSecurityDontInheritName)) $wgSecurityDontInheritName = "*$wgSecurityName"; if (!isset($wgSecurityInheritable)) $wgSecurityInheritable = true;

  1. Handle moves (since they don't use $action)

$securityAction = $action == 'submit' ? 'edit' : strtolower($action); if ($title == 'Special:Movepage' && $action == 'submit') { $securityAction = 'move'; $securityTitle = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target')); } else $securityTitle = str_replace(' ','_',$title);

  1. If title exists, activate the security

if ($securityTitle) {

# Globals holding security info $securityDenyRevisionID = 2575; # the current-revision-id of the Template:Action not permitted $securityCache = array(); $securityItems = array();

# Handle security on files (needs apache mod-rewrite) # - mod-rewrite redirects /wiki/images/... requests to article title Download/image-path... # - Use the following rewrite condition and rule (adjust to your wiki path if necessary) # RewriteCond %{REQUEST_URI} ^/wiki/images/.+ # RewriteRule ^/wiki/images/(.+) /wiki/index.php/Download/$1 [L] if (ereg('^Download/(.+)/([^/]+)$',$securityTitle,$m)) { $path = $m[1]; $file = $image = $m[2]; if (ereg('^thumb/.+/([^/]+)$',$path,$m)) $image = $m[1]; $securityTitle = "Image:$image"; } else $file = ; if (0) { # Create a new secureLoadBalancer class by extending the existing one $LoadBalancer = get_class($wgLoadBalancer); $secureLoadBalancer = "secure$LoadBalancer"; eval('class '.$secureLoadBalancer.' extends '.$LoadBalancer.' {

# Override the getConnection method to return a secureDatabase connection function &getConnection($i, $fail = true, $groups = array()) { $db =& parent::getConnection($i,$fail,$groups);

# Create a secure subclass of the database class with extended fetchObject method $dbClass = get_class($db); $dbSecure = "secure$dbClass"; if (!class_exists($dbSecure)) { eval(\'class \'.$dbSecure.\' extends \'.$dbClass.\' { function fetchObject($res) { $row = parent::fetchObject($res); wfRunHooks("DatabaseFetchObject", array(&$this,&$row)); return $row; } }\'); }

# Replace the DatabaseXXX connection with an identical secureDatabaseXXX connection $sdb = new $dbSecure; foreach(array_keys(get_class_vars($dbClass)) as $k) $sdb->$k = $db->$k; unset($db);

return $sdb; }

}');

# Replace the global LoadBalancer object with an identical secureLoadBalancer $oldLoadBalancer = $wgLoadBalancer; $wgLoadBalancer = new $secureLoadBalancer; foreach(array_keys(get_class_vars($LoadBalancer)) as $k) $wgLoadBalancer->$k = $oldLoadBalancer->$k;

# Add our security function to the new hook # - also fills the global $securityCache cache with info to append to the rendered article #$wgHooks['DatabaseFetchObject'][] = 'securityAfterFetchObject'; function securityAfterFetchObject(&$db,&$row) { if (is_object($row) && $row->rev_text_id) { if ($row->rev_text_id == 2638) { $row->rev_text_id = $securityDenyRevisionID; msg($db->lastQuery()); } } } } # Hook2: 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 $wgHooks['ArticleAfterFetchContent'][] = 'securityAfterFetchContent'; $securityAfterFetchActive = true; function securityAfterFetchContent(&$article,&$text) { global $wgUser,$wgTitle,$securityItems,$securityCache,$securityAfterFetchActive, $wgSecurityName,$wgSecurityDontInheritName,$wgSecurityInheritable; if (!$securityAfterFetchActive) return true;

# Get the security info straight from cache if already exists $key = $article->mTitle->getPrefixedURL();

if (isset($securityCache[$key])) $items = $securityCache[$key]; else {

# Set up a new local parser object to process security items independently of main rendering process # - both Security and *Security items are processed and remembered $parser = new Parser; $options = ParserOptions::newFromUser($wgUser); $parser->setFunctionHook($wgSecurityName,'securityProcessItem'); $parser->setFunctionHook($wgSecurityDontInheritName,'securityProcessItem'); $securityItems = array(); $output = $parser->parse($text,$wgTitle,$options,false,false); $items = $securityItems;

# Before inheriting security from categories # - stop checking *Security because they shouldn't inherit # - disable this AfterDatabaseFetch hook while reading category contents unset($parser->mFunctionHooks[$wgSecurityDontInheritName]); $securityAfterFetchActive = false;

# Get the security items from the cats by running the parser over the content of each if ($wgSecurityInheritable) foreach ($output->getCategoryLinks() as $cat) { $ca = new Article($ct = Title::newFromText($cat = "Category:$cat")); $securityItems = array(); $parser->parse($ca->fetchContent(0,false,false),$ct,$options,false,false); foreach ($securityItems as $i) $items[] = array($i[0],$i[1],"this rule is inherited from $cat"); }

# Re-enable AfterFetch hook and cache the security info $securityAfterFetchActive = true; $securityCache[$key] = $items; }

# Don't allow any content to be returned if not viewable

if (!securityValidate($wgUser,$items,'view')) $text = "

Sorry, action not permitted!

Your user rights do not permit the view action to be performed on this article.

"; return true; }

# Hook3: Block content if action not allowed, and append any security info before parsing $wgHooks['ParserBeforeStrip'][] = 'securityBeforeParsing'; function securityBeforeParsing(&$parser,&$text,&$strip_state) { global $wgParser,$wgUser,$securityAllow,$securityCache,$securityAction,$wgSecurityName,$wgSecurityDontInheritName;

# Only process this hook if it's the main article if ($parser != $wgParser) return true; $key = $parser->mTitle->getPrefixedURL();

# Replace the content with the "Action not permitted" template if action not allowed

if (!$securityAllow) $text = "

Sorry, action not permitted!

Your user rights do not permit the $securityAction action to be performed on this article.

";

# Append the security info to the article content if (count($securityCache[$key]) && $securityAction == 'view') { $text .= "\n

Padlock.svg

There are security restrictions on this article (for information about this extension, click here)

\n"; foreach ($securityCache[$key] as $i) { $a = $i[0] == '*' ? 'Every action' : ucfirst($i[0]); $b = $i[1] == '*' ? 'anybody' : $i[1]; $c = $i[2] ? " ($i[2])" : ; $text .= "*$a requires the user to be $b$c\n"; } $text .= "\n

";

}

# Ensure the parser doesn't render the security items $parser->setFunctionHook($wgSecurityName,'securityProcessItem'); $parser->setFunctionHook($wgSecurityDontInheritName,'securityProcessItem'); return true; }

# Accumulates security information in the global $securityItems whenever a security item is parsed function securityProcessItem(&$parser,$a,$b) { global $securityItems; $securityItems[] = array($a,$b); return ; }

# Return whether or not a user is allowed to perform an action according to an array of security items function securityValidate(&$user,&$items,$action) { if (!is_array($items)) return true;

# Resolve permission for this action from the extracted security links $security = ; foreach ($items as $i) { if ($i[1] == ) $i[1] = 'sysop'; $actions = preg_split("/\\s*,\\s*/",strtolower($i[0])); if ((in_array($action,$actions)) || (in_array('*',$actions) && ($security == ))) $security = $i[1]; }

# Validate extracted security against this user/groups $groups = $user->getGroups(); foreach($groups as $k => $v) $groups[$k] = strtolower($v); $deny = false; if ($security && !in_array('sysop',$groups) && !in_array('director',$groups)) { $security = preg_split("/\\s*,\\s*/",$security); if (!in_array('*',$security)) { $groups[] = ucfirst($user->mName); if (count(array_intersect($groups,$security))==0) $deny = true; } }

return !$deny; }

# Read the article content to update security info cache $tmp = new Article(Title::newFromText($securityTitle)); $tmp->fetchContent(0,false,false); $items = $securityCache[$securityTitle];

# Validate current action and set to view if not allowed if (!$securityAllow = securityValidate($wgUser,$items,$securityAction)) $action = 'view';

# If its a file, send it back to the client and exit, or block by redirecting to the file's associated article if ($file) {

# Log the download event if any security on file if (count($items)) { $user = $wgUser->mName; $entry = $securityAllow ? "User:$user accessed \"$file\"" : "User:$user was denied access to \"$file\""; $ts = $wgLang->timeanddate(wfTimestampNow(),true); $la = new Article(Title::newFromText('Download log')); $log = "{{Log entry". "|ts=$ts". "|entry=$entry". "|ip=".$_SERVER['REMOTE_ADDR']. "|ua=".$_SERVER['HTTP_USER_AGENT']. "|cookie=".$_SERVER['HTTP_COOKIE']. "|qs=".$_SERVER['QUERY_STRING']. "}}\n"; $la->quickEdit($log.$la->fetchContent(0,false,false)); }

# Return the file to the client if security validates if ($securityAllow) { header("Content-Type: application/octet-stream"); header("Content-Disposition: attachment; filename=\"$file\""); @readfile("$wgUploadDirectory/$path/$file"); die; } else $title = $securityTitle; } }

?>