Extension:PermissionACL
|
|
If you need per-page or partial page access restrictions, you are advised to install an appropriate content management package. MediaWiki was not written to provide per-page access restrictions, and almost all hacks or patches promising to add them will likely have flaws somewhere, which could lead to exposure of confidential data. We are not responsible for anything being leaked, leading to loss of funds or one's job. For further details, see Security issues with authorization extensions |
|
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 | ||
|
|||
|
|||
|
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):
- which page : select of pages (namespaces, categories)
- which user : select of users (groups)
- which action : select of actions ( userCan actions - read, edit, create, move)
- 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 ?>
