<?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>';
}
}