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

From Organic Design wiki
(done - now to debug it)
(rv until fixed)
Line 1: Line 1:
 
<?
 
<?
 
# Simple security extension
 
# Simple security extension
# - Version 2.0
+
# - [[Security:edit,delete,move,protect|sysop]]
 
# - See [[[[MediaWiki Security]]]] article for installation and usage details
 
# - See [[[[MediaWiki Security]]]] article 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
  
# Handle moves (since they don't use $action)
+
# Handle moves
$securityAction = $action == 'submit' ? 'edit' : strtolower($action);
+
$a = $action == 'submit' ? 'edit' : strtolower($action);
if ($title == 'Special:Movepage' && $action == 'submit') {
+
if ($title=='Special:Movepage' && $action=='submit') {
$securityAction = 'move';
+
$a = 'move';
 
$t = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target'));
 
$t = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target'));
 
} else $t = $title;
 
} else $t = $title;
 +
$securityAction = $a;
  
# If title exists, activate the security
+
$groups = $wgUser->getGroups();
 +
foreach($groups as $k => $v) $groups[$k] = strtolower($v);
 
if ($t) {
 
if ($t) {
  
Line 28: Line 30:
 
} else $file = '';
 
} else $file = '';
  
# Hook1: Asseses security after raw content fetched from database and clears if not readable
+
# Get security links from this article
# - also fills the global $securityItems cache with info to append to the rendered article
+
$secinfo = '';
$securityAfterFetchIndex = count($wgHooks['ArticleAfterFetchContent']);
+
$text = new Article(Title::newFromText($t));
$wgHooks['ArticleAfterFetchContent'][] = 'securityAfterFetch';
+
$text = $text->fetchContent(0,false,false);
function securityAfterFetch(&$this,&$text) {
+
if (preg_match_all('/\\[{2}\\s*:?security\\s*:\\s*([^\\]]+?)\\s*\\|\\s*([^\\]]*)\\s*\\]{2}/i',$text,$seclinks,PREG_SET_ORDER))
global $wgUser,$wgTitle,$securityAction,$securityItems,$securityAfterFetchIndex;
+
addSecurityInfo($seclinks,$secinfo); else $seclinks = array();
  
# Set up a new local parser object to process security items independently of main rendering process
+
# Get security links from article's categories
# - both Security and :Security items are processed and remembered
+
preg_match_all('/\\[{2}category:(.+?)(\\|.+?)?\\]]/i',$text,$cats);
$parser = new Parser;
+
foreach ($cats[1] as $i => $cat) {
$options = ParserOptions::newFromUser($wgUser);
+
$cats[1][$i] = $cat = 'Category:'.ucfirst($cat);
$parser->setFunctionHook('Security','securityProcessItem');
+
if (is_object($text = new Article(Title::newFromText($cat)))) {
$parser->setFunctionHook(':Security','securityProcessItem');
+
if (preg_match_all('/\\[{2}\\s*security\\s*:\\s*([^\\]]+?)\\s*\\|\\s*([^\\]]*)\\s*\\]{2}/i',$text->fetchContent(0,false,false),$match,PREG_SET_ORDER)) {
$securityItems = array();
+
$seclinks = array_merge($match,$seclinks);
$output = $parser->parse($text,$wgTitle,$options,false,false);
+
addSecurityInfo($match,$secinfo,"this rule is inherited from [[:$cat]]");
$allItems = $securityItems;
 
 
 
# Before inheriting security from categories
 
# - stop checking :Security because they shouldn't inherit
 
# - remove this AfterDatabaseFetch hook because we need to read each cats content
 
unset($parser->mFunctionHooks[':Security']);
 
$hookBak = $wgHooks['ArticleAfterFetchContent'];
 
$wgHooks['ArticleAfterFetchContent'][$securityAfterFetchIndex]
 
= array_splice($wgHooks['ArticleAfterFetchContent'],$securityAfterFetchIndex,1);
 
 
 
# Get the security items from the cats by running the parser over the content of each
 
foreach ($output->getCategoryLinks() as $cat) {
 
$article = new Article($title = Title::newFromText($cat = "Category:$cat"));
 
$securityItems = array();
 
$parser->parse($article->fetchContent(0,false,false),$title,$options,false,false);
 
foreach ($securityItems as $i) $allItems[] = array($i[0],$i[1],"this rule is inherited from [[:$cat]]");
 
}
 
$securityItems = $allItems;
 
 
 
# Clear content if not viewable
 
if (!securityValidate($wgUser,$securityItems,'view')) $text = "{{Action not permitted|$securityAction}}";
 
 
 
# Put the AfterFetch hook back how it was and exit successfully
 
$wgHooks['ArticleAfterFetchContent'][$securityAfterFetchIndex] = $hookBak;
 
return true;
 
}
 
 
 
# Hook2: Block content if action not allowed, and append any security info before parsing
 
$wgHooks['ParserBeforeStrip'][] = 'securityBeforeParsing';
 
function securityBeforeParsing(&$parser,&$text,&$strip_state) {
 
global $securityAllow,$securityItems,$securityAction;
 
if (!$securityAllow) {
 
$text = "{{Action not permitted|$securityAction}}";
 
$securityAllow = true;
 
$securityAction = 'view';
 
}
 
if (count($securityItems) && 0 == $GLOBALS['rsi-done']++ && $securityAction == 'view') {
 
$text .= "\n{{Security info|1=\n";$secinfo
 
foreach ($securityItems as $i) {
 
$a = $i[1] == '*' ? 'Every action' : ucfirst($i[1]);
 
$b = $i[2] == '*' ? 'anybody' : $i[2];
 
$text .= "*'''$a''' requires the user to be '''$b'''$comment\n";
 
 
}
 
}
$text .= "\n}}";
 
 
}
 
}
return true;
 
 
}
 
}
  
# Accumulates security information in a global cache whenever a security item is parsed
+
# Resolve permission for this action from the extracted security links
function securityProcessItem(&$this,$a,$b) {
+
$security = '';
$GLOBALS['securityItems'][] = array($a,$b);
+
foreach ($seclinks as $link) {
return '';
+
if ($link[2]=='') $link[2] = 'sysop';
 +
$actions = preg_split("/\\s*,\\s*/",strtolower($link[1]));
 +
if (in_array($a,$actions)) $security = $link[2];
 +
if (in_array('*',$actions) && ($security == '')) $security = $link[2];
 
}
 
}
  
# Return whether or not a user is allowed to perform an action according to an array of security items
+
# Validate extracted security against this user/groups
function securityValidate(&$user,&$items,$action) {
+
$deny = false;
 
+
if ($security && !in_array('sysop',$groups) && !in_array('director',$groups)) {
# Resolve permission for this action from the extracted security links
+
$security = preg_split("/\\s*,\\s*/",$security);
$security = '';
+
if (!in_array('*',$security)) {
foreach ($items as $i) {
+
$groups[] = ucwords($wgUser->mName);
if ($i[2] == '') $i[2] = 'sysop';
+
if (count(array_intersect($groups,$security))==0) {
$actions = preg_split("/\\s*,\\s*/",strtolower($i[1]));
+
$action = 'view';
if ((in_array($action,$actions)) || (in_array('*',$actions) && ($security == ''))) $security = $i[2];
+
$deny = true;
}
 
 
 
# 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;
 
 
}
 
}
 
# Get security items from this article with a dummy read to activate our AfterDatabaseFetch hook
 
$securityItems = array();
 
$tmp = new Article(Title::newFromText($t));
 
$tmp->fetchContent(0,false,false);
 
$allow = securityValidate($wgUser,$securityItems,$securityAction);
 
 
# If its a file, send it back to the client and exit, or block by redirecting to the file's associated article
 
 
if ($file) {
 
if ($file) {
 
 
# Log the download event if any security on file
 
# Log the download event if any security on file
if (count($securityItems)) {
+
if ($secinfo) {
 
$user = $wgUser->mName;
 
$user = $wgUser->mName;
$entry = $allow ? "User:$user accessed \"$file\"" : "User:$user was denied access to \"$file\"";
+
if ($deny) $entry = "User:$user was denied access to \"$file\"";
$ts = $wgLang->timeanddate(wfTimestampNow(),true);
+
else $entry = "User:$user accessed \"$file\"";
 +
$ts = $GLOBALS['wgLang']->timeanddate(wfTimestampNow(),true);
 
$la = new Article(Title::newFromText('Download log'));
 
$la = new Article(Title::newFromText('Download log'));
 
$log = "{{Log entry".
 
$log = "{{Log entry".
Line 148: Line 88:
 
$la->quickEdit($log.$la->fetchContent(0,false,false));
 
$la->quickEdit($log.$la->fetchContent(0,false,false));
 
}
 
}
 
 
# Return the file to the client if security validates
 
# Return the file to the client if security validates
if ($allow) {
+
if ($deny) $title = $t; else {
 
header("Content-Type: application/octet-stream");
 
header("Content-Type: application/octet-stream");
 
header("Content-Disposition: attachment; filename=\"$file\"");
 
header("Content-Disposition: attachment; filename=\"$file\"");
 
@readfile("$wgUploadDirectory/$path/$file");
 
@readfile("$wgUploadDirectory/$path/$file");
 
die;
 
die;
} else $title = $t;
+
}
 +
}
 +
}
 +
 
 +
# Remove the security links and append security info before wiki-parsing
 +
$wgHooks['ParserBeforeStrip'][] = 'securityBeforeParsing';
 +
function securityBeforeParsing(&$parser,&$text,&$strip_state) {
 +
global $deny,$secinfo,$securityAction,$wgHooks;
 +
if ($deny) {
 +
$text = "{{Action not permitted|$securityAction}}";
 +
$deny = false;
 +
$securityAction = 'view';
 
}
 
}
 +
else $text = preg_replace("/\\[{2}\\s*:?security\\s*:[^\\]]+?\\]{2}[\r\n]?/i",'',$text);
 +
if ($secinfo && 0 == $GLOBALS['rsi-done']++ && $securityAction == 'view') $text .= "\n{{Security info|1=\n$secinfo\n}}";
 +
return true;
 
}
 
}
  
 +
# Add security information item
 +
function addSecurityInfo(&$links,&$info,$comment='') {
 +
if ($comment) $comment = " &nbsp; ''($comment)''";
 +
foreach ($links as $link) {
 +
$a = $link[1] == '*' ? 'Every action' : ucfirst($link[1]);
 +
$b = $link[2] == '*' ? 'anybody' : $link[2];
 +
$info .= "*'''$a''' requires the user to be '''$b'''$comment\n";
 +
}
 +
}
 
?>
 
?>

Revision as of 11:13, 3 March 2007

<?

  1. Simple security extension
  2. - sysop
  3. - See [[MediaWiki Security]] article 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
  1. Handle moves

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

$groups = $wgUser->getGroups(); foreach($groups as $k => $v) $groups[$k] = strtolower($v); if ($t) {

# 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/(.+)/([^/]+)$',$t,$m)) { $path = $m[1]; $file = $image = $m[2]; if (ereg('^thumb/.+/([^/]+)$',$path,$m)) $image = $m[1]; $t = "Image:$image"; } else $file = ;

# Get security links from this article $secinfo = ; $text = new Article(Title::newFromText($t)); $text = $text->fetchContent(0,false,false); if (preg_match_all('/\\[{2}\\s*:?security\\s*:\\s*([^\\]]+?)\\s*\\|\\s*([^\\]]*)\\s*\\]{2}/i',$text,$seclinks,PREG_SET_ORDER)) addSecurityInfo($seclinks,$secinfo); else $seclinks = array();

# Get security links from article's categories preg_match_all('/\\[{2}category:(.+?)(\\|.+?)?\\]]/i',$text,$cats); foreach ($cats[1] as $i => $cat) { $cats[1][$i] = $cat = 'Category:'.ucfirst($cat); if (is_object($text = new Article(Title::newFromText($cat)))) { if (preg_match_all('/\\[{2}\\s*security\\s*:\\s*([^\\]]+?)\\s*\\|\\s*([^\\]]*)\\s*\\]{2}/i',$text->fetchContent(0,false,false),$match,PREG_SET_ORDER)) { $seclinks = array_merge($match,$seclinks); addSecurityInfo($match,$secinfo,"this rule is inherited from $cat"); } } }

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

# Validate extracted security against this user/groups $deny = false; if ($security && !in_array('sysop',$groups) && !in_array('director',$groups)) { $security = preg_split("/\\s*,\\s*/",$security); if (!in_array('*',$security)) { $groups[] = ucwords($wgUser->mName); if (count(array_intersect($groups,$security))==0) { $action = 'view'; $deny = true; } } } if ($file) { # Log the download event if any security on file if ($secinfo) { $user = $wgUser->mName; if ($deny) $entry = "User:$user was denied access to \"$file\""; else $entry = "User:$user accessed \"$file\""; $ts = $GLOBALS['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 ($deny) $title = $t; else { header("Content-Type: application/octet-stream"); header("Content-Disposition: attachment; filename=\"$file\""); @readfile("$wgUploadDirectory/$path/$file"); die; } } }

  1. Remove the security links and append security info before wiki-parsing

$wgHooks['ParserBeforeStrip'][] = 'securityBeforeParsing'; function securityBeforeParsing(&$parser,&$text,&$strip_state) { global $deny,$secinfo,$securityAction,$wgHooks; if ($deny) {

$text = "

Sorry, action not permitted!

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

"; $deny = false; $securityAction = 'view'; } else $text = preg_replace("/\\[{2}\\s*:?security\\s*:[^\\]]+?\\]{2}[\r\n]?/i",,$text); if ($secinfo && 0 == $GLOBALS['rsi-done']++ && $securityAction == 'view') $text .= "\n

Padlock.svg

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

\n$secinfo\n

";

return true; }

  1. Add security information item

function addSecurityInfo(&$links,&$info,$comment=) { if ($comment) $comment = "   ($comment)"; foreach ($links as $link) { $a = $link[1] == '*' ? 'Every action' : ucfirst($link[1]); $b = $link[2] == '*' ? 'anybody' : $link[2]; $info .= "*$a requires the user to be $b$comment\n"; } } ?>