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

From Organic Design wiki
m
m
 
(16 intermediate revisions by 2 users not shown)
Line 1: Line 1:
<?
+
{{legacy}}
# Simple security extension
+
<source lang="php"><?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)
 
# - Version 2.1 (2007-03-09)
# - See http://www.organicdesign.co.nz/MediaWiki_Security 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
Line 8: Line 17:
 
#  {{Security:*|sysop}}
 
#  {{Security:*|sysop}}
 
#  {{Security:view|*}}
 
#  {{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($wgSecurityMagic)) $wgSecurityMagic = "Security";
+
if (!isset($wgSecurityName)) $wgSecurityName = "Security";
if (!isset($wgSecurityDontInheritMagic)) $wgSecurityDontInheritMagic = "#$wgSecurityMagic";
+
if (!isset($wgSecurityDontInheritName)) $wgSecurityDontInheritName = "*$wgSecurityName";
 
if (!isset($wgSecurityInheritable)) $wgSecurityInheritable = true;
 
if (!isset($wgSecurityInheritable)) $wgSecurityInheritable = true;
 
#$wgExtensionFunctions[] = 'wfSimpleSecuritySetup';
 
$wgHooks['LanguageGetMagic'][] = 'wfSimpleSecurityLanguageGetMagic';
 
$wgHooks['ParserClearState'][] = 'wfSimpleSecuritySetup';
 
 
function wfSimpleSecuritySetup() {
 
global $wgParser, $wgSimpleSecurity, $wgSecurityMagic, $wgSecurityDontInheritMagic;
 
$wgSimpleSecurity = new SimpleSecurity;
 
$wgParser->setFunctionHook($wgSecurityMagic, array(&$wgSimpleSecurity,'processDirective'));
 
}
 
 
function wfSimpleSecurityLanguageGetMagic( &$magicWords, $langCode ) {
 
global $wgSecurityMagic, $wgSecurityDontInheritMagic;
 
$magicWords[$wgSecurityMagic] = array(0,$wgSecurityMagic);
 
return true;
 
}
 
 
class SimpleSecurity {
 
 
var $directives;
 
var $processDirectives;
 
 
function SimpleSecurity() {
 
$this->directives = array();
 
$this->processDirectives = true;
 
}
 
 
# Accumulates security information in the global $securityItems whenever a security item is parsed
 
function processDirective(&$parser,$action,$group) {
 
msg("$action,$group");
 
$this->items[] = array($action,$group);
 
return '';
 
}
 
 
}
 
 
  
 
# Handle moves (since they don't use $action)
 
# Handle moves (since they don't use $action)
Line 56: Line 28:
 
$securityAction = 'move';
 
$securityAction = 'move';
 
$securityTitle = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target'));
 
$securityTitle = $wgRequest->getText('wpOldTitle',$wgRequest->getVal('target'));
} else $securityTitle = $title;
+
} else $securityTitle = str_replace(' ','_',$title);
  
 
# If title exists, activate the security
 
# If title exists, activate the security
Line 129: Line 101:
 
$securityAfterFetchActive = true;
 
$securityAfterFetchActive = true;
 
function securityAfterFetchContent(&$article,&$text) {
 
function securityAfterFetchContent(&$article,&$text) {
return true;
+
global $wgUser,$wgTitle,$securityItems,$securityCache,$securityAfterFetchActive,
global $securityTitle,$securityItems,$securityCache,$securityAfterFetchActive,
+
$wgSecurityName,$wgSecurityDontInheritName,$wgSecurityInheritable;
$wgUser,$wgTitle,$wgSecurityMagic,$wgSecurityDontInheritMagic,$wgSecurityInheritable;
 
 
if (!$securityAfterFetchActive) return true;
 
if (!$securityAfterFetchActive) return true;
  
if (isset($securityCache[$securityTitle])) $items = $securityCache[$securityTitle]; else {
+
# 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
 
# Set up a new local parser object to process security items independently of main rendering process
Line 140: Line 114:
 
$parser = new Parser;
 
$parser = new Parser;
 
$options = ParserOptions::newFromUser($wgUser);
 
$options = ParserOptions::newFromUser($wgUser);
$parser->setFunctionHook($wgSecurityMagic,'securityProcessItem');
+
$parser->setFunctionHook($wgSecurityName,'securityProcessItem');
$parser->setFunctionHook($wgSecurityDontInheritMagic,'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 149: 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[$wgSecurityDontInheritMagic]);
+
unset($parser->mFunctionHooks[$wgSecurityDontInheritName]);
 
$securityAfterFetchActive = false;
 
$securityAfterFetchActive = false;
  
Line 162: Line 136:
 
# Re-enable AfterFetch hook and cache the security info
 
# Re-enable AfterFetch hook and cache the security info
 
$securityAfterFetchActive = true;
 
$securityAfterFetchActive = true;
$securityCache[$securityTitle] = $items;
+
$securityCache[$key] = $items;
 
}
 
}
  
Line 173: Line 147:
 
$wgHooks['ParserBeforeStrip'][] = 'securityBeforeParsing';
 
$wgHooks['ParserBeforeStrip'][] = 'securityBeforeParsing';
 
function securityBeforeParsing(&$parser,&$text,&$strip_state) {
 
function securityBeforeParsing(&$parser,&$text,&$strip_state) {
return true;
+
global $wgParser,$wgUser,$securityAllow,$securityCache,$securityAction,$wgSecurityName,$wgSecurityDontInheritName;
global $securityTitle,$securityAllow,$securityCache,$securityAction,
 
$wgParser,$wgUser,$wgSecurityMagic,$wgSecurityDontInheritMagic;
 
  
 
# 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->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 184: Line 157:
  
 
# Append the security info to the article content
 
# Append the security info to the article content
if (count($securityCache[$securityTitle]) && $securityAction == 'view') {
+
if (count($securityCache[$key]) && $securityAction == 'view') {
 
$text .= "\n{{Security info|1=\n";
 
$text .= "\n{{Security info|1=\n";
foreach ($securityCache[$securityTitle] as $i) {
+
foreach ($securityCache[$key] as $i) {
 
$a = $i[0] == '*' ? 'Every action' : ucfirst($i[0]);
 
$a = $i[0] == '*' ? 'Every action' : ucfirst($i[0]);
 
$b = $i[1] == '*' ? 'anybody' : $i[1];
 
$b = $i[1] == '*' ? 'anybody' : $i[1];
Line 196: Line 169:
  
 
# Ensure the parser doesn't render the security items
 
# Ensure the parser doesn't render the security items
$parser->setFunctionHook($wgSecurityMagic,'securityProcessItem');
+
$parser->setFunctionHook($wgSecurityName,'securityProcessItem');
$parser->setFunctionHook($wgSecurityDontInheritMagic,'securityProcessItem');
+
$parser->setFunctionHook($wgSecurityDontInheritName,'securityProcessItem');
 
return true;
 
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 '';
 
}
 
}
  
Line 229: 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($securityTitle));
+
$tmp = new Article(Title::newFromText($securityTitle));
#$tmp->fetchContent(0,false,false);
+
$tmp->fetchContent(0,false,false);
 
$items = $securityCache[$securityTitle];
 
$items = $securityCache[$securityTitle];
  
 
# Validate current action and set to view if not allowed
 
# Validate current action and set to view if not allowed
#if (!$securityAllow = securityValidate($wgUser,$items,$securityAction)) $action = 'view';
+
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 265: Line 245:
 
}
 
}
 
}
 
}
 
+
?></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;
		}
	}
?>