Extension:PermissionACL

From MediaWiki.org
Jump to: navigation, search
MediaWiki extensions manual - list
Crystal Clear action run.png
PermissionACL

Release status: beta

Implementation User rights
Description implements per-{namespace, page, category, user, group} access permissions
Author(s) Jan Vavříček (VavricekTalk)
License GPL
Download Code
Parameters

$wgPermissionACL, $wgPermissionACL_Superuser

Hooks used
userCan

Check usage (experimental)

Contents

[edit] What can this extension do?

The PermissionACL extension implements a way to restrict access to specific {namespaces, pages, categories} based on user group or user name. This provides a more fine grained security model than the one provided by the default $wgGroupPermissions.

PermissionACL extension configuration is based on ACL (Access Control List) - list of rules which are processing from first to last. First applicable rule is used! On the end of list is implicit rule DENY TO ALL!

[edit] How secure is this extension?

Security issues with authorization extensions
Inclusion/transclusion safe - If Extension:RemoveProtectedContent is used as well
Preloading  ?
XML export (Special:Export) safe
Atom/RSS feeds  ?
Listings & search safe - If Extension:RemoveProtectedContent is used as well
Diff & revision links  ?
API  ?
Action links safe
Related rights  ?
Author backdoor safe
Caching  ?
Files & Images  ?
Redirects  ?
Edit Section safe
Watching Pages unsafe
Other extensions  ?
Known bugs Configuration is case-sensitive (e.g. Special:Userlogin is different to Special:UserLogin)

[edit] Usage

If $wgPermissionACL is set, then ACL model is used - if not, the extension will do nothing.

Rules are array elements and their order in array is used by ACL mechanism.

Syntax of rules (every rule has 4 parts):

  1. which page : select of pages (namespaces, categories)
  2. which user : select of users (groups)
  3. which action : select of actions ( userCan actions - read, edit, create, move)
  4. operation : permit or deny access

First, second and third rule part can be:

  • one value
  • array of values
  • ALL (represented by asterisk)

[edit] Summary of syntax

The following may be repeated multiple times to add rules to the ACL:

$wgPermissionACL[] = array(
                           {'group' | 'user'}, => {<username> | <groupname> | '*'} [, {<username> | <groupname> | '*'}...] ,
                           {'namespace' | 'page' | 'category'} =>  <namespace> [, <namespace>...] ,
                           'action'    => {'read', 'edit', 'create', 'move', '*'} [, {'read', 'edit', 'create', 'move', '*'}...] ,
                           'operation' => {'permit', 'deny'}
                          );

[edit] Example

This example configures for the scenario:

  • Namespaces: Private, Ccna, Ccnp, Ns, Fwl
  • User groups: private, ccna, ccnp, ns, fwl;
  • Group ccna has RW access only to namespace Ccna, group fwl to Fwl, ...
  • Group private has RW access to every namespace
  • Unlogged users can only read NS_MAIN namespace
  • Administrators (users "wikisysop" and "vav166") can do everything
require_once("$IP/extensions/PermissionACL/PermissionACL.php");
$wgExtraNamespaces = array( 100 => "Private",
                            101 => "Private_Talk",
                            102 => "Ccna",
                            103 => "Ccna_Talk",
                            104 => "Ccnp",
                            105 => "Ccnp_Talk",
                            106 => "Ns",
                            107 => "Ns_Talk",
                            108 => "Fwl",
                            109 => "Fwl_Talk" );
 
$wgGroupPermissions['ccna']['read'] = true;
$wgGroupPermissions['ccnp']['read'] = true;
$wgGroupPermissions['ns']['read']   = true;
$wgGroupPermissions['fwl']['read']  = true;
$wgGroupPermissions['private']['read'] = true;
 
//whitelist is used, but same thing can be done by ACL:
/*
$wgPermissionACL[] = array('group'     => '*',
                           'page'      =>  array('Special:UserLogin', 'Special:UserLogout', 'Special:Resetpass', 'Special:Confirmemail'),
                           'action'    => 'read',
                           'operation' => 'permit');
*/
$wgWhitelistRead = array('Special:Userlogin', 'Special:Userlogout', 'Special:Resetpass', 'Special:Confirmemail');
 
//Superuser is only simplification - same result will have ACL:
/*
$wgPermissionACL[] = array('user'      =>  array('wikisysop', 'vav166'),
                           'page'      => '*',
                           'action'    => '*',
                           'operation' => 'permit');
*/
$wgPermissionACL_Superuser = array('wikisysop', 'vav166');
 
$wgPermissionACL[] = array('group'     => '*',
                           'namespace' =>  NS_MAIN,
                           'action'    => 'read',
                           'operation' => 'permit');
 
$wgPermissionACL[] = array('group'     => 'user',
                           'namespace' =>  array(NS_MAIN, NS_SPECIAL, NS_USER, NS_CATEGORY),
                           'action'    => 'read',
                           'operation' => 'permit');
 
$wgPermissionACL[] = array('group'     => 'private',
                           'namespace' =>  array(100, 101, 102, 103, 104, 105, 106, 107, 108, 109),
                           'action'    => '*',
                           'operation' => 'permit');
 
$wgPermissionACL[] = array('group'     => 'ccna',
                           'namespace' =>  array(102, 103),
                           'action'    => '*',
                           'operation' => 'permit');
 
$wgPermissionACL[] = array('group'     => 'ccnp',
                           'namespace' =>  array(104, 105),
                           'action'    => '*',
                           'operation' => 'permit');
 
$wgPermissionACL[] = array('group'     => 'ns',
                           'namespace' => array(106, 107),
                           'action'    => '*',
                           'operation' => 'permit');
 
$wgPermissionACL[] = array('group'     => 'fwl',
                           'namespace' => array(108, 109),
                           'action'    => '*',
                           'operation' => 'permit');

[edit] Download instructions

Please cut and paste the code found below and place it in $IP/extensions/PermissionACL/PermissionACL.php.

Note: $IP stands for the root directory of your MediaWiki installation, the same directory that holds LocalSettings.php.

[edit] Installation

To install this extension, add the following to LocalSettings.php:

require_once("$IP/extensions/PermissionACL/PermissionACL.php");
 
#add configuration parameters here
#setup user rights here

[edit] Code

<?php
 
if( !defined( 'MEDIAWIKI' ) ) {
    echo("This file is an extension to the MediaWiki software and cannot be used standalone.\n");
    die(1);
}
 
$wgExtensionCredits['other'][] = array(
        'name' => 'PermissionACL',
        'author' => 'Jan Vavříček',
        'url' => 'http://mediawiki.org/wiki/Extension:PermissionACL',
        'description' => 'permissions access control list',
);
 
$wgHooks['userCan'][] = 'PermissionACL_userCan';
 
function PermissionACL_userCan($title, $user, $action, &$result) {
    global $wgPermissionACL, $wgPermissionACL_Superuser, $wgWhitelistRead;
 
    $result = NULL;
 
    if(!isset($wgPermissionACL)) return TRUE; //if not set - grant access
 
    if($title->isCssJsSubpage()) return TRUE;
 
    if($action == 'read' && is_array($wgWhitelistRead)) { //don't continue if - read action on whitelisted pages
        if(in_array($title->getPrefixedText(), @$wgWhitelistRead)) {
            $result = TRUE;
            return TRUE;
        }//if-in_array
    }//if-whitelist
 
    if(isset($wgPermissionACL_Superuser)) { //Superuser can do everything - no need to read ACLs
        if(is_array($wgPermissionACL_Superuser)) {
            if(in_array(strtolower($user->getName()), ArrayToLower($wgPermissionACL_Superuser))) return TRUE;
        }//if-superusers array
        else if(strtolower($user->getName()) == strtolower($wgPermissionACL_Superuser)) return TRUE;
    }//if-superuser
 
    foreach($wgPermissionACL as $rule) { //process ACLs
        if(!PermissionACL_isRuleValid($rule)) continue; //syntax checking
 
        if(PermissionACL_isRuleApplicable($rule, $title, $user, $action)) {
            if(PermissionACL_Permit($rule)) {
                $result = TRUE;
                return TRUE;
            }
            else {
                $result = FALSE;
                return FALSE;
            }
        }//if-applicable
    }//foreach
 
    // If the user's trying to "read"/"edit" a page which doesn't exist; 
    // check for "create" and "createpage" actions
    if (
        (!($title->isKnown())) &&
        (
         ($action == 'read') ||
         ($action == 'edit')
        )
       ) {
      if (
          PermissionACL_userCan($title, $user, 'create', &$result) ||
          PermissionACL_userCan($title, $user, 'createpage', &$result)
         ) {
        return $result;
      }
    }
 
    //implicit end rule - DENY ALL
    $result = FALSE;
    return FALSE;
}//PermissionACL_userCan
 
function PermissionACL_isRuleValid($rule) {
    /* rule parts:
        'group' || 'user'
        'namespace' || 'page' || 'category'
        'action' = (read, edit, create, move, *)
        'operation' = (permit, deny)
    */
    $tmp_actions    = array('read', 'edit', 'create', 'move', '*');
    $tmp_operations = array('permit', 'deny');
 
    if( ( isset($rule['group']) && !isset($rule['user'])) ||
        (!isset($rule['group']) &&  isset($rule['user'])) ) {
 
        if( ( isset($rule['namespace']) && !isset($rule['page']) && !isset($rule['category'])) ||
            (!isset($rule['namespace']) &&  isset($rule['page']) && !isset($rule['category'])) ||
            (!isset($rule['namespace']) && !isset($rule['page']) &&  isset($rule['category'])) ) {
 
            if( isset($rule['action'])  && ((is_string($rule['action']) && in_array($rule['action'], $tmp_actions)) ||
                is_array($rule['action']) )) {
 
                if( isset($rule['operation']) && in_array($rule['operation'], $tmp_operations)) {
                    return TRUE;
                }//if-operation test
            }//if-action test
        }//if-namespace, page, category test
    }//if-user, group test
 
    return FALSE;
}//function PermissionACL_isRuleValid
 
function PermissionACL_isRuleApplicable($rule, $title, $user, $action) {
    //group|user rule
    if(isset($rule['group'])) { //group rule
        if(is_array($rule['group']))
            $tmp = ArrayToLower($rule['group']);
        else
            $tmp = strtolower($rule['group']);
 
        $groups = ArrayToLower($user->getEffectiveGroups());
        if(!( (is_string($tmp) && in_array($tmp, $groups)) ||
              (is_array($tmp) && count(array_intersect($tmp, $groups))>0)
            )) return FALSE;
    }
    else { // user rule
        if(is_array($rule['user']))
            $tmp = ArrayToLower($rule['user']);
        else
            $tmp = strtolower($rule['user']);
        $tmp2 = strtolower($user->getName());
 
        if(!( (is_string($tmp) && $tmp=='*') ||
              (is_string($tmp) && $tmp==$tmp2) ||
              (is_array($tmp) && in_array($tmp2, $tmp))
            )) return FALSE;
    }
 
    //namespace|page|category rule
    if(isset($rule['namespace'])) { //namespace rule
        $tmp = $rule['namespace'];
        $tmp2 = $title->getNamespace();
 
        if(!( (is_int($tmp) &&  $tmp==$tmp2) ||
              (is_string($tmp) && $tmp=='*') ||
              (is_array($tmp) && in_array($tmp2, $tmp))
            )) return FALSE;
    }
    else if(isset($rule['page'])){ //page rule
        $tmp = $rule['page'];
        $tmp2 = $title->getPrefixedText();
 
        if(!( (is_string($tmp) && $tmp==$tmp2) ||
              (is_string($tmp) && $tmp=='*') ||
              (is_array($tmp) && in_array($tmp2, $tmp))
            )) return FALSE;
    }
    else { //category rule
        $tmp = $rule['category'];
        $tmp2 = $title->getParentCategories();
        $categs = array();
 
        if(is_array($tmp2)) {
            global $wgContLang;
            $tmp_pos = strrpos($wgContLang->getNSText(NS_CATEGORY), ':');
 
            foreach($tmp2 as $cat => $page) {
                if($tmp_pos === FALSE) {
                    $categs[] = substr($cat, strpos($cat, ':')+1);
                }
                else {
                    $tmp_categ = substr($cat, $tmp_pos+1);
                    $categs[] = substr($tmp_categ, strpos($tmp_categ, ':')+1);
                }
            }
        }
 
        if(!( (is_string($tmp) && is_array($tmp2) && in_array($tmp, $categs)) ||
              (is_string($tmp) && $tmp=='*') ||
              (is_array($tmp)  && is_array($tmp2) && count(array_intersect($tmp, $categs))>0)
            )) return FALSE;
    }
 
    //action rule
    if(is_array($rule['action']))
        $tmp = ArrayToLower($rule['action']);
    else
        $tmp = strtolower($rule['action']);
 
    return ($tmp == $action) ||
          (is_string($tmp) && $tmp=='*') ||
          (is_array($tmp) && in_array($action, $tmp));
}//function PermissionACL_isRuleApplicable
 
function PermissionACL_Permit($rule) {
    return $rule['operation'] == 'permit';
}//function PermissionACL_Permit
 
function ArrayToLower($ar) {
    $tmp = array();
    foreach($ar as $index => $value)
        $tmp[$index] = strtolower($value);
    return $tmp;
}//function ArrayToLower
?>


Personal tools
Namespaces
Variants
Actions
Site
Support
Download
Development
Communication
Print/export
Toolbox