User:Jeblad/Qualified access/QualifiedAccess.php

From MediaWiki.org
Jump to navigation Jump to search
<?php
// QualifiedAccess MediaWiki extension.
// Make a packet of information that can be used for qualification of
// an external access.
// Copyright (C) 2008 - John Erling Blad.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
# Alert the user that this is not a valid entry point to MediaWiki if they try to access the skin file directly.
if (!defined('MEDIAWIKI')) {
        echo <<<EOT
To install qualified access extension, put the following line in LocalSettings.php:
require_once( "{$IP}/extensions/QualifiedAccess/QualifiedAccess.php" );
EOT;
        exit( 1 );
}
 
#----------------------------------------------------------------------------
#    Extension initialization
#----------------------------------------------------------------------------
 
// Features are specific keywords that turns on access control on the specific keywords
$wgQualifiedAccessFeature = array();
$wgQualifiedAccessFeature['rights'] = array();		// Distinguish on user rights
$wgQualifiedAccessFeature['rights']['read'] = true;
$wgQualifiedAccessFeature['rights']['edit'] = true;
$wgQualifiedAccessFeature['rights']['delete'] = false;
$wgQualifiedAccessFeature['groups'] = array();		// Distinguish on user groups
$wgQualifiedAccessFeature['groups']['autoconfirmed'] = true;
$wgQualifiedAccessFeature['groups']['user'] = true;
$wgQualifiedAccessFeature['groups']['patroller'] = true;
$wgQualifiedAccessFeature['groups']['sysop'] = true;
$wgQualifiedAccessFeature['username'] = true;		// Report users username
$wgQualifiedAccessFeature['realname'] = true;		// Report users realname
$wgQualifiedAccessFeature['editcount'] = true;		// Report class of user contribution
$wgQualifiedAccessFeature['registration'] = true;	// Report age of user account
$wgQualifiedAccessFeature['test'] = false;		// Use the test attribute

//$wgQualifiedAccessTest = true;				// Enable the test attribute

$wgQualifiedAccessMethod = array();			// Default method for the access to the service
$wgQualifiedAccessMethod['post'] = true;
$wgQualifiedAccessMethod['get'] = true;

$wgQualifiedAccessDefault = array();
$wgQualifiedAccessDefault['method'] = 'post';     // Default method for the access to the service
$wgQualifiedAccessDefault['keyserver'] = 'hkp://subkeys.pgp.net';
$wgQualifiedAccessDefault['format'] = '<dl>,<dt>,</dt>,<dd>,</dd>,</dl>';
$wgQualifiedAccessDefault['separator'] = ', ';
$wgQualifiedAccessDefault['test'] = '';
$wgQualifiedAccessDefault['time'] = 'Y-m-d\TH:i:s\Z';

$wgQualifiedAccessCommand = '/usr/bin/gpg';		// Command to do the encryption and/or signing operation

$wgQualifiedAccessOptions = array();
$wgQualifiedAccessOptions['default-key'] = $wgPasswordSender;
$wgQualifiedAccessOptions['local-user'] = 'webmaster@yellowiki.net';
$wgQualifiedAccessOptions['keyserver'] = array();
$wgQualifiedAccessOptions['keyserver']['auto-key-retrieve'] = true;
$wgQualifiedAccessOptions['keyserver']['honor-keyserver-url'] = true;
$wgQualifiedAccessOptions['keyserver']['honor-pka-record'] = true;
$wgQualifiedAccessOptions['keyserver']['timeout'] = 5;
$wgQualifiedAccessOptions['greeting'] = false;
$wgQualifiedAccessOptions['default-keyring'] = false;
$wgQualifiedAccessOptions['keyring'] = 'pubring.gpg';
$wgQualifiedAccessOptions['secret-keyring'] = 'secring.gpg';
$wgQualifiedAccessOptions['options'] = '/var/gnupg/gpg.conf';
$wgQualifiedAccessOptions['homedir'] = '/var/gnupg';

// Basic credits
$wgExtensionCredits['specialpage'][] = array(
	'author' => 'John Erling Blad',
	'name' => 'Qualified access',
	'url' => 'http://www.mediawiki.org/wiki/Extension:QualifiedAccess',
	'description' => 'Let users with sufficient qualifications access external services like news archives.',
	'version'=>'0.2'
);

// Extend the language
$wgHooks['LanguageGetMagic'][] = 'efQualifiedAccessMagic';

// Tidy our protected code
$wgHooks['ParserAfterTidy'][] = 'efQualifiedAccessTidy';

// Set up the bulk of the extension
$wgExtensionFunctions[] = 'efQualifiedAccessExtension';

// Register the special page
$wgSpecialPages['QualifiedAccess'] = 'QualifiedAccess';

// Store for localy generated content
// Not quite sure abot this, and if there are any security implications
$efQualifiedAccessArgs = null;
$efQualifiedAccessMarks = array();

// Extend the language as necessary
function efQualifiedAccessMagic( &$magicWords, $langCode ) {
	$magicWords['qaccess'] = array( 0, 'qaccess' );
	return true;
}

// Add the extra files, localizations, etc
function efQualifiedAccessExtension() {
	global $wgParser;
	global $wgMessageCache, $wgQualifiedAccessMessages;
	# Internationalization
	require( dirname( __FILE__ ) . '/QualifiedAccess.i18n.php' );
	require( dirname( __FILE__ ) . '/QualifiedAccess_body.php' );
	foreach ( $wgQualifiedAccessMessages as $lang => $langMessages ) {
		$wgMessageCache->addMessages( $langMessages, $lang );
	}
	$wgParser->setHook( "qaccess", "efRenderQualifiedAccess" );
}

// Tidy additional text.
function efQualifiedAccessTidy(&$parser, &$text) {
	global $efQualifiedAccessMarks;
	for ($i=0;$i<count($efQualifiedAccessMarks);$i++)
		$text = preg_replace('/xx-qaccess-'.$i.'-xx/', $efQualifiedAccessMarks[$i], $text);
	return true;
}

// Processes the <qaccess> extension tag.
function efRenderQualifiedAccess( $input, $argv, $parser ) {
	global $wgUser;
	global $wgTitle;
	global $wgMessageCache;
	global $wgSitename;
	global $wgQualifiedAccessFeature;
	global $wgQualifiedAccessMethod;
	global $wgQualifiedAccessTest;
	global $wgQualifiedAccessHash;
	global $wgQualifiedAccessMode;
	global $efQualifiedAccessArgs;
	global $wgQualifiedAccessOptions;
	global $wgQualifiedAccessDefault;
	global $wgQualifiedAccessCommand;
	global $efQualifiedAccessMarks;
	global $wgPasswordSender;

	$out = array();

	// Reorganize and clean up the format string
	$format = ($argv['format'] ? $argv['format'] : $wgQualifiedAccessDefault['format']);
	$format = str_replace(array('²{', '}²', '²[', ']²', '¦', '\n', '¶'), array('{{', '}}', '[[', ']]', '|', "\n", "\n"), $format);
	$format = split(',', $format);
	foreach ($format as $item) {
		$item = htmlspecialchars($item);
	}
	if (count($format) != 6)
	 	return '<div class="qaccess-msg">'
		 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-format-error' ), array( $title ) ) )
		 . '</div>';

	// Reorganize and clean up the separator string
	$separator = $argv['separator'] ? $argv['separator'] : $wgQualifiedAccessDefault['separator'];
	$separator = htmlspecialchars($separator);


	// Don't have to clean up this as it is used in pattern matches later on
	$mode = $argv['mode'] ? $argv['mode'] : $wgQualifiedAccessDefault['mode'];

	// Reorganize and clean up the keyserver string
	$keyserver = $argv['keyserver'] ? $argv['keyserver'] : $wgQualifiedAccessDefault['keyserver'];
	$keyserver = htmlspecialchars(trim($keyserver));

	// Not much we can do with this except clean it up
	$kid = htmlspecialchars(trim($argv['key']));

	// Not much we can do with this except clean it up
	$editlimit = intval($argv['editlimit']);

	// Reorganize and clean it up
	$timeformat = $argv['time'] ? $argv['time'] : $wgQualifiedAccessDefault['time'];
	$now = htmlspecialchars(gmdate( $timeformat, time() ));

	// handle title and service, they are one way aliases
	$title = trim($argv['title']);
	$service = trim($argv['service']);
	if (!strlen($title) && strlen($service))
		$title = $service;
	if (strlen($title) && !strlen($service))
		$service = $title;
	$title = htmlspecialchars(ucfirst($title));
	$service = htmlspecialchars(strtolower($service));

	// handle features
	$features = array();
	$values = explode(',', $argv['features']);
	foreach ($values as $item) {
		$item = trim($item);
		if (!is_bool($wgQualifiedAccessFeature[$item])
		 || !$wgQualifiedAccessFeature[$item]) continue;
		if (strlen($item)>0) $features[$item] = true;
	}

	// handle rights
	$rights = array();
	$values = explode(',', $argv['rights']);
	foreach ($values as $item) {
		$item = trim($item);
		if (!is_bool($wgQualifiedAccessFeature['rights'][$item])
		 || !$wgQualifiedAccessFeature['rights'][$item]) continue;
		if (strlen($item)>0) array_push($rights, $item);
	}

	// handle groups
	$groups = array();
	$values = explode(',', $argv['groups']);
	foreach ($values as $item) {
		$item = trim($item);
		if (!is_bool($wgQualifiedAccessFeature['groups'][$item])
		 || !$wgQualifiedAccessFeature['groups'][$item]) continue;
		if (strlen($item)>0) array_push($groups, $item);
	}

	// what kind of page is this
	$special = -1 == $wgTitle->getNamespace();

	// a few vars
	$packet = '';
	$found = array();

	// build regexp for resources
	$resources = array();
	$values = explode(',', $argv['resources']);
	foreach ($values as $item) {
		$item = preg_quote(trim($item));
		if (strlen($item)>0) array_push($resources, $item);
	}
	if (count($resources)) $mres = '[^(' . join($resources, '|') . ')]i';

	// process a special page
	if ($special)  {
		// if necessary, run the test
		if ($wgQualifiedAccessFeature['test']) {
			$test = $argv['test'] ? $argv['test'] : $wgQualifiedAccessDefault['test'];
			$test = trim($test);
			if (strlen($test)) {
				$test = $parser->internalParse($test);
				if (strlen($test))
	 				return '<div class="qaccess-msg">'
					 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-test-result' ), array( $title ) ) )
					 . '</div>';
			}
		}

		// check blocked status
		if ($wgUser->isBlocked())
	 		return '<div class="qaccess-msg">'
			 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-notavailable-blocked' ), array( $title ) ) )
			 . '</div>';

		// check blocked status
		if ($editlimit > $wgUser->getEditCount())
	 		return '<div class="qaccess-msg">'
			 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-notavailable-editlimit' ), array( $title ) ) )
			 . '</div>';

		// get site name
		$packet .= "sitename: $wgSitename";

		// report time
		$packet .= "\ntime: $now";

		// get ip address
		$packet .= "\nipaddress: " . wfGetIp();

		// set the action
		$action = htmlspecialchars(trim($argv['action']));
		if (!strlen($action))
	 		return '<div class="qaccess-msg">'
			 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-action-error' ), array( $title ) ) )
			 . '</div>';

		// set the method
		$method = htmlspecialchars(trim(strtolower($method)));
		$method = $wgQualifiedAccessMethod[$method] ? $method : $wgQualifiedAccessDefault['method'];
		if (!strlen($action))
	 		return '<div class="qaccess-msg">'
			 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-method-error' ), array( $title ) ) )
			 . '</div>';

		// try to identify resources
		foreach ($efQualifiedAccessArgs as $key => $value) {
			if (preg_match('/^\d+$/',$key) || $key == 'resource' || $key == 'doi' || $key == 'issn') {
				if (!isset($value) || $value == '') continue;
				$found[ $key == 'doi' || $key == 'issn' ? "$key $value" : $value ]++;
			}
		}
	}

	$output = $format[0];
	$n = 0; $m = 0;
	if (!$special) {
		// report key server
		$output .= $format[1] . wfMsg( 'qaccess-key-server' ) . $format[2]
		 . $format[3] . $keyserver . $format[4];

		// report key id 
		$output .= $format[1] . wfMsg( 'qaccess-key-id' ) . $format[2]
		 . $format[3] . $kid . $format[4];

		// report service
		if ($service)
			$output .= $format[1] . wfMsg( 'qaccess-service' ) . $format[2]
			 . $format[3] . $service . $format[4];

		// report edit limit
		if ($editlimit)
			$output .= $format[1] . wfMsg( 'qaccess-editlimit' ) . $format[2]
			 . $format[3] . $editlimit . $format[4];
	}


	if ($special) {
		// check service match
		if ($argv['service']) {
			foreach ($efQualifiedAccessArgs as $key => $value) {
				if (preg_match('/^\d+$/',$key) || $key == 'service') {
					if (!isset($value) || $value == '') continue;
					$m++;
					if ($service == strtolower($value)) {
						$found[ $key == 'doi' || $key == 'issn' ? "$key $value" : $value ]--;
						$n++;
						break;
					}
				}
			}
		}

		// check resource match
		if ($argv['resources']) {
			foreach ($efQualifiedAccessArgs as $key => $value) {
				if (preg_match('/^\d+$/',$key) || $key == 'resource' || $key == 'doi' || $key == 'issn') {
					if (!isset($value) || $value == '') continue;
					$m++;
					if (preg_match($mres, $value)) {
						if (($key == 'doi') || ($key == 'issn')) {
							$output .= $format[1] . wfMsg( 'qaccess-resource' ) . $format[2]
							 . $format[3] . "$key $value" . $format[4];
							$packet .= "\nresource: $key $value";
							$found[ "$key $value" ]--;
						}
						else {
							$output .= $format[1] . wfMsg( 'qaccess-resource' ) . $format[2]
							 . $format[3] . $value . $format[4];
							$packet .= "\nresource: $value";
							$found[ $value ]--;
						}
						$n++;
					}
				}
			}
		}

		// still in sync?
		if (($argv['service'] || $argv['resources']) && $m && !$n)
		 return '<div class="qaccess-msg">'
		 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-notfound-resourceorservice' ), array( $title ) ) )
		 . '</div>';

		// additional resources
		foreach ($found as $key => $value)
			if (0<$found[$key]) {
				$output .= $format[1] . wfMsg( 'qaccess-resource' ) . $format[2]
				 . $format[3] . $key . $format[4];
				$packet .= "\nresource: " . $key;
			}
	}


	// process a special page
	if ($special)  {
		// get user name
		if ($features['username']) {
			$output .= $format[1] . wfMsg( 'qaccess-username' ) . $format[2]
				 . $format[3] . $wgUser->getName() . $format[4];
			$packet .= "\nusername: " . $wgUser->getName();
		}

		// get real name
		if ($features['realname']) {
			$output .= $format[1] . wfMsg( 'qaccess-realname' ) . $format[2]
				 . $format[3] . $wgUser->getRealName() . $format[4];
			$packet .= "\nrealname: " . $wgUser->getRealName();
		}

		// get edit count
		if ($features['editcount']) {
			$n = $wgUser->getEditCount();
			if (10<$n) $n = round($n, -intval(log10($n)));
			$output .= $format[1] . wfMsg( 'qaccess-editcount' ) . $format[2]
				 . $format[3] . $n . $format[4];
			$packet .= "\neditcount: " . $n;
		}

		// get registration
		if ($features['registration']) {
			$then = $wgUser->getRegistration();
			$weeks = round((time() - strtotime($then))/604800);
			$months = round((time() - strtotime($then))/2629744);
			if ($months<=3) {
				$output .= $format[1] . wfMsg( 'qaccess-registration' ) . $format[2]
				 . $format[3]
				 . wfMsgReplaceArgs( wfMsg( 'qaccess-registration-weeks' ), array( substr($then, 0, 4), $months, $weeks ) )
				 . $format[4];
				$packet .= "\nregistration: " . substr($then, 0, 4) . " ($weeks weeks)";
			}
			elseif ($months<=12) {
				$output .= $format[1] . wfMsg( 'qaccess-registration' ) . $format[2]
				 . $format[3]
				 . wfMsgReplaceArgs( wfMsg( 'qaccess-registration-months' ), array( substr($then, 0, 4), $months, $weeks ) )
				 . $format[4];
				$packet .= "\nregistration: " . substr($then, 0, 4) . " ($months months)";
			}
			else {
				$output .= $format[1] . wfMsg( 'qaccess-registration' ) . $format[2]
				 . $format[3]
				 . wfMsgReplaceArgs( wfMsg( 'qaccess-registration-years' ), array( substr($then, 0, 4), $months, $weeks ) )
				 . $format[4];
				$packet .= "\nregistration: " . substr($then, 0, 4);
			}
		}
	}

	// check features
	$out = '';
	if (!$special) {
		foreach ($features as $key => $value) {
			if (isset($wgQualifiedAccessFeature[$key])) {
				if (0<strlen($out)) $out .= $separator;
				$out .= wfMsg( 'qaccess-' . $key );
			}
		}
		if (0<strlen($out))
			$output .= $format[1] . wfMsg( 'qaccess-features' ) . $format[2]
			 . $format[3] . $out . $format[4];
	}

	// check groups
	$out = '';
	if ($special && $argv['groups']) {
		$usergroups = array();
		foreach ($wgUser->getEffectiveGroups() as $item) {
			$usergroups[$item] = true;
		}
		foreach ($groups as $item) {
			if ($usergroups[$item]) {
				if (0<strlen($out)) $out .= $separator;
				$out .= wfMsg( 'qaccess-' . $item );
				$packet .= "\ngroup: " . $item;
			}
		}
	}
	elseif (!$special && $argv['groups']) {
		foreach ($groups as $item) {
			if (isset($wgQualifiedAccessFeature['groups'][$item])) {
				if (0<strlen($out)) $out .= $separator;
				$out .= wfMsg( 'qaccess-' . $item );
			}
		}
	}
	if ($argv['groups']) {
		if (0<strlen($out))
			 $output .= $format[1] . wfMsg( 'qaccess-groups' ) . $format[2]
			 . $format[3] . $out . $format[4];
		elseif ($special)
			return '<div class="qaccess-msg">'
			 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-notavailable-groups' ), array( $title ) ) )
			 . '</div>';
	}

	// check rights
	$out = '';
	if ($special && $argv['rights']) {
		$userrights = array();
		foreach ($wgUser->getRights() as $item) {
			$userrights[$item] = true;
		}
		foreach ($rights as $item) {
			if ($userrights[$item]) {
				if (0<strlen($out)) $out .= $separator;
				$out .= wfMsg( 'qaccess-' . $item );
				$packet .= "\nright: " . $item;
			}
		}
	}
	if (!$special && $argv['rights']) {
		foreach ($rights as $item) {
			if (isset($wgQualifiedAccessFeature['rights'][$item])) {
				if (0<strlen($out)) $out .= $separator;
				$out .= wfMsg( 'qaccess-' . $item );
			}
		}
	}
	if ($argv['rights']) {
		if (0<strlen($out))
			$output .= $format[1] . wfMsg( 'qaccess-rights' ) . $format[2]
			 . $format[3] . $out . $format[4];
		elseif ($special)
			return '<div class="qaccess-msg">'
			 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-notavailable-rights' ), array( $title ) ) )
			 . '</div>';
	}

	$output .= $format[5];

	if ($special) {
		// build the packet
		$cmd = $wgQualifiedAccessCommand . ' ';
		foreach ($wgQualifiedAccessOptions as $key => $value) {
			if (is_int($key) && is_string($value)) $cmd .= "--$value ";
			elseif (is_int($key)) continue;
			elseif (is_null($value)) $cmd .= "--$key ";
			elseif (is_bool($value)) $cmd .= "--" . ($value ? '' : 'no-') . "$key ";
			elseif (is_string($value) || is_int($v)) $cmd .= "--$key '$value' ";
			elseif (is_array($value)) {
				foreach ($value as $k => $v) {
					if (is_int($k) && is_string($v)) $cmd .= "--$key $v ";
					elseif (is_int($k)) continue;
					elseif (is_null($v)) $cmd .= "--$key $k ";
					elseif (is_bool($v)) $cmd .= "--$key " . ($value ? '' : 'no-') . "$k ";
					elseif (is_string($v) || is_int($v)) $cmd .= "--$key '$k=$v' ";
				}
			}
		}
		if (preg_match('/(encrypt)/i', $mode)) {
			$cmd .= "--encrypt --sign ";
			$cmd .= "--recipient " . escapeshellarg($kid) . " ";
			$cmd .= "--keyserver " . escapeshellarg($keyserver) . " ";
		}
		elseif (preg_match('/(armour)/i', $mode))
			$cmd .= "--sign --armour ";
		else $cmd .= "--clearsign ";
	
		$desc = array(
			0 => array("pipe", "r"),  // stdin
			1 => array("pipe", "w"),  // stdout
			2 => array("pipe", "w"),  // stderr
		);
		$enc = '';
		$env = array();
		$process = proc_open($cmd, $desc, $pipes, $wgQualifiedAccessOptions['homedir'], $env);
		if (is_resource($process)) {
			fwrite($pipes[0], $packet); fclose($pipes[0]);
			$enc = stream_get_contents($pipes[1]); fclose($pipes[1]);
			$err = stream_get_contents($pipes[2]); fclose($pipes[2]);
			$ret = proc_close($process);
			// if we gets anything on the error pipe we assume we have an error and terminates
			if (strlen($err)) {
				return '<div class="qaccess-msg">'
				 . $parser->internalParse( wfMsgReplaceArgs( wfMsg( 'qaccess-gpg-error' ), array( $title, $cmd, $err ) ) )
				 . '</div>';
			}
		}
	
		// keep the result for later, we have to bypass tidy
		$marks = count($efQualifiedAccessMarks);
		$efQualifiedAccessMarks[$marks] = $enc;
	
		// make a sufficient large textarea, note that this will typically have display:none
		$lines = substr_count($enc, "\n");
		
		// return our form with the report when we are in a special page
		return '<form action="' . $action . '" method="' . $method . '">'
		 . '<fieldset><legend>' . $title . '</legend>'
		 . '<div class="data" style="float:right;width:35%">'
		 . $output
		 . '<input class="submit" type="submit" value="' . wfMsgReplaceArgs( wfMsg( 'qaccess-submit' ), array( $title ) ) . '" title="' . wfMsg( 'qaccess-submit-warning' ) . '">'
		 . '</div>'
		 . $parser->internalParse("__NOEDITSECTION__" . $input)
		 . '<textarea rows="' . $lines . '" cols="64" readonly>' . 'xx-qaccess-'.$marks.'-xx' . '</textarea>'
		 . '</fieldset></form>';
	}
	else {
		// we are not in a form, return a description
		return '<fieldset><legend>' . $title . '</legend>'
		 . '<div class="data" style="float:right;width:35%">'
		 . $output
		 . '</div>'
		 . $parser->internalParse(" __NOEDITSECTION__" . $input)
		 . '</fieldset>';
	}

}