RestrictAccessByCategoryAndGroup.php: Difference between revisions

From publications
No edit summary
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 users
// 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.
//                           changes so white-listed pages cannot override an exclusive category that denies access
//                 white-listed pages cannot override an exclusive category denying access
// 2022-08-15 Ralph Holland - made pages without a category not public, made p[[category:private]] exclusive
// 2022-08-15 Ralph Holland - made pages without a category not public, made private exclusive, implemented category:User:<name> as exclusive page access
//                            implemented [[category:User:<name>]] an exclusive page access marking
//
//
   
   
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]]',
         'mailto' => 'ralph.holland@live.com.au',
         'version' => '3.1.0-adapted-by-ralph-holland'
        'version' => '2.0.2-RBH-adapted-for-this-wiki'
);
);
   
   
Line 29: Line 30:
//$wgHooks['getUserPermissionsErrors'][] = '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 ) {
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 == 'Special:UserLogin') {
     if (strpos($title,'Special:')===0) {
        # debug( "*** login permitted" );
            switch($title) {
         return true;
        case 'Special:Login':
    }
         case 'Special:Logout':
    else if ( $title == 'Special:UserLogout') {
            case 'Special:UserLogin':
         # debug( "*** logout permitted" );
            case 'Special:UserLogout':
        return true;
            case 'Special:Badtitle':
    }
            case 'Special:Random':
    else if ( $title == 'Special:Badtitle') {
            // case 'Special:RecentChanges':
        # debug( "*** bad title permitted" );
        case 'Special:Version':
        return true;
        case 'Special:Categories':
     }
         case 'Special:AllPages':
     else if ( in_array('sysop',$user->getGroups()) ) {
                    # 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 ) {
         $formattedKey = substr( $key, ( strpos( $key, ":" ) + 1 ) );
         $formatedKey = substr( $key, ( strpos( $key, ":" ) + 1 ) );
   
   
         #debug( "category:".$formattedKey."<br/>" );
         # debug( "category:".$formatedKey."<br/>" );
   
   
         // check for the exclusive category public
         // check for the exclusive category public
         if ( $formattedKey=='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($formattedKey,$userGroups) ) {
             if (! in_array($formatedKey,$userGroups) ) {
                 $privatePage = true;
                 $privatePage = true;
             }
             }
         }
         }
         // check for the exclusive category user:*
         // check for the exclusive category user:*
         elseif ( strpos( $formattedKey, '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( $formattedKey.' '.$name.'<-name<br/>' );
             # debug( $formatedKey.' '.$name.'<-name<br/>' );
             if ( $name && $formattedKey==$name ) {
             if ( $name && $formatedKey===$name ) {
                 # debug( '**** user permitted by user category'.$formattedKey.' '.'allowed<br/>' );
                 # debug( '**** user permitted by user category'.$formatedKey.' '.'allowed<br/>' );
                 $userCategoryMatched = true;
                 $userCategoryMatched = true;
             }
             }
         }
         }
         elseif ( strpos( $formattedKey, 'edit:user:' ) === 0 ) {
         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 === $formattedKey) {
                 if ($name === $formatedKey) {
                     # debug('*** edit user matched'.$name);
                     # debug('*** edit user matched'.$name);
                     $editUserMatched = true;
                     $editUserMatched = true;
Line 126: Line 138:
             }     
             }     
         }
         }
         elseif ( strpos( $formattedKey, 'edit:' ) === 0 ) {
         elseif ( strpos( $formatedKey, 'edit:' ) === 0 ) {
             //  check is user has a group 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( $formattedKey, ( strpos( $formattedKey, ":" ) + 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);
Line 138: Line 150:
         }     
         }     
         else {
         else {
             // build up non-exclusive categories that were assigned to the page
             // build up non-exclusive categories
             $systemCategory[ $formattedKey ] = $value;
             $systemCategory[ $formatedKey ] = $value;
         }
         }
     }
     }
Line 150: Line 162:
         }
         }
         else if ($editUserMatched) {
         else if ($editUserMatched) {
             # debug('*** matched a category:edit:user: marking
             # debug('*** matched a category:edit:user: markingi');
             return true;
             return true;
         }
         }
         else {
         else {
             # debug('--- denied user edit by category:edit marking');
             # debug('--- denied user by category:edit marking');
             return false;
             return false;
         }
         }
Line 183: Line 195:
     }
     }
   
   
    // 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
     // check if page has any remaining categories
     if ( count( $systemCategory ) == 0 ) {
     if ( count( $systemCategory ) === 0 ) {
         if ( count($userGroups) > 0 ) {
         if ( count($userGroups) > 0 ) {
             # debug('*** logged in users can access pages without categories');
             # debug('*** logged in users can access pages without categories');

Revision as of 17:16, 2 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 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';
//$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 ) {
            # 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:<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.
    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: the page is restricted for edit unless there is:
      • a [[:category:edit:user:<name>]] to permit that user, or
      • 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.
    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).

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.