Whitelisting publicly readable title with regex

This patch make it possible to whitelist pages which anonymous users may
see. It is similar to $wgWhitelistRead expect it uses regular
expressions, the list of regex are to be added in the new global array
$wgWhitelistReadRegexp.

This would be useful in a semi-public team wiki situation where the
admin would want to hide an entire namespace from everyone except those
in a particular group due to sensitive team specific information.

Added new unit test testUserCan in includes/TitleTest.php to
test this new functionality.

* adds $wgWhitelistReadRegexp to DefaultSettings.php
* updates RELEASE-NOTES-1.21 new features
* updates CREDITS

Signed-off-by: Antoine Musso <hashar@free.fr>

Change-Id: I83f6a614874c3d289ff4bd8d015f1d9c92e500b6
This commit is contained in:
nullspoon 2012-08-07 16:06:12 -06:00 committed by Antoine Musso
parent 401aa5c63f
commit 550b878e63
5 changed files with 158 additions and 1 deletions

View file

@ -87,6 +87,7 @@ following names for their contribution to the product.
== Patch Contributors ==
* Aaron Pramana
* Aaron Ball
* Agbad
* Ahmad Sherif
* Alejandro Mery

View file

@ -85,6 +85,7 @@ production.
a security fix (bug 42202).
* Added the ability to limit the wall clock time used by shell processes,
as well as the CPU time. Configurable with $wgMaxShellWallClockTime.
* Added $wgWhitelistReadRegexp for regex whitelisting
=== Bug fixes in 1.21 ===
* (bug 40353) SpecialDoubleRedirect should support interwiki redirects.

View file

@ -3816,6 +3816,34 @@ $wgBlockDisablesLogin = false;
*/
$wgWhitelistRead = false;
/**
* Pages anonymous user may see, set as an array of regular expressions.
*
* This function will match the regexp against the title name, which
* is without underscore.
*
* @par Example:
* To whitelist [[Main Page]]:
* @code
* $wgWhitelistReadRegexp = array( "/Main Page/" );
* @endcode
*
* @note Unless ^ and/or $ is specified, a regular expression might match
* pages not intended to be whitelisted. The above example will also
* whitelist a page named 'Security Main Page'.
*
* @par Example:
* To allow reading any page starting with 'User' regardless of the case:
* @code
* $wgWhitelistReadRegexp = array( "@^UsEr.*@i" );
* @endcode
* Will allow both [[User is banned]] and [[User:JohnDoe]]
*
* @note This will only work if $wgGroupPermissions['*']['read'] is false --
* see below. Otherwise, ALL pages are accessible, regardless of this setting.
*/
$wgWhitelistReadRegexp = false;
/**
* Should editors be required to have a validated e-mail
* address before being allowed to edit?

View file

@ -2087,7 +2087,7 @@ class Title {
* @return Array list of errors
*/
private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
global $wgWhitelistRead, $wgRevokePermissions;
global $wgWhitelistRead, $wgWhitelistReadRegexp, $wgRevokePermissions;
static $useShortcut = null;
# Initialize the $useShortcut boolean, to determine if we can skip quite a bit of code below
@ -2155,6 +2155,17 @@ class Title {
}
}
if( !$whitelisted && is_array( $wgWhitelistReadRegexp ) && !empty( $wgWhitelistReadRegexp ) ) {
$name = $this->getPrefixedText();
// Check for regex whitelisting
foreach ( $wgWhitelistReadRegexp as $listItem ) {
if ( preg_match( $listItem, $name ) ) {
$whitelisted = true;
break;
}
}
}
if ( !$whitelisted ) {
# If the title is not whitelisted, give extensions a chance to do so...
wfRunHooks( 'TitleReadWhitelist', array( $this, $user, &$whitelisted ) );

View file

@ -79,6 +79,122 @@ class TitleTest extends MediaWikiTestCase {
}
}
/**
* Provides test parameter values for testIsValidMoveOperation()
*/
function dataTestIsValidMoveOperation() {
return array(
array( 'Test', 'Test', 'selfmove' ),
array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' )
);
}
/**
* Auth-less test of Title::userCan
*
* @param array $whitelistRegexp
* @param string $source
* @param string $action
* @param array|string|true $expected Required error
*
* @covers Title::checkReadPermission
* @dataProvider dataWgWhitelistReadRegexp
*/
function testWgWhitelistReadRegexp($whitelistRegexp, $source, $action, $expected) {
// $wgWhitelistReadRegexp must be an array. Since the provided test cases
// usually have only one regex, it is more concise to write the lonely regex
// as a string. Thus we cast to an array() to honor $wgWhitelistReadRegexp
// type requisite.
if( is_string( $whitelistRegexp ) ) {
$whitelistRegexp = array( $whitelistRegexp );
}
$title = Title::newFromDBkey( $source );
global $wgGroupPermissions;
$oldPermissions = $wgGroupPermissions;
// Disallow all so we can ensure our regex works
$wgGroupPermissions = array();
$wgGroupPermissions['*']['read'] = false;
global $wgWhitelistRead;
$oldWhitelist = $wgWhitelistRead;
// Undo any LocalSettings explicite whitelists so they won't cause a
// failing test to succeed. Set it to some random non sense just
// to make sure we properly test Title::checkReadPermissions()
$wgWhitelistRead = array( 'some random non sense title' );
global $wgWhitelistReadRegexp;
$oldWhitelistRegexp = $wgWhitelistReadRegexp;
$wgWhitelistReadRegexp = $whitelistRegexp ;
// Just use $wgUser which in test is a user object for '127.0.0.1'
global $wgUser;
// Invalidate user rights cache to take in account $wgGroupPermissions
// change above.
$wgUser->clearInstanceCache();
$errors = $title->userCan( $action, $wgUser );
// Restore globals
$wgGroupPermissions = $oldPermissions;
$wgWhitelistRead = $oldWhitelist;
$wgWhitelistReadRegexp = $oldWhitelistRegexp;
if( is_bool( $expected ) ) {
# Forge the assertion message depending on the assertion expectation
$allowableness = $expected
? " should be allowed"
: " should NOT be allowed"
;
$this->assertEquals( $expected, $errors, "User action '$action' on [[$source]] $allowableness." );
} else {
$errors = $this->flattenErrorsArray( $errors );
foreach ( (array)$expected as $error ) {
$this->assertContains( $error, $errors );
}
}
}
/**
* Provides test parameter values for testWgWhitelistReadRegexp()
*/
function dataWgWhitelistReadRegexp() {
$ALLOWED = true;
$DISALLOWED = false;
return array(
// Everything, if this doesn't work, we're really in trouble
array( '/.*/', 'Main_Page', 'read', $ALLOWED ),
array( '/.*/', 'Main_Page', 'edit', $DISALLOWED ),
// We validate against the title name, not the db key
array( '/^Main_Page$/', 'Main_Page', 'read', $DISALLOWED ),
// Main page
array( '/^Main/', 'Main_Page', 'read', $ALLOWED ),
array( '/^Main.*/', 'Main_Page', 'read', $ALLOWED ),
// With spaces
array( '/Mic\sCheck/', 'Mic Check', 'read', $ALLOWED ),
// Unicode multibyte
// ...without unicode modifier
array( '/Unicode Test . Yes/', 'Unicode Test Ñ Yes', 'read', $DISALLOWED ),
// ...with unicode modifier
array( '/Unicode Test . Yes/u', 'Unicode Test Ñ Yes', 'read', $ALLOWED ),
// Case insensitive
array( '/MiC ChEcK/', 'mic check', 'read', $DISALLOWED ),
array( '/MiC ChEcK/i', 'mic check', 'read', $ALLOWED ),
// From DefaultSettings.php:
array( "@^UsEr.*@i", 'User is banned', 'read', $ALLOWED ),
array( "@^UsEr.*@i", 'User:John Doe', 'read', $ALLOWED ),
// With namespaces:
array( '/^Special:NewPages$/', 'Special:NewPages', 'read', $ALLOWED ),
array( null, 'Special:Newpages', 'read', $DISALLOWED ),
);
}
function flattenErrorsArray( $errors ) {
$result = array();
foreach ( $errors as $error ) {