Difference between revisions of "Extension:SimpleSecurity2.1.php"
(version 2.0 - redoing almost from scratch, much more efficient and stronger!) |
m |
||
Line 1: | Line 1: | ||
<? | <? | ||
− | # Simple security extension | + | # Simple security extension |
− | + | # - Version 2.0 | |
# - 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) | ||
+ | $securityAction = $action == 'submit' ? 'edit' : strtolower($action); | ||
+ | if ($title=='Special:Movepage' && $action=='submit') { | ||
+ | $securityAction = 'move'; | ||
+ | $t = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target')); | ||
+ | } else $t = $title; | ||
+ | |||
+ | # If title exists, activate the security | ||
+ | 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 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 ($file) { | ||
+ | |||
+ | # Log the download event if any security on file | ||
+ | if (count($securityItems)) { | ||
+ | $user = $wgUser->mName; | ||
+ | $entry = $allow ? "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 ($allow) { | ||
+ | header("Content-Type: application/octet-stream"); | ||
+ | header("Content-Disposition: attachment; filename=\"$file\""); | ||
+ | @readfile("$wgUploadDirectory/$path/$file"); | ||
+ | die; | ||
+ | } else $title = $t; | ||
+ | |||
+ | |||
+ | } | ||
+ | |||
+ | # 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\n}}"; | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | |||
+ | # 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"; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | |||
# Asseses security after raw content fetched from database and clears if not readable | # Asseses security after raw content fetched from database and clears if not readable | ||
Line 11: | Line 99: | ||
$wgHooks['ArticleAfterFetchContent'][] = 'securityAfterFetch'; | $wgHooks['ArticleAfterFetchContent'][] = 'securityAfterFetch'; | ||
function securityAfterFetch(&$this,&$text) { | function securityAfterFetch(&$this,&$text) { | ||
− | global $wgUser,$wgTitle,$securityItems,$securityAfterFetchIndex; | + | global $wgUser,$wgTitle,$securityAction,$securityItems,$securityAfterFetchIndex; |
+ | |||
+ | # 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; | $parser = new Parser; | ||
$options = ParserOptions::newFromUser($wgUser); | $options = ParserOptions::newFromUser($wgUser); | ||
Line 35: | Line 126: | ||
foreach ($securityItems as $i) $allItems[] = array($i[0],$i[1],"this rule is inherited from [[:$cat]]"); | 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; | ||
+ | } | ||
+ | |||
+ | # Accumulates security information in a global cache whenever a security item is parsed | ||
+ | function securityProcessItem(&$this,$a,$b) { | ||
+ | $GLOBALS['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) { | ||
# Resolve permission for this action from the extracted security links | # Resolve permission for this action from the extracted security links | ||
$security = ''; | $security = ''; | ||
− | foreach ($ | + | foreach ($items as $i) { |
− | if ($ | + | if ($i[2] == '') $i[2] = 'sysop'; |
− | $actions = preg_split("/\\s*,\\s*/",strtolower($ | + | $actions = preg_split("/\\s*,\\s*/",strtolower($i[1])); |
− | if (in_array($ | + | if ((in_array($action,$actions)) || (in_array('*',$actions) && ($security == ''))) $security = $i[2]; |
− | |||
} | } | ||
# Validate extracted security against this user/groups | # Validate extracted security against this user/groups | ||
− | $groups = $ | + | $groups = $user->getGroups(); |
foreach($groups as $k => $v) $groups[$k] = strtolower($v); | foreach($groups as $k => $v) $groups[$k] = strtolower($v); | ||
$deny = false; | $deny = false; | ||
Line 52: | Line 160: | ||
$security = preg_split("/\\s*,\\s*/",$security); | $security = preg_split("/\\s*,\\s*/",$security); | ||
if (!in_array('*',$security)) { | if (!in_array('*',$security)) { | ||
− | $groups[] = | + | $groups[] = ucfirst($user->mName); |
− | if (count(array_intersect($groups,$security))==0) | + | if (count(array_intersect($groups,$security))==0) $deny = true; |
− | |||
− | |||
− | |||
} | } | ||
} | } | ||
− | + | return !$deny; | |
− | |||
− | |||
} | } | ||
− | |||
− | |||
− | |||
− | |||
− | |||
?> | ?> |
Revision as of 10:16, 3 March 2007
<?
- Simple security extension
- - Version 2.0
- - See [[MediaWiki Security]] article 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
- Handle moves (since they don't use $action)
$securityAction = $action == 'submit' ? 'edit' : strtolower($action); if ($title=='Special:Movepage' && $action=='submit') { $securityAction = 'move'; $t = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target')); } else $t = $title;
- If title exists, activate the security
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 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 ($file) {
# Log the download event if any security on file if (count($securityItems)) { $user = $wgUser->mName; $entry = $allow ? "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 ($allow) { header("Content-Type: application/octet-stream"); header("Content-Disposition: attachment; filename=\"$file\""); @readfile("$wgUploadDirectory/$path/$file"); die; } else $title = $t;
}
# 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 = "
Sorry, action not permitted!
Your user rights do not permit the $securityAction action to be performed on this article.
";
$securityAllow = true;
$securityAction = 'view';
}
if (count($securityItems) && 0 == $GLOBALS['rsi-done']++ && $securityAction == 'view') {
$text .= "\n
";
} return true; }
- 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"; } }
- Asseses security after raw content fetched from database and clears if not readable
- - also fills the global $securityItems cache with info to append to the rendered article
$securityAfterFetchIndex = count($wgHooks['ArticleAfterFetchContent']); $wgHooks['ArticleAfterFetchContent'][] = 'securityAfterFetch'; function securityAfterFetch(&$this,&$text) { global $wgUser,$wgTitle,$securityAction,$securityItems,$securityAfterFetchIndex;
# 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('Security','securityProcessItem'); $parser->setFunctionHook(':Security','securityProcessItem'); $securityItems = array(); $output = $parser->parse($text,$wgTitle,$options,false,false); $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 = "
Sorry, action not permitted!
Your user rights do not permit the $securityAction action to be performed on this article.
";
# Put the AfterFetch hook back how it was and exit successfully $wgHooks['ArticleAfterFetchContent'][$securityAfterFetchIndex] = $hookBak; return true; }
- Accumulates security information in a global cache whenever a security item is parsed
function securityProcessItem(&$this,$a,$b) { $GLOBALS['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) {
# Resolve permission for this action from the extracted security links $security = ; foreach ($items as $i) { if ($i[2] == ) $i[2] = 'sysop'; $actions = preg_split("/\\s*,\\s*/",strtolower($i[1])); if ((in_array($action,$actions)) || (in_array('*',$actions) && ($security == ))) $security = $i[2]; }
# 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; }
?>