Open main menu

Extension:Permission

MediaWiki extensions manual
OOjs UI icon advanced.svg
Permission
Release status: unmaintained
Implementation User rights
Description Extends native MediaWiki protection to allow article's viewability to be restricted
Author(s) Alexandre Porto da Silva (Alexandre Portotalk)
Latest version 11.06.30 alpha (2011-06-30)
MediaWiki 1.17+
License GPL
Download copy/paste
Translate the Permission extension if it is available at translatewiki.net
Check usage and version matrix.

InstallEdit

Copy & paste:

Include on LocalSettings.php:

require_once( "$IP/extensions/Permission/Permission.php" );

Permission.phpEdit

<?php
# Credits: http://www.mediawiki.org/wiki/Manual:Tag_extensions#How_do_I_get_my_extension_to_show_up_on_Special:Version.3F
/**
 * Permission - permissions for reading and editing based on categories.
 *
 * To activate this extension, add the following into your LocalSettings.php file:
 * require_once( "$IP/extensions/Permission/Permission.php" );
 *
 * @ingroup Extensions
 * @author Alexandre Porto da Silva
 * @link http://www.mediawiki.org/wiki/Extension:Permission
 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
 */
 
/**
 * Protect against register_globals vulnerabilities.
 * This line must be present before any global variable is referenced.
 */
if( !defined( 'MEDIAWIKI' ) ) {
        echo( "This is an extension to the MediaWiki package and cannot be run standalone.\n" );
        die( -1 );
}

define( 'PERMISSION_VERSION', '11.06.26 alpha' );
define( 'PERMISSION_TIME', 10 ); // time in seconds for cache: "Permission.session.php"
$wgPermissionHidden = true;

# Extension credits that will show up on Special:Version    
$wgExtensionCredits['permission'][] = array(
	'path'           => __FILE__,
	'name'           => 'Permission',
	'version'        => PERMISSION_VERSION,
	'author'         => 'Alexandre Porto da Silva', 
	'url'            => 'http://www.mediawiki.org/wiki/Extension:Permission',
	'descriptionmsg' => 'permission-desc',
	'description'    => 'permission'
);

# Manual: http://www.mediawiki.org/wiki/Manual:Special_pages#The_Body_File
$dir = dirname(__FILE__);
$wgExtensionMessagesFiles['Permission'] = "$dir/Permission.i18n.php";
require_once( "$dir/Permission.session.php" );
require_once( "$dir/Permission.hooks.php" );

class Permission {

	private static function getRequiredCategories( $parents ) {
		$stack = array();
		foreach ( $parents as $parent => $current ) {
			$token = substr( $parent, 0, 1 );
			if ( preg_match( "/^[$@][^:]+:/i", $parent ) ) {
				$stack[$parent] = $token;
			} else {
				if ( $current && $token == '@' ) { // @acl
					$stack = array_merge( $stack, self::getRequiredCategories( $current ) );
				}
			}
		}
		return $stack;
	}

	//start/adapted from "$IP/includes/Title.php"
	private static function getParentCategories( $title ) { #adapted
		$titlekey = $title->getArticleID(); #adapted
		$dbr = wfGetDB( DB_SLAVE );
		$categorylinks = $dbr->tableName( 'categorylinks' );
		# NEW SQL
		$sql = "SELECT * FROM $categorylinks"
		     . " WHERE cl_from=$titlekey"
			 . " AND cl_to REGEXP '^([$][^:]+:|@)'" #added
			 . " ORDER BY cl_sortkey";
		$res = $dbr->query( $sql );
		$data = array();
		if ( $dbr->numRows( $res ) > 0 ) {
			foreach ( $res as $row ) {
				$data[$row->cl_to] = 1; #changed
			}
		}
		return $data;
	}
	private static function getParentCategoryTree( $title, $children = array() ) { #adapted
		$stack = array();
		$parents = self::getParentCategories( $title ); #adapted
		if ( $parents ) {
			foreach ( $parents as $parent => $current ) {
				if ( array_key_exists( $parent, $children ) ) {
					# Circular reference
					$stack[$parent] = array();
				} else {
					$nt = Title::makeTitle( NS_CATEGORY, $parent ); #changed
					if ( $nt ) {
						$stack[$parent] = self::getParentCategoryTree( $nt, $children + array( $parent => 1 ) ); #adapted
					}
				}
			}
		}
		return $stack;
	}
	//end/adapted from "$IP/includes/Title.php"

	private static function getRequired( &$title, &$categories ) {
		$categories = $stack = array();
		while ( true ) {
			$parents = self::getParentCategoryTree( $title );
			$categories = self::getRequiredCategories( $parents );
			if ( $categories || !$title->isRedirect() ) {
				break;
			}
			# Circular reference
			$titleDBkey = $title->getDBkey();
			if ( isset( $stack[$titleDBkey] ) ) {
				break;
			} else {
				$stack[$titleDBkey] = 1;
			}
			$article = new Article( $title );
			$title = $article->getRedirectTarget();
			if ( !$title instanceOf Title || !$title->isValidRedirectTarget() ) {
				break;
			}
		}
		if ( !$categories ) {
			return false;
		}
		$required = array();
		if ( in_array( '$', $categories ) ) {
			$required['$'] = '$';
		}
		if ( in_array( '@', $categories ) ) {
			$required['@'] = '@';
		}
		return $required;
	}

	private static function userCan( $categories, $action ) {
		$userCan = PermissionSession::userCan();
		if ( $userCan ) {
			$pattern = "/^[$action]($userCan)$/i";
			foreach ( $categories as $category => $current ) {
				if ( preg_match( $pattern, $category ) ) {
					return true;
				}
			}
		}
		return false;
	}

	private static function getPermission( &$title, $action ) {
		$required = self::getRequired( $title, $categories );
		if ( !$required ) {
			return true;
		}
		if ( $action == '$' && !in_array( '$', $required ) ) {
			return 1;
		}
		$action = in_array( '@', $required ) ? ( $action == '@' ? '@' : '$@' ) : '$';
		return self::userCan( $categories, $action ) ? 1 : 0;
	}

	public static function check( $title, $action, $session = false ) {
		if ( $action == 'purge' || $action == 'unwatch'
			|| !$title instanceof Title 
			|| $title->isSpecialPage() ) {
			return true;
		}
		# http://www.mediawiki.org/wiki/Action#Actions
		$actions = array(
			'$' => '$', // $Secret, private (read)
			'history' => '$',
			'markpatrolled' => '$',
			'print' => '$',
			'raw' => '$',
			'raw' => '$',
			'read' => '$',
			'view' => '$',
			'watch' => '$',
			'@' => '@', // @author, alter (edit)
			'delete' => '@',
			'deletetrackback' => '@',
			'edit' => '@',
			'editredlink' => '@',
			'protect' => '@',
			'unprotect' => '@',
			'revert' => '@',
			'rollback' => '@',
			'submit' => '@',
		);
		$action = isset( $actions[$action] ) ? $actions[$action] : '@';
		$permission = true;
		$titlekey = $title->getArticleID();
		if ( $titlekey ) {
			if ( $session ) {
				$permission = PermissionSession::getPermission( $titlekey, $action );
				if ( $permission !== null ) {
					return $permission;
				}
			}
			$permission = self::getPermission( $title, $action );
		}
		switch ( $permission ) {
			case 0:
				$permission = false;
				break;
			case 1:
				$permission = true;
				break;
			default:
				$titleDBkey = $title->getDBkey();
				if ( $i = strrpos( $titleDBkey, '/' ) ) {
					$titleDBkey = substr( $titleDBkey, 0, $i );
					$ns = $title->getNamespace();
					$nt = Title::makeTitle( $ns, $titleDBkey );
					$permission = self::check( $nt, $action, $session );
				}
		}
		if ( $titlekey && $session ) {
			PermissionSession::setPermission( $titlekey, $action, $permission );
		}
		return $permission;
	}

	private static function checkRedirect( $text ) {
#	private static function checkRedirect( $parserOutput ) {
#		$out = new OutputPage;
#		$out->addParserOutput( $parserOutput );
#		$redirect = $out->getRedirect();
#	private static function checkRedirect( $title ) {
#		$redirect = $title->isRedirect();
#		$article = new Article( $title );
#		$title = $article->getRedirectTarget();
		# FIXME: redirect to page (target) without permission
		# TODO: not allow redirect to page without permission
		return true;
	}

	public static function articleSave( $title, $user, &$text ) {
		if( !self::check( $title, 'edit', false ) ) {
			return false;
		}
		$parser = new Parser;
		$options = ParserOptions::newFromUser( $user );
		$out = $parser->parse( $text, $title, $options, true, true );
		$parents = $out->getCategories();
		if ( !$parents ) {
			return self::checkRedirect( $text );
		}
		$stack = array();
		foreach ( $parents as $parent => $current ) {
			if ( preg_match( "/^[$@][^:]+:/i", $parent ) ) {
				$stack[$parent] = array();
			} else {
				if ( substr( $parent, 0, 1 ) == '@' ) { // @acl
					$nt = Title::makeTitle( NS_CATEGORY, $parent );
					if ( $nt ) {
						$stack[$parent] = self::getParentCategoryTree( $nt );
					}
				}
			}
		}
		$categories = self::getRequiredCategories( $stack );
		if ( $categories ) {
			if ( !PermissionSession::userCan() ) {
				return false;
			}
			$required = in_array( '@', $categories ) ? '@' : '$';
			if ( !self::userCan( $categories, $required ) ) {
				global $wgContLang;
				$categoryText = $wgContLang->getNSText( NS_CATEGORY );
				$usr = wfMsg( 'permission-user' );
				$text .= "\n[[$categoryText:$required$usr:{$user->getName()}]]";
			}
		}
		return true;
	}

	public static function articleSaveComplete( $title ) {
		$categories = self::getParentCategories( $title );
		if ( $categories ) {
			global $wgPermissionHidden;
			$text = wfMsg( 'permission-text' );
			$summary = wfMsg( 'permission-summary' );
			foreach ( $categories as $category => $current ) {
				$nt = Title::makeTitle( NS_CATEGORY, $category );
				if ( !$nt->exists() ) {
					$hidden = $wgPermissionHidden && preg_match( "/^[$@][^:]+:.+$/i", $category ) ? "__HIDDENCAT__\n" : '';
					$article = new Article( $nt );
					$article->doEdit( $hidden . $text, $summary, EDIT_NEW );
				}
			}
		}
		return true;
	}
}

Permission.hooks.phpEdit

<?php
# Hooks: http://www.mediawiki.org/wiki/Hooks
class PermissionHooks {

	# userCan:
	public static function userCan( &$title, &$user, $action, &$result ) {
		return $result = Permission::check( $title, $action, true );
	}

	# article:
	public static function beforeParserFetchTemplateAndtitle( $parser, &$title, &$skip, &$id ) {
		if ( !Permission::check( $title, 'read', false ) ) {
			$skip = true;
			return false;
		}
		return true;
	}
	public static function articleSave( &$article, &$user, &$text, &$summary, $minor, $watch, $sectionanchor, &$flags ) {
		return Permission::articleSave( $article->getTitle(), $user, $text );
	}
	public static function articleSaveComplete( &$article, &$user, $text, $summary, $minoredit, $watchthis, $sectionanchor, &$flags, $revision, &$status, $baseRevId, &$redirect ) {
		return Permission::articleSaveComplete( $article->getTitle() );
	}
	public static function watchArticle( &$user, &$article ) {
		return Permission::check( $article->getTitle(), 'read', false );
	}

	# file:
	public static function imgAuthBeforeStream( $title, $path, $name, $result ) {
		if( !Permission::check( $title, 'read', false ) ) {
			$result = array( 'img-auth-accessdenied', 'img-auth-noread', $name );
			return false;
		}
		return true;
	}

	# session:
	public static function userLoginComplete( &$user, &$inject_html ) {
		$usr = wfMsg( 'permission-user' );
		$userName = $user->getName();
		$permissions = "$usr:$userName";
		$groups = $user->getGroups();
		if ( in_array( 'sysop', $groups ) ) {
			$permissions .= '|sys:op';
		}
		if ( in_array( 'bureaucrat', $groups ) ) {
			$permissions .= '|sys:bureaucrat';
		}
		$vars = array();
		wfRunHooks( 'PermissionLoginComplete', array( &$user, &$permissions, &$vars ) );
		PermissionSession::userLogin( $userName, $permissions, $vars );
		return true;
	}
	public static function userLogoutComplete( &$user, &$inject_html, $old_name ) {
		PermissionSession::userLogout();
		return true;
	}
}

# userCan:
$wgHooks['userCan'][] = 'PermissionHooks::userCan';
# article:
$wgHooks['BeforeParserFetchTemplateAndtitle'][] = 'PermissionHooks::beforeParserFetchTemplateAndtitle';
$wgHooks['ArticleSave'][] = 'PermissionHooks::articleSave';
$wgHooks['ArticleSaveComplete'][] = 'PermissionHooks::articleSaveComplete';
$wgHooks['WatchArticle'][] = 'PermissionHooks::watchArticle';
# file:
$wgHooks['ImgAuthBeforeStream'][] = 'PermissionHooks::imgAuthBeforeStream';
# session:
$wgHooks['UserLoginComplete'][] = 'PermissionHooks::userLoginComplete';
$wgHooks['UserLogoutComplete'][] = 'PermissionHooks::userLogoutComplete';

Permission.session.phpEdit

<?php
/*
 * Permission Extension for MediaWiki
 * Adapted from Collection: http://www.mediawiki.org/wiki/Extension:Collection
 *                          Copyright (C) PediaPress GmbH
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 * 
 * Adapted by: Alexandre Porto da Silva
 * Kept under: GNU General Public License
 */

class PermissionSession {

	private static function hasSession() {
		if ( !session_id() ) {
			return false;
		}
		return isset( $_SESSION['wsPermission'] );
	}

	private static function clearPermission() {
		$_SESSION['wsPermission'] = array();
	}

	private static function startSession() {
		if ( session_id() == '' ) {
			wfSetupSession();
		}
		self::clearPermission();
	}

	public static function userLogin( $username, $permissions = '', $vars = array() ) {
		if ( !self::hasSession() ) {
			self::startSession();
		}
		$_SESSION['wsPermission']['userName'] = $username;
		$_SESSION['wsPermission']['permissions'] = $permissions;
		$_SESSION['wsPermission']['var'] = $vars;
	}

	public static function userLogout() {
		if ( !self::hasSession() ) {
			return;
		}
		self::clearPermission();
	}

	public static function userCan() {
		return self::hasSession() ? $_SESSION['wsPermission']['permissions'] : '';
	}

	# permission
	public static function setPermission( $titlekey, $action, $data ) {
		if ( self::hasSession() ) {
			$_SESSION['wsPermission']['page'][$titlekey]['permission'][$action]['time'] = time();
			$_SESSION['wsPermission']['page'][$titlekey]['permission'][$action]['data'] = $data;
		}
		return;
	}

	public static function getPermission( $titlekey, $action ) {
		if (
			self::hasSession()
			&& isset( $_SESSION['wsPermission']['page'][$titlekey]['permission'][$action] )
			&& ( time() - $_SESSION['wsPermission']['page'][$titlekey]['permission'][$action]['time'] ) < PERMISSION_TIME
		) {
			return $_SESSION['wsPermission']['page'][$titlekey]['permission'][$action]['data'];
		}
		return null;
	}

	# var
	public static function setVar( $var, $data = '' ) {
		if ( $var && self::hasSession() ) {
			$_SESSION['wsPermission']['var'][$var] = $data;
		}
		return;
	}

	public static function getVar( $var ) {
		if ( $var && self::hasSession() && isset( $_SESSION['wsPermission']['var'][$var] ) ) {
			return $_SESSION['wsPermission']['var'][$var];
		}
		return '';
	}
}

Permission.i18n.phpEdit

<?php
# Manual: http://www.mediawiki.org/wiki/Manual:Special_pages#The_Messages.2FInternationalization_File

$messages = array();
 
/* *** English *** */
$messages['en'] = array( 
	'permission' => 'Permission',
	'permission-desc' => "Permissions for reading and editing based on categories.",
	'permission-user' => 'user',
	# messages to create the category page:
	'permission-text' => 'This [[Special:Categories|category]] contains articles of restricted access.',
	'permission-summary' => 'Category page automatically created by extension "Permission".'
);

/* *** Português (Brasil) *** */
$messages['pt-br'] = array(
	'permission' => 'Permissão',
	'permission-desc' => 'Permissões para leitura e edição baseado em categorias.',
	'permission-user' => 'user',
	# mensagens para criar a página da categoria:
	'permission-text' => 'Esta [[Especial:Categorias|categoria]] reúne artigos de acesso restrito.',
	'permission-summary' => 'Página da categoria criada automaticamente pela extensão "Permission".'
);