Regional-training RestrictAccessByCategoryAndGroup.php: Difference between revisions

From regional-training
No edit summary
Line 3: Line 3:
<pre>
<pre>
<?php
<?php
 
// History:
// History:
// 2023-06-08 Ralph Holland - include Special:Search and Special:RecentChanges in white-listing.
// 2022-09-20 Ralph Holland - include Special:Categories 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 - now permit quite a few more Special: page titles.
// 2022-09-20 Ralph Holland - fixed a comparison typo after r
// 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 user
// 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.
//                            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
//       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
// 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' ) ) {
if ( !defined( 'MEDIAWIKI' ) ) {
         die( 'Not a valid entry point.' );
         die( 'Not a valid entry point.' );
}
}
 
$wgExtensionCredits['parserhook'][] = array(
$wgExtensionCredits['parserhook'][] = array(
         'name' => 'Restrict access by category and group',
         'name' => 'Restrict access by category and group',
Line 26: Line 27:
         'version' => '3.1.0-adapted-by-ralph-holland'
         'version' => '3.1.0-adapted-by-ralph-holland'
);
);
 
$wgHooks['userCan'][] = 'restrictAccessByCategoryAndGroup';
$wgHooks['userCan'][] = 'restrictAccessByCategoryAndGroup';
//$wgHooks['getUserPermissionsErrors'][] = 'restrictAccessByCategoryAndGroup';
 
function debug( $a ) {
function debug( $a ) {
    for ($i =0; $i < 35; $i++) {
for ($i =0; $i < 35; $i++) {
        print "&nbsp;";
print "&nbsp;";
    }
}
    print "$a";
print "$a";
}
}
 
function restrictAccessByCategoryAndGroup( $title, $user, $action, $result ) {
function restrictAccessByCategoryAndGroup( $title, $user, $action, $result ) {
 
    # debug("Ralph is debugging access controls ATM title=".$title." user=".$user." action=".$action."<br/>");
# debug("Ralph is debugging access controls ATM title=".$title." user=".$user." action=".$action."<br/>");
 
    global $wgGroupPermissions;
global $wgGroupPermissions;
    global $wgWhitelistRead;
global $wgWhitelistRead;
    global $wgLang;
global $wgLang;
    global $wgHooks;
global $wgHooks;
    global $wgContLang;
global $wgContLang;
    global $wgVersion;
global $wgVersion;
 
    // strip the prefix of Talk etc off
// strip the prefix of Talk etc off
    $posn = strpos( $title,":");
$posn = strpos( $title,":");
    $prefix = $posn > -1 ? substr( $title, 0, $posn ) : "";
$prefix = $posn > -1 ? substr( $title, 0, $posn ) : "";
    $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  
// RBH These are special pages to be white-listed  
    if (strpos($title,'Special:')===0) {
if (strpos($title,'Special:')===0) {
        switch($title) {
        switch($title) {
        case 'Special:Login':
    case 'Special:Login':
        case 'Special:Logout':
    case 'Special:Logout':
        case 'Special:UserLogin':
            case 'Special:UserLogin':
        case 'Special:UserLogout':
            case 'Special:UserLogout':
        case 'Special:Badtitle':
        case 'Special:Badtitle':
        case 'Special:Random':
        case 'Special:Random':
        // case 'Special:RecentChanges':
        case 'Special:Version':
        case 'Special:Version':
    case 'Special:Categories':
        case 'Special:Categories':
    case 'Special:AllPages':
        case 'Special:AllPages':
    case 'Special:Search': // added 2023-06-08
                    # debug( "*** $title permitted" );
    case 'Special:RecentChanges';  // added 2023-06-08
                    return true;
                # debug( "*** $title permitted" );
                return true;
        default:
 
            if ( strpos($title,"Special:WhatLinksHere")===0 ) { // || strpos($title,"Special:RecentChangesLinked")===0 ) {
    default:
                # debug("*** $title must be permitted");
    if ( strpos($title,"Special:WhatLinksHere")===0 ) { // || strpos($title,"Special:RecentChangesLinked")===0 ) {
                return true;
    # debug("*** $title must be permitted");
            }
    return true;
            break;
    }
    break;
    }
         }
         }
        }
   
if ( in_array('sysop',$user->getGroups()) ) {
    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();
 
// get the users groups
    // get the users groups
$userGroups = $user->getGroups();
    $userGroups = $user->getGroups();
$hasUserCategory = false;
    $hasUserCategory = false;
$userCategoryMatched = false;
    $userCategoryMatched = false;
 
// now check the page categories against the system groups
    // now check the page categories against the system groups
$publicPage = false;
    $publicPage = false;
$privatePage = false;
    $privatePage = false;
$editUserMatched = false;
    $editUserMatched = false;
$editGroupMatched = false;
    $editGroupMatched = false;
 
// 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 ) );
        $formatedKey = substr( $key, ( strpos( $key, ":" ) + 1 ) );
 
# debug( "category:".$formatedKey."<br/>" );
        # debug( "category:".$formatedKey."<br/>" );
 
// check for the exclusive category public
        // check for the exclusive category public
if ( $formatedKey=='public' ) {
        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($formatedKey,$userGroups) ) {
            if (! in_array($formatedKey,$userGroups) ) {
$privatePage = true;
                $privatePage = true;
}
            }
}
        }
// check for the exclusive category user:*
        // check for the exclusive category user:*
elseif ( strpos( $formatedKey, 'user:' ) === 0  ) {
        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( $formatedKey.' '.$name.'<-name<br/>' );
            # debug( $formatedKey.' '.$name.'<-name<br/>' );
if ( $name && $formatedKey===$name ) {
            if ( $name && $formatedKey===$name ) {
# debug( '**** user permitted by user category'.$formatedKey.' '.'allowed<br/>' );
                # debug( '**** user permitted by user category'.$formatedKey.' '.'allowed<br/>' );
$userCategoryMatched = true;
                $userCategoryMatched = true;
}
            }
}
        }
elseif ( strpos( $formatedKey, 'edit:user:' ) === 0 ) {
        elseif ( strpos( $formatedKey, 'edit:user:' ) === 0 ) {
// check if user is not 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 === $formatedKey) {
                if ($name === $formatedKey) {
# debug('*** edit user matched'.$name);
                    # debug('*** edit user matched'.$name);
$editUserMatched = true;
                    $editUserMatched = true;
}
                }
}
            }  
}
        }
elseif ( strpos( $formatedKey, 'edit:' ) === 0 ) {
        elseif ( strpos( $formatedKey, 'edit:' ) === 0 ) {
//  check is user does not have groups to permit edit
            //  check is user does not have groups to permit edit
if ($action === 'edit') {
            if ($action === 'edit') {
$hasEdit = true;
                $hasEdit = true;
$group = substr( $formatedKey, ( strpos( $formatedKey, ":" ) + 1 ) );
                $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);
$editGroupMatched = true;
                    $editGroupMatched = true;
}
                }
}
            }
}
        }  
else {
        else {
// build up non-exclusive categories
            // build up non-exclusive categories
$systemCategory[ $formatedKey ] = $value;
            $systemCategory[ $formatedKey ] = $value;
}
        }
}
    }
 
// check if category:edit: marking found
    // check if category:edit: marking found
if ($hasEdit) {
    if ($hasEdit) {
if ($editGroupMatched) {
        if ($editGroupMatched) {
# debug('*** matched a category:edit:group marking');
            # debug('*** matched a category:edit:group marking');
return true;
            return true;
}
        }
else if ($editUserMatched) {
        else if ($editUserMatched) {
# debug('*** matched a category:edit:user: markingi');
            # debug('*** matched a category:edit:user: markingi');
return true;
            return true;
}
        }
else {
        else {
# debug('--- denied user by category:edit marking');
            # debug('--- denied user by category:edit marking');
return false;
            return false;
}
        }
}
    }
 
// check if page marked with the user:category
    // check if page marked with the user:category
if ($userCategoryMatched) {
    if ($userCategoryMatched) {
# debug('*** user allowed by user:category');
        # debug('*** user allowed by user:category');
return true;
        return true;
}
    }
 
// check if user is excluded by page marked private
    // check if user is excluded by page marked private
if ($privatePage) {
    if ($privatePage) {
# debug('--- denied because the page was marked private');
        # debug('--- denied because the page was marked private');
return false;
        return false;
}
    }
 
// check if user is excluded by another user:category
    // check if user is excluded by another user:category
if ($hasUserCategory) {
    if ($hasUserCategory) {
# debug('--- denied by user:category');
        # debug('--- denied by user:category');
return false;
        return false;
}
    }
 
// check if page was marked as public
    // check if page was marked as public
if ($publicPage) {
    if ($publicPage) {
# debug('*** permitted by public page');
        # debug('*** permitted by public page');
return true;
        return true;
}
    }
 
        // 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 ) ) {
        if ( in_array( $title, $wgWhitelistRead ) ) {
                        # debug("*** white listed" );
                # debug("*** white listed" );
                         return true;
                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");
// check if page has any remaining categories
    return false;
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) {
// 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;
}
}
</pre>
</pre>

Revision as of 12:12, 9 June 2023

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:
// 2023-06-08 Ralph Holland - include Special:Search and 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>" );

	//  RBH 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:Version':
		    case 'Special:Categories':
		    case 'Special:AllPages':
		    case 'Special:Search':		// added 2023-06-08	
		    case 'Special:RecentChanges';  // added 2023-06-08
                	# 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 ) {
			# 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:

  1. the special pages:
    are always permitted.
  2. pages marked with any Categories are examined to determine if they are marked with an Access Control marking as follows:
    1. when marked with a category:user: mark (containing a user name) then the page is accessible only to:
    2. when marked with a private [[:category:]] including a group (e.g. category:lesson) then then 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 in to the user in the Special:UserRights pages.
    3. 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
    4. when marked with a category:edit: mark the page is restricted for edit unless there is:
    5. 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.
  3. pages that are not marked with any categories are not accessible to an anonymous user.
  4. 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).

Code changes

references

categories