RestrictAccessByCategoryAndGroup.php
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.