RestrictAccessByCategoryAndGroup.php

From publications
Revision as of 18:02, 2 October 2022 by Ralph (talk | contribs) (→‎references)

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-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.
//                            changes so white-listed pages cannot override an exclusive category that denies access
// 2022-08-15 Ralph Holland - made pages without a category not public, made p[[category:private]] exclusive
//                            implemented [[category:User:<name>]] an exclusive page access marking
//
 
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]]',
        'mailto' => 'ralph.holland@live.com.au',
        'version' => '2.0.2-RBH-adapted-for-this-wiki'
);
 
$wgHooks['userCan'][] = 'restrictAccessByCategoryAndGroup';
//$wgHooks['getUserPermissionsErrors'][] = 'restrictAccessByCategoryAndGroup';
 
// old-school: this just outputs $a as a string after a few spaces into the HTML response so I can see what is happening without running an IDE/debugger
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 ( $title == 'Special:UserLogin') {
        # debug( "*** login permitted" );
        return true;
    }
    else if ( $title == 'Special:UserLogout') {
        # debug( "*** logout permitted" );
        return true;
    }
    else if ( $title == 'Special:Badtitle') {
        # debug( "*** bad title permitted" );
        return true;
    }
    else 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 ) {
        $formattedKey = substr( $key, ( strpos( $key, ":" ) + 1 ) );
 
        #debug( "category:".$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 ( strpos( $formattedKey, 'edit:user:' ) === 0 ) {
            // check if user is permitted to edit
            if ($action === 'edit') {
                $hasEdit = true;
                $name = 'edit:user:'.strtolower($user->getName());
                if ($name === $formattedKey) {
                    # debug('*** edit user matched'.$name);
                    $editUserMatched = true;
                }
            }    
        }
        elseif ( strpos( $formattedKey, 'edit:' ) === 0 ) {
            //  check is user has a group to permit edit
            if ($action === 'edit') {
                $hasEdit = true;
                $group = substr( $formattedKey, ( strpos( $formattedKey, ":" ) + 1 ) );
                if ( in_array($group,$userGroups) ) {
                    # debug('*** edit group matched '.$group);
                    $editGroupMatched = true;
                }
            }
        }    
        else {
            // build up non-exclusive categories that were assigned to the page
            $systemCategory[ $formattedKey ] = $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: marking
            return true;
        }
        else {
            # debug('--- denied user edit 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 anonynous users (those who have not logged-in), where the white-listing check includes the three special pages enumerated above.

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 wikis so far - lol.