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

From Organic Design wiki
(version 2.0 ready, still not working for search and diff though)
m
 
(29 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
 
# These globals can be set in LocalSettings to adjust the Simple Security attributes
 
if (!isset($wgSecurityName)) $wgSecurityName = "Security";
 
if (!isset($wgSecurityName)) $wgSecurityName = "Security";
if (!isset($wgSecurityInheritName)) $wgSecurityInheritName = "*$wgSecurityName";
+
if (!isset($wgSecurityDontInheritName)) $wgSecurityDontInheritName = "*$wgSecurityName";
 
if (!isset($wgSecurityInheritable)) $wgSecurityInheritable = true;
 
if (!isset($wgSecurityInheritable)) $wgSecurityInheritable = true;
  
 
# Handle moves (since they don't use $action)
 
# Handle moves (since they don't use $action)
 
$securityAction = $action == 'submit' ? 'edit' : strtolower($action);
 
$securityAction = $action == 'submit' ? 'edit' : strtolower($action);
#if (isset($_REQUEST['wpPreview'])) $securityAction = 'view';
 
 
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 31: 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
 +
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,$wgSecurityInheritName,$wgSecurityInheritable;
+
$wgSecurityName,$wgSecurityDontInheritName,$wgSecurityInheritable;
 
if (!$securityAfterFetchActive) return true;
 
if (!$securityAfterFetchActive) return true;
  
 
# 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 56: Line 115:
 
$options = ParserOptions::newFromUser($wgUser);
 
$options = ParserOptions::newFromUser($wgUser);
 
$parser->setFunctionHook($wgSecurityName,'securityProcessItem');
 
$parser->setFunctionHook($wgSecurityName,'securityProcessItem');
$parser->setFunctionHook($wgSecurityInheritName,'securityProcessItem');
+
$parser->setFunctionHook($wgSecurityDontInheritName,'securityProcessItem');
 
$securityItems = array();
 
$securityItems = array();
 
$output = $parser->parse($text,$wgTitle,$options,false,false);
 
$output = $parser->parse($text,$wgTitle,$options,false,false);
Line 64: Line 123:
 
# - stop checking *Security because they shouldn't inherit
 
# - stop checking *Security because they shouldn't inherit
 
# - disable this AfterDatabaseFetch hook while reading category contents
 
# - disable this AfterDatabaseFetch hook while reading category contents
unset($parser->mFunctionHooks[$wgSecurityInheritName]);
+
unset($parser->mFunctionHooks[$wgSecurityDontInheritName]);
 
$securityAfterFetchActive = false;
 
$securityAfterFetchActive = false;
  
 
# 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 85: Line 144:
 
}
 
}
  
# 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 $wgParser,$wgUser,$securityAllow,$securityCache,$securityAction,$wgSecurityName,$wgSecurityInheritName;
+
global $wgParser,$wgUser,$securityAllow,$securityCache,$securityAction,$wgSecurityName,$wgSecurityDontInheritName;
  
 
# 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 111: Line 170:
 
# Ensure the parser doesn't render the security items
 
# Ensure the parser doesn't render the security items
 
$parser->setFunctionHook($wgSecurityName,'securityProcessItem');
 
$parser->setFunctionHook($wgSecurityName,'securityProcessItem');
$parser->setFunctionHook($wgSecurityInheritName,'securityProcessItem');
+
$parser->setFunctionHook($wgSecurityDontInheritName,'securityProcessItem');
 
return true;
 
return true;
 
}
 
}
  
 
# 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 124: 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
Line 149: Line 209:
  
 
# 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 182: Line 242:
 
@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;
		}
	}
?>