Regional-training LocalSettings.php: Difference between revisions
No edit summary |
No edit summary |
||
| Line 7: | Line 7: | ||
<pre> | <pre> | ||
<?php | |||
// History: | |||
// 2023-10-14 Ralph Holland - corrected the checks for access to pages with non-private categories. | |||
// 2023-06-08 Ralph Holland - include Special:Search an Special:RecentChanges in white-listing. | |||
// 2022-09-20 Ralph Holland - include Special:Categories white-listing. | |||
// 2022-09-20 Ralph Holland - now permit quite a few more Special: page titles. | |||
// 2022-09-20 Ralph Holland - fixed a comparison typo after r | |||
// 2022-09-19 Ralph Holland - made category:edit: markings inclusive while excluding users that have no corresponding marking for the edit action. | |||
// 2022-09-18 Ralph Holland - Now User:category is not completely exclusive, other User:category can be put on page to share with another users | |||
// public is not public when a private has been placed on the page or an exclusive user:category is on the page. | |||
// white-listed pages cannot override an exclusive category denying access | |||
// 2022-08-15 Ralph Holland - made pages without a category not public, made private exclusive, implemented category:User:<name> as exclusive page access | |||
// | |||
if ( !defined( 'MEDIAWIKI' ) ) { | |||
die( 'Not a valid entry point.' ); | |||
} | |||
$wgExtensionCredits['parserhook'][] = array( | |||
'name' => 'Restrict access by category and group', | |||
'author' => 'Andrés Orencio Ramirez Perez & Ralph Holland', | |||
'url' => 'https://www.mediawiki.org/wiki/Extension:Restrict_access_by_category_and_group', | |||
'description' => 'Allows to restrict access to pages by users groups and page categories: See [[Security]]', | |||
'version' => '3.1.0-adapted-by-ralph-holland' | |||
); | |||
$wgHooks['userCan'][] = 'restrictAccessByCategoryAndGroup'; | |||
function debug( $a ) { | |||
$ | for ($i =0; $i < 35; $i++) { | ||
$ | print " "; | ||
} | |||
$ | print "$a"; | ||
} | |||
function restrictAccessByCategoryAndGroup( $title, $user, $action, $result ) { | |||
# | # debug("Ralph is debugging access controls ATM title=".$title." user=".$user." action=".$action."<br/>"); | ||
$ | |||
global $wgGroupPermissions; | |||
$ | global $wgWhitelistRead; | ||
$ | global $wgLang; | ||
global $wgHooks; | |||
global $wgContLang; | |||
global $wgVersion; | |||
// strip the prefix of Talk etc off | |||
$ | $posn = strpos( $title,":"); | ||
$ | $prefix = $posn > -1 ? substr( $title, 0, $posn ) : ""; | ||
$page = $posn > -1 ? substr( $title, $posn+1 ) : $title; | |||
# | # debug( "RBH is debugging page=".$page." prefix=".$prefix." action=".$action." user=".$user."</br>" ); | ||
$ | |||
$ | |||
//These are special pages to be white-listed | |||
$ | if (strpos($title,'Special:')===0) { | ||
# | # debug('...special...<br/>'); | ||
switch($title) { | |||
case 'Special:Login': | |||
case 'Special:Logout': | |||
case 'Special:UserLogin': | |||
case 'Special:UserLogout': | |||
case 'Special:Badtitle': | |||
case 'Special:Random': | |||
// case 'Special:RecentChanges': | |||
case 'Special:Version': | |||
case 'Special:Categories': | |||
case 'Special:AllPages': | |||
case 'Special:Search': // added 2023-06-08 | |||
case 'Special:RecentChanges'; // added 2023-06-08 | |||
# debug( "*** $title permitted<br/>" ); | |||
return true; | |||
$ | default: | ||
$ | if ( strpos($title,"Special:WhatLinksHere")===0 ) { // || strpos($title,"Special:RecentChangesLinked")===0 ) { | ||
# debug("*** $title must be permitted<br/>"); | |||
return true; | |||
} | |||
break; | |||
} | |||
} | |||
if ( in_array('sysop',$user->getGroups()) ) { | |||
# debug( "*** sysop can see all pages<br/>' ); | |||
return true; | |||
} | |||
//Build up System categories from the title's categories | |||
$systemCategory = array(); | |||
// get the users groups | |||
$userGroups = $user->getGroups(); | |||
$hasUserCategory = false; | |||
$userCategoryMatched = false; | |||
// now check the page categories against the system groups | |||
$publicPage = false; | |||
$privatePage = false; | |||
$editUserMatched = false; | |||
$editGroupMatched = false; | |||
// now process page categories | |||
foreach ( array_change_key_case( $title->getParentCategories(), CASE_LOWER ) as $key => $value ) { | |||
$formattedKey = substr( $key, ( strpos( $key, ":" ) + 1 ) ); | |||
# debug( "checking category:".$formattedKey."=>[$formattedKey]"."<br/>" ); | |||
// check for the exclusive category public | |||
if ( $formattedKey=='public' ) { | |||
$publicPage = true; | |||
} | |||
// check for the exclusive category private | |||
elseif ( $formattedKey==='private' ) { | |||
// check that the user holds private group | |||
if (! in_array($formattedKey,$userGroups) ) { | |||
$privatePage = true; | |||
} | |||
} | |||
// check for the exclusive category user:* | |||
elseif ( strpos( $formattedKey, 'user:' ) === 0 ) { | |||
// these are special user categories e.g. [[User:Ralph]] | |||
$hasUserCategory = true; | |||
$name = 'user:'.strtolower($user->getName()); | |||
# debug( $formattedKey.' '.$name.'<-name<br/>' ); | |||
if ( $name && $formattedKey===$name ) { | |||
debug( '**** user permitted by user category'.$formattedKey.' '.'allowed<br/>' ); | |||
$userCategoryMatched = true; | |||
} | |||
} | |||
elseif ( $formattedKey === 'edit:*' ){ | |||
# debug('... matched '.$formattedKey.'<br/>'); | |||
$hasEdit = true; | |||
$editGroupMatched = true; | |||
} | } | ||
elseif ( strpos( $formattedKey, 'edit:user:' ) === 0 ) { | |||
// check if user is not permitted to edit | |||
if ($action === 'edit') { | |||
$hasEdit = true; | |||
$name = 'edit:user:'.strtolower($user->getName()); | |||
if ($name === $formattedKey) { | |||
# debug('*** edit user matched'.$name,'<br/>'); | |||
$editUserMatched = true; | |||
} | |||
} | |||
} | |||
elseif ( strpos( $formattedKey, 'edit:' ) === 0 ) { | |||
// check is user does not have groups to permit edit | |||
if ($action === 'edit') { | |||
$hasEdit = true; | |||
$group = substr( $formattedKey, ( strpos( $formattedKey, ":" ) + 1 ) ); | |||
if ( in_array($group,$userGroups) ) { | |||
# debug('*** edit group matched '.$group.'<br/>'); | |||
$editGroupMatched = true; | |||
} | |||
} | |||
} | |||
else { | |||
// build up non-exclusive categories | |||
$systemCategory[ $formattedKey ] = $value; | |||
} | |||
} | |||
// check if category:edit: marking found | |||
if ($hasEdit) { | |||
#debug('*** found category:edit:...<br/>'); | |||
if ($editGroupMatched) { | |||
#debug('*** matched a category:edit:group marking<br/>'); | |||
return true; | |||
} | |||
else if ($editUserMatched) { | |||
#debug('*** matched a category:edit:user: marking<br/>'); | |||
return true; | |||
} | |||
else { | |||
#debug('--- denied user by category:edit marking<br/>'); | |||
return false; | |||
} | |||
} | |||
// check if page marked with the user:category | |||
if ($userCategoryMatched) { | |||
# debug('*** user allowed by user:category'); | |||
return true; | |||
} | |||
// check if user is excluded by page marked private | |||
if ($privatePage) { | |||
# debug('--- denied because the page was marked private'); | |||
return false; | |||
} | |||
// check if user is excluded by another user:category | |||
if ($hasUserCategory) { | |||
# debug('--- denied by user:category'); | |||
return false; | |||
} | |||
// check if page was marked as public | |||
if ($publicPage) { | |||
# debug('*** permitted by public page'); | |||
return true; | |||
} | |||
// honour the existing white-list mechanism | |||
if ( $wgWhitelistRead && count( $wgWhitelistRead ) != 0 ) { | |||
if ( in_array( $title, $wgWhitelistRead ) ) { | |||
# debug("*** white listed" ); | |||
return true; | |||
} | |||
} | } | ||
// check if page has any remaining categories | |||
if ( count( $systemCategory ) === 0 ) { | |||
if ( count($userGroups) > 0 ) { | |||
# debug('*** logged in users can access pages without categories'); | |||
return true; | |||
} | |||
# debug('-- anonymous user cannot access pages without categories'); | |||
return false; | |||
} else { | |||
// check remaining page categories for inclusive private permissions | |||
// users must be logged in to process remaining category markings | |||
if ( count($userGroups) != 0) { | |||
# check is user has any group that permits private category access | |||
foreach ( $wgGroupPermissions as $key => $value ) { | |||
# debug( 'group->'.$key.' '.$action.' '.$title.'<br/>' ); | |||
// ... if the group permission is marked as 'private' then check ... | |||
if ( isset( $wgGroupPermissions[$key]['private'] ) ) { | |||
// ... if page is marked with a category that corresponds to the private group | |||
if ( array_key_exists( strtolower( str_replace( " ", "_", $key ) ), $systemCategory ) ) { | |||
// ... permit access if user is assigned the group | |||
if ( in_array( $key, $userGroups) ) { | |||
# debug( '*** permitted user holds the group '.$key ); | |||
return true; | |||
} | |||
} | |||
} | |||
} | |||
// check if any category on page is private | |||
foreach ( $systemCategory as $key => $value ) { | |||
#debug('****check '.$key.' val '.$value); | |||
if ( isset( $wgGroupPermission[$key]['private'] ) ) { | |||
// not permitted because checks above did not grant | |||
return false; | |||
} | |||
} | |||
// otherwise allow access to page that does not contain any private categories | |||
return true; | |||
} | |||
} | |||
# debug("--- user does not hold an appropriate private category and page is not marked for their access"); | |||
return false; | |||
} | } | ||
</pre> | </pre> | ||
Latest revision as of 13:10, 9 January 2024
The LocalSettings.php customisations:
- install the extension and
- defined private group privileges.
The http://regional-training.org wiki has the following LocalSettings.php customisations for category:Access Control:
<?php
// History:
// 2023-10-14 Ralph Holland - corrected the checks for access to pages with non-private categories.
// 2023-06-08 Ralph Holland - include Special:Search an Special:RecentChanges in white-listing.
// 2022-09-20 Ralph Holland - include Special:Categories white-listing.
// 2022-09-20 Ralph Holland - now permit quite a few more Special: page titles.
// 2022-09-20 Ralph Holland - fixed a comparison typo after r
// 2022-09-19 Ralph Holland - made category:edit: markings inclusive while excluding users that have no corresponding marking for the edit action.
// 2022-09-18 Ralph Holland - Now User:category is not completely exclusive, other User:category can be put on page to share with another users
// public is not public when a private has been placed on the page or an exclusive user:category is on the page.
// white-listed pages cannot override an exclusive category denying access
// 2022-08-15 Ralph Holland - made pages without a category not public, made private exclusive, implemented category:User:<name> as exclusive page access
//
if ( !defined( 'MEDIAWIKI' ) ) {
die( 'Not a valid entry point.' );
}
$wgExtensionCredits['parserhook'][] = array(
'name' => 'Restrict access by category and group',
'author' => 'Andrés Orencio Ramirez Perez & Ralph Holland',
'url' => 'https://www.mediawiki.org/wiki/Extension:Restrict_access_by_category_and_group',
'description' => 'Allows to restrict access to pages by users groups and page categories: See [[Security]]',
'version' => '3.1.0-adapted-by-ralph-holland'
);
$wgHooks['userCan'][] = 'restrictAccessByCategoryAndGroup';
function debug( $a ) {
for ($i =0; $i < 35; $i++) {
print " ";
}
print "$a";
}
function restrictAccessByCategoryAndGroup( $title, $user, $action, $result ) {
# debug("Ralph is debugging access controls ATM title=".$title." user=".$user." action=".$action."<br/>");
global $wgGroupPermissions;
global $wgWhitelistRead;
global $wgLang;
global $wgHooks;
global $wgContLang;
global $wgVersion;
// strip the prefix of Talk etc off
$posn = strpos( $title,":");
$prefix = $posn > -1 ? substr( $title, 0, $posn ) : "";
$page = $posn > -1 ? substr( $title, $posn+1 ) : $title;
# debug( "RBH is debugging page=".$page." prefix=".$prefix." action=".$action." user=".$user."</br>" );
//These are special pages to be white-listed
if (strpos($title,'Special:')===0) {
# debug('...special...<br/>');
switch($title) {
case 'Special:Login':
case 'Special:Logout':
case 'Special:UserLogin':
case 'Special:UserLogout':
case 'Special:Badtitle':
case 'Special:Random':
// case 'Special:RecentChanges':
case 'Special:Version':
case 'Special:Categories':
case 'Special:AllPages':
case 'Special:Search': // added 2023-06-08
case 'Special:RecentChanges'; // added 2023-06-08
# debug( "*** $title permitted<br/>" );
return true;
default:
if ( strpos($title,"Special:WhatLinksHere")===0 ) { // || strpos($title,"Special:RecentChangesLinked")===0 ) {
# debug("*** $title must be permitted<br/>");
return true;
}
break;
}
}
if ( in_array('sysop',$user->getGroups()) ) {
# debug( "*** sysop can see all pages<br/>' );
return true;
}
//Build up System categories from the title's categories
$systemCategory = array();
// get the users groups
$userGroups = $user->getGroups();
$hasUserCategory = false;
$userCategoryMatched = false;
// now check the page categories against the system groups
$publicPage = false;
$privatePage = false;
$editUserMatched = false;
$editGroupMatched = false;
// now process page categories
foreach ( array_change_key_case( $title->getParentCategories(), CASE_LOWER ) as $key => $value ) {
$formattedKey = substr( $key, ( strpos( $key, ":" ) + 1 ) );
# debug( "checking category:".$formattedKey."=>[$formattedKey]"."<br/>" );
// check for the exclusive category public
if ( $formattedKey=='public' ) {
$publicPage = true;
}
// check for the exclusive category private
elseif ( $formattedKey==='private' ) {
// check that the user holds private group
if (! in_array($formattedKey,$userGroups) ) {
$privatePage = true;
}
}
// check for the exclusive category user:*
elseif ( strpos( $formattedKey, 'user:' ) === 0 ) {
// these are special user categories e.g. [[User:Ralph]]
$hasUserCategory = true;
$name = 'user:'.strtolower($user->getName());
# debug( $formattedKey.' '.$name.'<-name<br/>' );
if ( $name && $formattedKey===$name ) {
debug( '**** user permitted by user category'.$formattedKey.' '.'allowed<br/>' );
$userCategoryMatched = true;
}
}
elseif ( $formattedKey === 'edit:*' ){
# debug('... matched '.$formattedKey.'<br/>');
$hasEdit = true;
$editGroupMatched = true;
}
elseif ( strpos( $formattedKey, 'edit:user:' ) === 0 ) {
// check if user is not permitted to edit
if ($action === 'edit') {
$hasEdit = true;
$name = 'edit:user:'.strtolower($user->getName());
if ($name === $formattedKey) {
# debug('*** edit user matched'.$name,'<br/>');
$editUserMatched = true;
}
}
}
elseif ( strpos( $formattedKey, 'edit:' ) === 0 ) {
// check is user does not have groups to permit edit
if ($action === 'edit') {
$hasEdit = true;
$group = substr( $formattedKey, ( strpos( $formattedKey, ":" ) + 1 ) );
if ( in_array($group,$userGroups) ) {
# debug('*** edit group matched '.$group.'<br/>');
$editGroupMatched = true;
}
}
}
else {
// build up non-exclusive categories
$systemCategory[ $formattedKey ] = $value;
}
}
// check if category:edit: marking found
if ($hasEdit) {
#debug('*** found category:edit:...<br/>');
if ($editGroupMatched) {
#debug('*** matched a category:edit:group marking<br/>');
return true;
}
else if ($editUserMatched) {
#debug('*** matched a category:edit:user: marking<br/>');
return true;
}
else {
#debug('--- denied user by category:edit marking<br/>');
return false;
}
}
// check if page marked with the user:category
if ($userCategoryMatched) {
# debug('*** user allowed by user:category');
return true;
}
// check if user is excluded by page marked private
if ($privatePage) {
# debug('--- denied because the page was marked private');
return false;
}
// check if user is excluded by another user:category
if ($hasUserCategory) {
# debug('--- denied by user:category');
return false;
}
// check if page was marked as public
if ($publicPage) {
# debug('*** permitted by public page');
return true;
}
// honour the existing white-list mechanism
if ( $wgWhitelistRead && count( $wgWhitelistRead ) != 0 ) {
if ( in_array( $title, $wgWhitelistRead ) ) {
# debug("*** white listed" );
return true;
}
}
// check if page has any remaining categories
if ( count( $systemCategory ) === 0 ) {
if ( count($userGroups) > 0 ) {
# debug('*** logged in users can access pages without categories');
return true;
}
# debug('-- anonymous user cannot access pages without categories');
return false;
} else {
// check remaining page categories for inclusive private permissions
// users must be logged in to process remaining category markings
if ( count($userGroups) != 0) {
# check is user has any group that permits private category access
foreach ( $wgGroupPermissions as $key => $value ) {
# debug( 'group->'.$key.' '.$action.' '.$title.'<br/>' );
// ... if the group permission is marked as 'private' then check ...
if ( isset( $wgGroupPermissions[$key]['private'] ) ) {
// ... if page is marked with a category that corresponds to the private group
if ( array_key_exists( strtolower( str_replace( " ", "_", $key ) ), $systemCategory ) ) {
// ... permit access if user is assigned the group
if ( in_array( $key, $userGroups) ) {
# debug( '*** permitted user holds the group '.$key );
return true;
}
}
}
}
// check if any category on page is private
foreach ( $systemCategory as $key => $value ) {
#debug('****check '.$key.' val '.$value);
if ( isset( $wgGroupPermission[$key]['private'] ) ) {
// not permitted because checks above did not grant
return false;
}
}
// otherwise allow access to page that does not contain any private categories
return true;
}
}
# debug("--- user does not hold an appropriate private category and page is not marked for their access");
return false;
}
summary
These privileges may be assigned to a selected user from the Special:UserRights page, and are accessible in the php hook via $user->getGroups().

.
Categories may be assigned to a page, where the category may be associated by name to group.
Matches are performed case-insensitively, and when they occur, it means that the page is subject to fine-grained category:Access Control that is implemented by the Regional-training RestrictAccessByCategoryAndGroup.php extension.