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

From Organic Design wiki
(done - now to debug it)
m
 
(31 intermediate revisions by 2 users not shown)
Line 1: Line 1:
<?
+
{{legacy}}
# Simple security extension
+
<source lang="php"><?php
# - Version 2.0
+
# THIS SCRIPT IS OBSOLETE!
# - See [[[[MediaWiki Security]]]] article for installation and usage details
+
# for the newer Version 3.1 (2007-03-14) see:
 +
# http://www.organicdesign.co.nz/SimpleSecurity.php
 +
#
 +
# and also the whole help and needed reference at:
 +
# http://www.mediawiki.org/wiki/Extension:Simple_Security
 +
#
 +
#------------------------------------------------------------------------------------#
 +
#
 +
# - Version 2.1 (2007-03-09)
 +
# - 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
 +
if (!isset($wgSecurityName)) $wgSecurityName = "Security";
 +
if (!isset($wgSecurityDontInheritName)) $wgSecurityDontInheritName = "*$wgSecurityName";
 +
if (!isset($wgSecurityInheritable)) $wgSecurityInheritable = true;
  
 
# Handle moves (since they don't use $action)
 
# Handle moves (since they don't use $action)
Line 10: Line 27:
 
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
 +
$securityDenyRevisionID = 2575; # the current-revision-id of the Template:Action not permitted
 +
$securityCache = array();
 +
$securityItems = array();
  
 
# Handle security on files (needs apache mod-rewrite)
 
# Handle security on files (needs apache mod-rewrite)
Line 21: Line 43:
 
#  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
# - also fills the global $securityItems cache with info to append to the rendered article
+
function &getConnection($i, $fail = true, $groups = array()) {
$securityAfterFetchIndex = count($wgHooks['ArticleAfterFetchContent']);
+
$db =& parent::getConnection($i,$fail,$groups);
$wgHooks['ArticleAfterFetchContent'][] = 'securityAfterFetch';
+
 
function securityAfterFetch(&$this,&$text) {
+
# Create a secure subclass of the database class with extended fetchObject method
global $wgUser,$wgTitle,$securityAction,$securityItems,$securityAfterFetchIndex;
+
$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;
  
# Set up a new local parser object to process security items independently of main rendering process
+
# Get the security info straight from cache if already exists
# - both Security and :Security items are processed and remembered
+
$key = $article->mTitle->getPrefixedURL();
$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
+
if (isset($securityCache[$key])) $items = $securityCache[$key]; else {
# - 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
+
# Set up a new local parser object to process security items independently of main rendering process
foreach ($output->getCategoryLinks() as $cat) {
+
# - both Security and *Security items are processed and remembered
$article = new Article($title = Title::newFromText($cat = "Category:$cat"));
+
$parser = new Parser;
 +
$options = ParserOptions::newFromUser($wgUser);
 +
$parser->setFunctionHook($wgSecurityName,'securityProcessItem');
 +
$parser->setFunctionHook($wgSecurityDontInheritName,'securityProcessItem');
 
$securityItems = array();
 
$securityItems = array();
$parser->parse($article->fetchContent(0,false,false),$title,$options,false,false);
+
$output = $parser->parse($text,$wgTitle,$options,false,false);
foreach ($securityItems as $i) $allItems[] = array($i[0],$i[1],"this rule is inherited from [[:$cat]]");
+
$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;
 
}
 
}
$securityItems = $allItems;
 
  
# Clear content if not viewable
+
# Don't allow any content to be returned if not viewable
if (!securityValidate($wgUser,$securityItems,'view')) $text = "{{Action not permitted|$securityAction}}";
+
if (!securityValidate($wgUser,$items,'view')) $text = "{{Action not permitted|view}}";
 
 
# Put the AfterFetch hook back how it was and exit successfully
 
$wgHooks['ArticleAfterFetchContent'][$securityAfterFetchIndex] = $hookBak;
 
 
return true;
 
return true;
 
}
 
}
  
# 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) {
global $securityAllow,$securityItems,$securityAction;
+
global $wgParser,$wgUser,$securityAllow,$securityCache,$securityAction,$wgSecurityName,$wgSecurityDontInheritName;
if (!$securityAllow) {
+
 
$text = "{{Action not permitted|$securityAction}}";
+
# Only process this hook if it's the main article
$securityAllow = true;
+
if ($parser != $wgParser) return true;
$securityAction = 'view';
+
$key = $parser->mTitle->getPrefixedURL();
}
+
 
if (count($securityItems) && 0 == $GLOBALS['rsi-done']++ && $securityAction == 'view') {
+
# Replace the content with the "Action not permitted" template if action not allowed
$text .= "\n{{Security info|1=\n";$secinfo
+
if (!$securityAllow) $text = "{{Action not permitted|$securityAction}}";
foreach ($securityItems as $i) {
+
 
$a = $i[1] == '*' ? 'Every action' : ucfirst($i[1]);
+
# Append the security info to the article content
$b = $i[2] == '*' ? 'anybody' : $i[2];
+
if (count($securityCache[$key]) && $securityAction == 'view') {
$text .= "*'''$a''' requires the user to be '''$b'''$comment\n";
+
$text .= "\n{{Security info|1=\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}}";
 
$text .= "\n}}";
 
}
 
}
 +
 +
# Ensure the parser doesn't render the security items
 +
$parser->setFunctionHook($wgSecurityName,'securityProcessItem');
 +
$parser->setFunctionHook($wgSecurityDontInheritName,'securityProcessItem');
 
return true;
 
return true;
 
}
 
}
  
# Accumulates security information in a global cache 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) {
$GLOBALS['securityItems'][] = array($a,$b);
+
global $securityItems;
 +
$securityItems[] = array($a,$b);
 
return '';
 
return '';
 
}
 
}
Line 99: Line 183:
 
# 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
 
$security = '';
 
$security = '';
 
foreach ($items as $i) {
 
foreach ($items as $i) {
if ($i[2] == '') $i[2] = 'sysop';
+
if ($i[1] == '') $i[1] = 'sysop';
$actions = preg_split("/\\s*,\\s*/",strtolower($i[1]));
+
$actions = preg_split("/\\s*,\\s*/",strtolower($i[0]));
if ((in_array($action,$actions)) || (in_array('*',$actions) && ($security == ''))) $security = $i[2];
+
if ((in_array($action,$actions)) || (in_array('*',$actions) && ($security == ''))) $security = $i[1];
 
}
 
}
  
Line 123: Line 208:
 
}
 
}
  
# Get security items from this article with a dummy read to activate our AfterDatabaseFetch hook
+
# Read the article content to update security info cache
$securityItems = array();
+
$tmp = new Article(Title::newFromText($securityTitle));
$tmp = new Article(Title::newFromText($t));
 
 
$tmp->fetchContent(0,false,false);
 
$tmp->fetchContent(0,false,false);
$allow = securityValidate($wgUser,$securityItems,$securityAction);
+
$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 its a file, send it back to the client and exit, or block by redirecting to the file's associated article
Line 133: Line 220:
  
 
# Log the download event if any security on file
 
# Log the download event if any security on file
if (count($securityItems)) {
+
if (count($items)) {
 
$user = $wgUser->mName;
 
$user = $wgUser->mName;
$entry = $allow ? "User:$user accessed \"$file\"" : "User:$user was denied access to \"$file\"";
+
$entry = $securityAllow ? "User:$user accessed \"$file\"" : "User:$user was denied access to \"$file\"";
 
$ts = $wgLang->timeanddate(wfTimestampNow(),true);
 
$ts = $wgLang->timeanddate(wfTimestampNow(),true);
 
$la = new Article(Title::newFromText('Download log'));
 
$la = new Article(Title::newFromText('Download log'));
Line 150: Line 237:
  
 
# Return the file to the client if security validates
 
# Return the file to the client if security validates
if ($allow) {
+
if ($securityAllow) {
 
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;
+
} else $title = $securityTitle;
 
}
 
}
 
}
 
}
 
+
?></source>
?>
+
[[Category:Legacy Extensions|SimpleSecurity2.1]]

Latest revision as of 15:17, 27 February 2020

Legacy.svg Legacy: This article describes a concept that has been superseded in the course of ongoing development on the Organic Design wiki. Please do not develop this any further or base work on this concept, this is only useful for a historic record of work done. You may find a link to the currently used concept or function in this article, if not you can contact the author to find out what has taken the place of this legacy item.
<?php
# THIS SCRIPT IS OBSOLETE!
# for the newer Version 3.1 (2007-03-14) see:
# http://www.organicdesign.co.nz/SimpleSecurity.php
#
# and also the whole help and needed reference at:
# http://www.mediawiki.org/wiki/Extension:Simple_Security
#
#------------------------------------------------------------------------------------#
#
# - Version 2.1 (2007-03-09)
# - See http://www.organicdesign/MediaWiki_Security 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
# - 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
if (!isset($wgSecurityName)) $wgSecurityName = "Security";
if (!isset($wgSecurityDontInheritName)) $wgSecurityDontInheritName = "*$wgSecurityName";
if (!isset($wgSecurityInheritable)) $wgSecurityInheritable = true;

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

# 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 = "{{Action not permitted|view}}";
		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 = "{{Action not permitted|$securityAction}}";

		# Append the security info to the article content
		if (count($securityCache[$key]) && $securityAction == 'view') {
			$text .= "\n{{Security info|1=\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;
		}
	}
?>