DisallowAccessOnCategory/Documentation

Aus Free Software
Zur Navigation springen Zur Suche springen

Introduction

This Extension was created out of a desire to make it possible to restrict access to a set of pages quickly. This extension allows you to restrict or grant any kind of access based on the category of the page and the groups the user is in.

Warning: This extension was specifically developed for our internal wiki. Some of the features came more as a side-effect and are not very well tested (read: not at all). The basic functionality works though. Please feel free to post suggestions and bugreports to the DiscussionPage.

Features

This extension allows you to restrict (or grant) access to any page (that can be in a category) based on the categories the page belongs to and the rights the user has. It extends the standard MediaWiki permission System and makes it more powerfull.

You can:

  1. Associate (one or more) categories with (one or more) groups
  2. Define a different level of access (i.e. read/write, read-only...) for each group on each category
  3. Users that are in that group are now able to perform these actions on pages that are in that category
  4. Access defined with this extension is able to override restrictions the user would normally have, but this is disabled by default


Please read about security issues with authorization extensions. It may be a good Idea to have an extra page for checking security, so this is done here.

Configuration

Initial setup

First you have to download DisallowAccessOnCategory.php_0.2.tar.gz, unpack it into the extensions directory of your MediaWiki-Installation and make some changes to your LocalSettings.php:

require_once ("$IP/extensions/DisallowAccessOnCategory.php");
# set to false to disable the DisallowAccessOnCategory extension:
$wgDAOC_enable = true;
Whats restricted?

You of course have to know what you want to restrict. To do this, you must associate one or more Categories each with one or more groups. The extension will check if the requested page is in any of the categories defined here. If the page is in none of these categories, the extension wont have any effect. If it is in one or more of the categories, the extension will check for every matching category if the user is in any of the groups associated with the category. The extension will go through the categories/groups in exactly the order defined here, the first match will decide what permissions the user has on this page. The next chapter gives a little further explanation.

The following example should explain it a bit:

# The key (first column) is a category, the value (second column) is a group or set of groups.
$wgDAOC_Categories = array (
        "Project 1"     => array (
                            # you can also assign a value of 0 to deactivate the group.
                            "project_1_admin" => 1,
                            "project_1" => 1
                           ),
        "Project 2"     => "project_2",
        "Project 3"     => "project_3",
);

The first column defines a category, the second value defines a (set of) groups. As shown above, the group value can either be a string or an array. You can also, of course, only define a single category/group pair.

Note that neither the category nor the group have to exist at that point, if it doesn't, the extension just won't have any effect.

Order of Category/group pairs

If you define multiple pairs and/or multiple groups, the extension will depend on the order you specify your pairs in.

  • If a page is in none of the categories you specify in $wgDAOC_Categories, the extension wont make any effect.
  • If a page is in one of the categories, it will check all groups associated with it and grants the user the permissions defined for the first group in the array that the user is in. If the user is in none of the groups, it will deny access.
  • If a page is in more than one category, it will try to find a group associated with the first defined category that the user is in (just as above), however, if it doesn't find one, will continue to try with the second one, and so on.

Lets give an example:

$wgDAOC_Categories = array (
        "Project 1"     => array (
                            "project_1_admin" => 1,
                            "project_1" => 1
                           ),
        "Project 2"     => array (
                            "project_2_admin" => 1,
                            "project_2" => 1
                           ),
        "Project 3"     => "project_3",
);
  • A page that is in none of the defined categories the user will have normal access and the extension wont have any effect.
  • If a page is in the first category, the user will have all access that is defined by the first group associated with the category and that the user is a member of:
    • If he is a member of the "project_1_admin" group, he will have the access granted by that group
    • If he is not a member of the "project_1_admin" group but of the "project_1" group, he will have the access granted by the "project_1" group
  • If he is not a member in any of the associated groups, the extension will try continue with the next category that the page is in.
    • If a page is in none of the other categories, no match was found and access is denied.
    • If a page is in another category, the extension will try to find a matching group there.
Define access for groups

Next we have to actually define the user groups that have access to the pages and what kind of access they have. You do this by defining rights in the standard MediaWiki rights system, using $wgGroupPermissions. You of course have to use the group names you already used in $wgDAOC_Categories, the rights follow the convention:

$group_$category_$kind_of_access

So to define that the group "project_1" should have read access for pages in the "Project 1" category, we write:

$wgGroupPermissions['project_1']['project_1_Project 1_read'] = true;

Note that this setting of course won't have any effect if the category "Project 1" is not associated with the group "project_1".

To be able to quickly disable access to a group in a category, there is a "kill switch" that has to be set to true, otherwise all access will be denied:

$wgGroupPermissions['project_1']['project_1_Project 1'] = true;
a complete example

ok, now lets give a complete example. Lets assume we want the following:

  • Pages in the category "Car" should be
    • viewable by logged in users
    • editable only by users in the "Car Experts" group
  • Pages in the category "Plane" should only be
    • viewable by everybody (even anonymous)
    • eidtable only by users in the "Plane Experts" group
  • Some planes are also cars (hehe) and should be editable only by people in the "Multi_Experts" group.
    • "Multi_Expert" status should not automatically grant edit-access everywhere, only to Pages that are in "Both".
    • Only Car or Plane Experts should have read access, so some logged in users won't have access!
    • Since you currently cannot define multiple groups as key, we need an extra group for this: "Both"
  • Additionally, sysops should have edit-rights too, everywhere, no matter what other groups they are in.


Ok, so lets go:

$wgDAOC_enable = true;
$wgDAOC_Categories = array (
                        "Both"   => array (
                                       "sysops"         => 1,
                                       "Multi Experts"  => 1,
                                       "Car Experts"    => 1,
                                       "Plane Experts"  => 1
                                    ),
                        "Car"    => array (
                                       "sysops"       => 1,
                                       "Car Experts"  => 1,
                                       "user"         => 1
                                    ),
                        "Plane"  => array (
                                       "sysops"         => 1,
                                       "Plane Experts"  => 1,
                                       "*"              => 1
                                    )
                     );

# now the permissions
$wgGroupPermissions['Multi Experts']['Multi Experts_Both'] = true;
$wgGroupPermissions['Multi Experts']['Multi Experts_Both_read'] = true;
$wgGroupPermissions['Multi Experts']['Multi Experts_Both_edit'] = true;
$wgGroupPermissions['Car Experts']['Car Experts_Both'] = true;
$wgGroupPermissions['Car Experts']['Car Experts_Both_read'] = true;
$wgGroupPermissions['Plane Experts']['Plane Experts_Both'] = true;
$wgGroupPermissions['Plane Experts']['Plane Experts_Both_read'] = true;

$wgGroupPermissions['Car Experts']['Car Experts_Car'] = true;
$wgGroupPermissions['Car Experts']['Car Experts_Car_read'] = true;
$wgGroupPermissions['Car Experts']['Car Experts_Car_edit'] = true;
$wgGroupPermissions['user']['user_Car'] = true;
$wgGroupPermissions['user']['user_Car_read'] = true;

$wgGroupPermissions['Plane Experts']['Plane Experts_Plane'] = true;
$wgGroupPermissions['Plane Experts']['Plane Experts_Plane_read'] = true;
$wgGroupPermissions['Plane Experts']['Plane Experts_Plane_edit'] = true;
$wgGroupPermissions['*']['*_Plane'] = true;
$wgGroupPermissions['*']['*_Plane_read'] = true;

# and the sysop:
$wgGroupPermissions['sysop']['sysop_Both'] = true;
$wgGroupPermissions['sysop']['sysop_Both_read'] = true;
$wgGroupPermissions['sysop']['sysop_Both_edit'] = true;
$wgGroupPermissions['sysop']['sysop_Car'] = true;
$wgGroupPermissions['sysop']['sysop_Car_read'] = true;
$wgGroupPermissions['sysop']['sysop_Car_edit'] = true;
$wgGroupPermissions['sysop']['sysop_Plane'] = true;
$wgGroupPermissions['sysop']['sysop_Plane_read'] = true;
$wgGroupPermissions['sysop']['sysop_Plane_edit'] = true;
Finally?

Finally, all we have to do is add users to their groups unter Special:Userrights and of course add pages to the categories we defined. Please note that this extension can even restrict sysops, if they are not in the correct usergroup.

Whitelist or blacklist?

Depending on what you need, you might want to configure a blacklist or a whitelist:

  • A blacklist grants, per default, access to everything, restricted areas have to be explicitly defined.
  • a whitelist grants, per default, access to nothing, unrestricted areas have to be explicitly defined.

Turns out, with this extension, you can achieve both. The approach above is, by the way, a blacklist: pages have to be explictly added to a group to be restricted. To increase security, this extension never grants access that would normally be denied. So the following example:

$wgGroupPermissions['*']['read'] = false;
$wgGroupPermissions['project_1']['project_1'] = true;
$wgGroupPermissions['project_1']['project_1_Project_1_read'] = true;

will not grant read access to users in the group "project_1".

But to create a whitelist, you of course want exactly that. You can achieve that with another line in LocalSettings.php:

$wgDAOC_extendPrivileges = true;

if this variable is set to true, the above example will grant read access to users in the group "project_1" (to pages in the category "Project 1"), even if the 'read' permission is explicitly set to false. So lets give a whitelisted example, that should behave like this:

  • anonymous users have, per default, no access at all to any pages.
  • if a page is added to the category "public", anonymous users have read-access to it.
  • logged in users have, per default, read-access to all pages, but they should also be able to edit pages in the category "public"
  • sysops should have, of course, all permissions.

Ok, here we go:

# normal MediaWiki permissions
$wgGroupPermissions['*']['read'] = false;
$wgGroupPermissions['user']['read'] = true; # default (shown here for clarity)
$wgGroupPermissions['user']['write'] = false;

############################
# now the extension stuff: #
############################

# head
$wgDAOC_enable = true;
$wgDAOC_extendPrivileges = true;
$wgDAOC_Categories = array (
        "Public"     => array (
                           "sysop",
                           "user"
                        )
);

# Kill switches
$wgGroupPermissions['*']['*_Public'] = true;
$wgGroupPermissions['user']['user_Public'] = true;
$wgGroupPermissions['sysop']['sysop_Public'] = true;

# anonymous
$wgGroupPermissions['*']['*_Public_read'] = true;
# logged-in users
$wgGroupPermissions['user']['user_Public_read'] = true;
$wgGroupPermissions['user']['user_Public_edit'] = true;
# sysops
$wgGroupPermissions['sysop']['[sysop_Public_read'] =  true;
$wgGroupPermissions['sysop']['[sysop_Public_edit'] =  true;
$wgGroupPermissions['sysop']['[sysop_Public_move'] =  true;
$wgGroupPermissions['sysop']['[sysop_Public_delete'] =  true;
# and so on for the rest of the stuff

RecentChanges patch

Not really part of this extension, but noteworthy none the less. We didn't want the pages to appear in RecentChanges, also the RSS Feed still displayed changes to restricted pages to everyone. We solved this problem by patching SpecialRecentchanges.php with this patch. This is only tested with MediaWiki 1.9.0, but should be easy to apply to other versions of MediaWiki as well. Here is the code of the patch:

--- SpecialRecentchanges.php.old        2007-02-20 17:00:24.000000000 +0100
+++ SpecialRecentchanges.php    2007-02-22 14:58:02.000000000 +0100
@@ -171,7 +171,23 @@
        $rows = array();
        $batch = new LinkBatch;
        while( $row = $dbr->fetchObject( $res ) ){
-               $rows[] = $row;
+               $nt = Title::newFromText ( $row->rc_title , $row->rc_namespace ) ;
+               $id = $nt->getArticleID() ;
+
+               /* Patch for the DissallowAccessOnCategory Extension:
+                * The following snippet excludes Articles from RecentChanges
+                * if the user is not allowed to view its contents
+                */
+               global $wgContLang;
+               $categories = $nt->getParentCategories();
+               $categoryNSText = $wgContLang->getNSText ( NS_CATEGORY );
+               if ( gettype($categories) == "array" && array_key_exists("$categoryNSText:Protected", $categories) ) {
+                       global $wgUser;
+                       if ( ! ( $wgUser->isAllowed("$action") && $wgUser->isAllowed('protected') ) )
+                               continue;
+               }
+
+               $rows[] = $row; 
                if ( !$feedFormat ) {
                        // User page link
                        $title = Title::makeTitleSafe( NS_USER, $row->rc_user_text );
@@ -286,6 +302,17 @@
        foreach ( $rows AS $k => $r ) {
                $nt = Title::newFromText ( $r->rc_title , $r->rc_namespace ) ;
                $id = $nt->getArticleID() ;
+
+               /* patch for the DisallowAccessOnCategory extension:
+                * same as above, but this function is only called if 
+                * $wgAllowCategorizedRecentChanges is set to true
+                *
+
+               /* patch for the DisallowAccessOnCategory extension:
+                * same as above, but this function is only called if 
+                * $wgAllowCategorizedRecentChanges is set to true
+                */
+               global $wgContLang;
+               $categories = $nt->getParentCategories();
+               $categoryNSText = $wgContLang->getNSText ( NS_CATEGORY );
+               if ( gettype($categories) == "array" && array_key_exists("$categoryNSText:Protected", $categories) )
+                       continue;
+
                if ( $id == 0 ) continue ; # Page might have been deleted...
                if ( !in_array ( $id , $articles ) ) {
                        $articles[] = $id ;

I'm not entirely sure if this is the best possible approach but it works. Unfortunatly RecentChanges Page doesn't provide any hooks yet.

TODO

I'm pretty happy so far. Let me now if you have any feature-requests.

Bugs

  • Under MediaWiki 1.8.2: If you add a subpage to the Protected category, its parent-page is also protected. This is caused by the function getParentCategories in Title.php that, in 1.8.2, also returns the Categories of its subpages. I don't know a way around this problem, unfortunatly.