RestrictAccessByCategoryAndGroup.php: Difference between revisions
No edit summary |
No edit summary |
||
| (10 intermediate revisions by the same user not shown) | |||
| Line 5: | Line 5: | ||
// History: | // History: | ||
// 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-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 | // 2022-09-18 Ralph Holland - Now User:category is not completely exclusive, other User:category can be put on page to share with another user | ||
// public is not public when a private has been placed on the page or an exclusive user:category is on the page. | // 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 | // 2022-08-15 Ralph Holland - made pages without a category not public, made private exclusive, implemented category:User:<name> as exclusive page access | ||
// | // | ||
| Line 22: | Line 24: | ||
'url' => 'https://www.mediawiki.org/wiki/Extension:Restrict_access_by_category_and_group', | '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]]', | 'description' => 'Allows to restrict access to pages by users groups and page categories: See [[Security]]', | ||
' | 'version' => '3.1.0-adapted-by-ralph-holland' | ||
); | ); | ||
| Line 29: | Line 30: | ||
//$wgHooks['getUserPermissionsErrors'][] = 'restrictAccessByCategoryAndGroup'; | //$wgHooks['getUserPermissionsErrors'][] = 'restrictAccessByCategoryAndGroup'; | ||
function debug( $a ) { | function debug( $a ) { | ||
for ($i =0; $i < 35; $i++) { | for ($i =0; $i < 35; $i++) { | ||
| Line 53: | Line 53: | ||
$page = $posn > -1 ? substr( $title, $posn+1 ) : $title; | $page = $posn > -1 ? substr( $title, $posn+1 ) : $title; | ||
# debug( "RBH is debugging page=".$page." prefix=".$prefix." action=".$action." user=".$user."</br>" ); | #debug( "RBH is debugging page=".$page." prefix=".$prefix." action=".$action." user=".$user."</br>" ); | ||
//These are special pages to be white-listed | //These are special pages to be white-listed | ||
if ( $title | if (strpos($title,'Special:')===0) { | ||
switch($title) { | |||
case 'Special:Login': | |||
case 'Special:Logout': | |||
case 'Special:UserLogin': | |||
# debug( "*** | case 'Special:UserLogout': | ||
case 'Special:Badtitle': | |||
case 'Special:Random': | |||
// case 'Special:RecentChanges': | |||
case 'Special:Version': | |||
case 'Special:Categories': | |||
case 'Special:AllPages': | |||
# debug( "*** $title permitted" ); | |||
return true; | |||
default: | |||
if ( strpos($title,"Special:WhatLinksHere")===0 ) { // || strpos($title,"Special:RecentChangesLinked")===0 ) { | |||
# debug("*** $title must be permitted"); | |||
return true; | |||
} | |||
break; | |||
} | |||
} | |||
if ( in_array('sysop',$user->getGroups()) ) { | |||
# debug( "*** sysop can see all pages" ); | # debug( "*** sysop can see all pages" ); | ||
return true; | return true; | ||
} | } | ||
// Build up System categories from the title's categories | //Build up System categories from the title's categories | ||
$systemCategory = array(); | $systemCategory = array(); | ||
| Line 89: | Line 101: | ||
// now process page categories | // now process page categories | ||
foreach ( array_change_key_case( $title->getParentCategories(), CASE_LOWER ) as $key => $value ) { | foreach ( array_change_key_case( $title->getParentCategories(), CASE_LOWER ) as $key => $value ) { | ||
$ | $formatedKey = substr( $key, ( strpos( $key, ":" ) + 1 ) ); | ||
#debug( "category:".$ | # debug( "category:".$formatedKey."<br/>" ); | ||
// check for the exclusive category public | // check for the exclusive category public | ||
if ( $ | if ( $formatedKey=='public' ) { | ||
$publicPage = true; | $publicPage = true; | ||
} | } | ||
// check for the exclusive category private | // check for the exclusive category private | ||
elseif ( $formattedKey=='private' ) { | elseif ( $formattedKey==='private' ) { | ||
// check that the user holds private group | // check that the user holds private group | ||
if (! in_array($ | if (! in_array($formatedKey,$userGroups) ) { | ||
$privatePage = true; | $privatePage = true; | ||
} | } | ||
} | } | ||
// check for the exclusive category user:* | // check for the exclusive category user:* | ||
elseif ( strpos( $ | elseif ( strpos( $formatedKey, 'user:' ) === 0 ) { | ||
// these are special user categories e.g. [[User:Ralph]] | // these are special user categories e.g. [[User:Ralph]] | ||
$hasUserCategory = true; | $hasUserCategory = true; | ||
$name = 'user:'.strtolower($user->getName()); | $name = 'user:'.strtolower($user->getName()); | ||
# debug( $ | # debug( $formatedKey.' '.$name.'<-name<br/>' ); | ||
if ( $name && $ | if ( $name && $formatedKey===$name ) { | ||
# debug( '**** user permitted by user category'.$ | # debug( '**** user permitted by user category'.$formatedKey.' '.'allowed<br/>' ); | ||
$userCategoryMatched = true; | $userCategoryMatched = true; | ||
} | } | ||
} | } | ||
elseif ( strpos( $ | elseif ( strpos( $formatedKey, 'edit:user:' ) === 0 ) { | ||
// check if user is permitted to edit | // check if user is not permitted to edit | ||
if ($action === 'edit') { | if ($action === 'edit') { | ||
$hasEdit = true; | $hasEdit = true; | ||
$name = 'edit:user:'.strtolower($user->getName()); | $name = 'edit:user:'.strtolower($user->getName()); | ||
if ($name === $ | if ($name === $formatedKey) { | ||
# debug('*** edit user matched'.$name); | # debug('*** edit user matched'.$name); | ||
$editUserMatched = true; | $editUserMatched = true; | ||
| Line 126: | Line 138: | ||
} | } | ||
} | } | ||
elseif ( strpos( $ | elseif ( strpos( $formatedKey, 'edit:' ) === 0 ) { | ||
// check is user | // check is user does not have groups to permit edit | ||
if ($action === 'edit') { | if ($action === 'edit') { | ||
$hasEdit = true; | $hasEdit = true; | ||
$group = substr( $ | $group = substr( $formatedKey, ( strpos( $formatedKey, ":" ) + 1 ) ); | ||
if ( in_array($group,$userGroups) ) { | if ( in_array($group,$userGroups) ) { | ||
# debug('*** edit group matched '.$group); | # debug('*** edit group matched '.$group); | ||
| Line 138: | Line 150: | ||
} | } | ||
else { | else { | ||
// build up non-exclusive categories | // build up non-exclusive categories | ||
$systemCategory[ $ | $systemCategory[ $formatedKey ] = $value; | ||
} | } | ||
} | } | ||
| Line 150: | Line 162: | ||
} | } | ||
else if ($editUserMatched) { | else if ($editUserMatched) { | ||
# debug('*** matched a category:edit:user: | # debug('*** matched a category:edit:user: markingi'); | ||
return true; | return true; | ||
} | } | ||
else { | else { | ||
# debug('--- denied user | # debug('--- denied user by category:edit marking'); | ||
return false; | return false; | ||
} | } | ||
| Line 185: | Line 197: | ||
// honour the existing white-list mechanism | // honour the existing white-list mechanism | ||
if ( count( $wgWhitelistRead ) != 0 ) { | if ( count( $wgWhitelistRead ) != 0 ) { | ||
if ( in_array( $title, $wgWhitelistRead ) ) { | |||
# debug("*** white listed" ); | |||
return true; | |||
} | |||
} | } | ||
// check if page has any remaining categories | // check if page has any remaining categories | ||
if ( count( $systemCategory ) == 0 ) { | if ( count( $systemCategory ) === 0 ) { | ||
if ( count($userGroups) > 0 ) { | if ( count($userGroups) > 0 ) { | ||
// Note this code insists on logged-on users being a member of at least one group | |||
# debug('*** logged in users can access pages without categories'); | # debug('*** logged in users can access pages without categories'); | ||
return true; | return true; | ||
| Line 264: | Line 277: | ||
##* a [[sysop]], | ##* a [[sysop]], | ||
##* or one of the [[users]] that matches any [[:category:user:]] [[marking]], otherwise the [[page]] is [[inaccessible]] to any other [[users]]. | ##* or one of the [[users]] that matches any [[:category:user:]] [[marking]], otherwise the [[page]] is [[inaccessible]] to any other [[users]]. | ||
## when [[marked]] with a private [[:category: | ## when [[marked]] with a private [[:category:<group>]] (e.g. [[:category:lesson]]) then a [[logged-in]] [[user]] is [[permitted]] to [[access]] the [[page]] when that [[user]] is a member of the group (e.g. the lesson group) i.e. if the group has been assigned to the user in the [[Special:UserRights]] [[pages]]. | ||
## when [[marked]] with [[:category:private]] the page is [[inaccessible]] to any [[user]] that is not a member of the [[:category:private]] [[group]], except for a [[sysop]] who is [[permitted]] to [[access]] any [[page]] | ## when [[marked]] with [[:category:private]] the page is [[inaccessible]] to any [[user]] that is not a member of the [[:category:private]] [[group]], except for a [[sysop]] who is [[permitted]] to [[access]] any [[page]] | ||
## when [[marked]] with a [[:category:edit: | ## when [[marked]] with a [[:category:edit:]] the [[page ]] is [[restricted]] for [[edit]] unless there is: | ||
##* a [[:category:edit:user: | ##* a [[:category:edit:user:<name>]] to permit that [[user]], or | ||
##* a [[:category:edit: | ##* a [[:category:edit:<group>]] of which the user is a member i.e. the [[user]] has been assigned the group with the particular [[private]] privilege. | ||
## when marked with [[:category:public]] the [[page]] is visible to any [[user]], provide none of the previous [[read]] [[access]] checks have failed to [[grant]] access. | ## when marked with [[:category:public]] the [[page]] is visible to any [[user]], provide none of the previous [[read]] [[access]] checks have failed to [[grant]] access. | ||
# [[pages]] that are not [[marked]] with any [[:categories]] are not accessible to an [[anonymous]] [[user]]. | # [[pages]] that are not [[marked]] with any [[:categories]] are not accessible to an [[anonymous]] [[user]]. | ||
# only [[pages]] marked with [[:category:public]] or [[pages]] that have been [[white-listed]] may be viewed by any user, including [[ | # only [[pages]] marked with [[:category:public]] or [[pages]] that have been [[white-listed]] may be viewed by any user, including [[anonymous]] users (those who have not [[logged-in]]). | ||
=references= | =references= | ||
<references/> | <references/> | ||
---- | |||
Note: I am making tweaks to this code as time goes on so I just have to remember to update it here until I upload the code to my github account. | Note: I am making tweaks to this code as time goes on so I just have to remember to update it here until I upload the code to my github account. | ||
I have also made security changes to the /includes/skin/Skin.php to restrict what links are presented to anonymous users. I will post those changes soon. | |||
Sorry about the empty pages. I copied and pasted this text from another wiki https://regional-training.org/rt/Category:Access_Control. I maintain 10 mediawikis so far - lol. | |||
[[category:public]] | [[category:public]] | ||
[[category:code]] | |||
[[category:customisation]] | |||
[[category:Security]] | |||
[[category:Access Control]] | |||
Latest revision as of 04:51, 8 October 2022
The original extension was written by Andrés Orencio Ramirez Perez[1] and has been customized by User:Ralph to function as outlined in category:Access Control for this wiki.
<?php
// History:
// 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 user
// 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';
//$wgHooks['getUserPermissionsErrors'][] = '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) {
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':
# debug( "*** $title permitted" );
return true;
default:
if ( strpos($title,"Special:WhatLinksHere")===0 ) { // || strpos($title,"Special:RecentChangesLinked")===0 ) {
# debug("*** $title must be permitted");
return true;
}
break;
}
}
if ( in_array('sysop',$user->getGroups()) ) {
# debug( "*** sysop can see all pages" );
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 ) {
$formatedKey = substr( $key, ( strpos( $key, ":" ) + 1 ) );
# debug( "category:".$formatedKey."<br/>" );
// check for the exclusive category public
if ( $formatedKey=='public' ) {
$publicPage = true;
}
// check for the exclusive category private
elseif ( $formattedKey==='private' ) {
// check that the user holds private group
if (! in_array($formatedKey,$userGroups) ) {
$privatePage = true;
}
}
// check for the exclusive category user:*
elseif ( strpos( $formatedKey, 'user:' ) === 0 ) {
// these are special user categories e.g. [[User:Ralph]]
$hasUserCategory = true;
$name = 'user:'.strtolower($user->getName());
# debug( $formatedKey.' '.$name.'<-name<br/>' );
if ( $name && $formatedKey===$name ) {
# debug( '**** user permitted by user category'.$formatedKey.' '.'allowed<br/>' );
$userCategoryMatched = true;
}
}
elseif ( strpos( $formatedKey, 'edit:user:' ) === 0 ) {
// check if user is not permitted to edit
if ($action === 'edit') {
$hasEdit = true;
$name = 'edit:user:'.strtolower($user->getName());
if ($name === $formatedKey) {
# debug('*** edit user matched'.$name);
$editUserMatched = true;
}
}
}
elseif ( strpos( $formatedKey, 'edit:' ) === 0 ) {
// check is user does not have groups to permit edit
if ($action === 'edit') {
$hasEdit = true;
$group = substr( $formatedKey, ( strpos( $formatedKey, ":" ) + 1 ) );
if ( in_array($group,$userGroups) ) {
# debug('*** edit group matched '.$group);
$editGroupMatched = true;
}
}
}
else {
// build up non-exclusive categories
$systemCategory[ $formatedKey ] = $value;
}
}
// check if category:edit: marking found
if ($hasEdit) {
if ($editGroupMatched) {
# debug('*** matched a category:edit:group marking');
return true;
}
else if ($editUserMatched) {
# debug('*** matched a category:edit:user: markingi');
return true;
}
else {
# debug('--- denied user by category:edit marking');
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 ( 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 ) {
// Note this code insists on logged-on users being a member of at least one group
# 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) {
// for each group permission ...
$hasPrivateCategories = false;
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'] ) ) {
$hasPrivateCategories = true;
// ... 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;
}
}
}
}
if (!$hasPrivateCategories) {
# debug('*** user permitted to access a page that does not contain private categories');
return true;
}
}
}
# debug("--- user does not hold an appropriate private category and page is not marked for their access");
return false;
}
installation
The extension is installed in the file location:
.../mediawiki/extensions/rabcg/RestrictAccessByCategoryAndGroup.php
The extension is loaded from the:
.../mediawiki/LocalSettings.php
access control checks
The access control reference data is defined in LocalSettings.php as follows:
- privileges are denied by:
$wgGroupPermissions[<group>]['*'] = false;
- and the private privilege for a group is defined by:
$wgGroupPermissions[<group>]['private'] = true;
Those groups may be assigned to a user account via the Special:UserRights page, where the privilege groups that were assigned are accessible via $user->getGroups() in this extension.
The Access Control checks are performed in this order as follows:
- the special pages:
- are always permitted.
- pages marked with any Categories are examined to determine if they are marked with an Access Control marking as follows:
- when marked with a category:user: mark (containing a user name) then the page is accessible only to:
- a sysop,
- or one of the users that matches any category:user: marking, otherwise the page is inaccessible to any other users.
- when marked with a private [[:category:<group>]] (e.g. category:lesson) then a logged-in user is permitted to access the page when that user is a member of the group (e.g. the lesson group) i.e. if the group has been assigned to the user in the Special:UserRights pages.
- when marked with category:private the page is inaccessible to any user that is not a member of the category:private group, except for a sysop who is permitted to access any page
- when marked with a category:edit: the page is restricted for edit unless there is:
- when marked with category:public the page is visible to any user, provide none of the previous read access checks have failed to grant access.
- when marked with a category:user: mark (containing a user name) then the page is accessible only to:
- pages that are not marked with any categories are not accessible to an anonymous user.
- only pages marked with category:public or pages that have been white-listed may be viewed by any user, including anonymous users (those who have not logged-in).
references
Note: I am making tweaks to this code as time goes on so I just have to remember to update it here until I upload the code to my github account. I have also made security changes to the /includes/skin/Skin.php to restrict what links are presented to anonymous users. I will post those changes soon. Sorry about the empty pages. I copied and pasted this text from another wiki https://regional-training.org/rt/Category:Access_Control. I maintain 10 mediawikis so far - lol.