Extension:SimpleSecurity2.1.php

From Organic Design wiki
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;
		}
	}
?>