Extension:Read Restrict

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

Release status: experimental

Implementation User rights
Description Stops reading of pages except to specific groups.
Author(s) Suki
MediaWiki Tested on version 1.9.3
License No license specified
Download Copy/Paste from here
Hooks used
userCan
Check usage and version matrix

This is my en devour to lockdown my MediaWiki site. It has some assumptions on the setup so mileage will vary.

One of the more notable dependency is that it will need two databases. Each one with the same prefix (or scheme). I tried an install that used table prefixes and it didn't work. Not sure if they were the same or not. My tests without prefixes did work

For this I used MediaWiki/1.9.3 and MySQL/5.0.27, on Apache/1.3.33 with PHP/5.2.1. This was tested on a "Mac OS X"/10.4.9 (darwin) system.

This extension or the concepts here have not been tested against any of the issues addressed in Security issues with authorization extensions. Use at your own risk.

-- Suki 21:00, 26 March 2007 (EDT)

Contents

Concepts [edit]

In this setup I needed part of the Wiki to be publicly accessible and another part locked down to just those in a special group. I refer to this group as the family version because it fit the needs for what I was accomplishing.

I had a home site that anyone can contribute to but only family members could work on the hidden pages. This was asked by those in my family to avoid personal information from being archive in search engines and prowlers.

So I decided to make two separate Wiki's one for the world and one for the family. I thought that I could lock the family one down using some authentication perhaps provided by the apache server. However I found that MediaWiki (at least since version 1.9.3) had some facilities to accomplish this via a well crafted extension and the "userCan" hook.

I perceive that most Security issues with authorization extensions are blocked because the user is unable to retrieve the pages required to interface with the feature. For example search would work because the user cannot "read" the search results. Although none of that has been tested so I give no suggestion that this stuff actually works.

This was usefull because the two Wiki's could still use the same user names and avoid having to login twice when switching between them. It also gave the advantage of interlinking them with the Help:Interwiki linking feature.

Below I will try my best to describe the prccess I used to achieve this set up.

Setting up the first Wiki [edit]

I downloaded the latest MediaWiki (1.9.3 at the time) and untar'ed it to my DocRoot. I named the directory main_wiki.

prompt$ tar zxvf mediawiki-1.9.3.tar.gz
prompt$ mv mediawiki-1.9.3 main_wiki
  1. Extract the contents of the archive.
  2. Move the created directory (mediawiki-1.9.3) to something more understandable (main_wiki)

I then set my permissions for the writeable directories:

prompt$ cd main_wiki
prompt$ chmod 777 config
prompt$ chmod 777 images #Optional for file uploads
  1. Change to the new directory (main_wiki)
  2. Change the permissions to all readable all writable for the config directory.
  3. Do the same for the images directory. Optional if you use image uploads.

(The web server needs write access to these directories. So we just make them writable for all.)

I ran the setup and created a database specific for the Main Wiki. I also took note of the database Name, Username, Password, and Table Prefix.

Finish the setup and move the LocalSettings.php to the root and close up the permissions on the config directory.

prompt$ mv config/LocalSettings.php .
prompt$ chmod 755 config
  1. move the file (LocalSettings.php) that the web server created in the config directory to the root (main_wiki).
  2. change the permissions of the config directory back to it default to prevent further write access to it.

Setting up the second Wiki [edit]

This step was a bit more complicated. I didn't like the idea of having two versions of the source. I wanted one place to store the files and one place to upgrade from. However I didn't want the data to mix since I was planning to lock down the second wiki. So the image used for uploading media would need to be seperate. And since the setting would be different I would need a seperate config directory to run the setup.

I decided to use symbolic links found on most unix based systems. I'm very confident that these ideas will work if one has two seperate copies of the code in different directories.

Linking the source [edit]

Make a new drectory and link the entire source tree:

prompt$ cd .. #Should now be in the DocRoot
prompt$ mkdir family_wiki
prompt$ cd family_wiki
prompt$ ln -s ../main_wiki/* .
  1. changed directory to the above (Now the Document root where the main_wiki resides.
  2. create directory (family_wiki)
  3. change directory to the new directory
  4. create a symbolic link to the first wiki directory (main_wiki) or you could copy the contents over.

As described above some things should not be in the cloned tree. I removed LocalSettings.php, config, and images. Then I recreated custom ones.

prompt$ rm LocalSettings.php config images
prompt$ mkdir images
prompt$ chmod 777 images
  1. delete LocalSettings.php, delete the directories config and images.
  2. create directory images
  3. change the permissions so the web server has writable access to directory images.

The config directory has the setup page inside it so we have to sym-link that after we recreate the directory.

prompt$ mkdir config
prompt$ chmod 777 config
prompt$ cd config
prompt$ ln -s ../../main_wiki/config/index.php .
prompt$ cd .. #Should now be in the family_wiki root
  1. create the directory config
  2. change the permissions so the web sever has writable access to the configdirectory.
  3. change directory to config
  4. create a symbolic link to the index.php file found in the main_wiki's config directory. Or copy the file here.
  5. change directory to family_wiki

Please note that the extensions directory doesn't need to be seperated because you choose which extensions load in the LocalSettings.php which is seperated so all extensions can be stored in one place.

Running the setup of the second wiki [edit]

Now we do the exact same procedure as above and run the setup of the second wiki. Open the second wiki in the browser. It is very important that the database table prefixes are the same or this will not work. I am under the impression that the Username and the Password should be the same however at the very least the choice of user must have access to both databases. And of course, a second database must be created.

Also note that the use that is created as the Admin for this second wiki is just a place holder and is not used once the two are linked. (See bellow)

After running the setup we close up the second wiki by moving the LocalSettings.php to the root and locking down the permissions to the config directory.

prompt$ mv config/LocalSettings.php .
prompt$ chmod 755 config
  1. move the file (LocalSettings.php) that the web server created in the config directory to the root (family_wiki).
  2. change the permissions of the config direwctoy back to it default to prevent further write access to it.

There should be two seperate wiki's installed. Next is how I linked the two together.

Inter-linking the two together [edit]

I linked the two wiki's together first by using the same user table for both and also using the Help:Interwiki linking feature to link the two so they could become a Wiki family.

Using the same user table [edit]

In order for the two wiki's to use the same users I used the $wgSharedDB option in the latest MediaWiki. This basically tells the current wiki that it should look up the user info from another wiki database. This does not as far as I can tell link the group information. I belive everything is separated except the info in the user table (username, password, etc)

Open up the second wiki's LocalSettings.php and add the first wiki's databse to it.

...
$wgSharedDB = "first_wiki_db";
...

Since the second wiki will be manipulating the first wiki's user data the password salt needs to match. So if we open the first wiki's LocalSettings.php and find the $wgProxyKey variable and copy it to the Second wiki's LocalSettings.php

...
#Second wiki's original $wgProxyKey commented out:
#$wgProxyKey = "b4ba0f901a90baf79a878202e43bf7750d87bb7c683e03f4fb10f56b4fb45fde";
#First wiki's $wgProxyKey setting copied:
$wgProxyKey = "e2b3d33282cf934253cfc36ef1a36a3c9f7caa3632b9ff497f0160b4a6683177";
...

Now the two wiki's use the same user table. Logging in one will log into the other. creation of a user in one will create one in the other. The only thing separate user wise is the groups.

InterWiki Linking [edit]

Adding non standard Interwiki links are not as easy. You have to manually manipulate the database tables for both wiki's. If you have phpMyAdmin you can add the links with that. I will place the SQL below for good measure.

Add an InterWiki link from the first wiki to the second. (I chose family as the second name and main as the first) And yes the path can be relative.

INSERT INTO `sukiwiki1`.`interwiki` (
  `iw_prefix` ,
  `iw_url` ,
  `iw_local` ,
  `iw_trans`
)
VALUES (
  'family', '../family_wiki/index.php/$1', '1', '0'
);

See Help:Interwiki linking#Adding more for what each field does. Above is what I tested on my site. If you use the iw_trans you need to set $wgEnableScaryTranscluding = true.

Now add the same for the second wiki.

INSERT INTO `sukiwiki2`.`interwiki` (
  `iw_prefix` ,
  `iw_url` ,
  `iw_local` ,
  `iw_trans`
)
VALUES (
  'main', '../main_wiki/index.php/$1', '1', '0'
);

That should complete the Interwiki linking. Also note that this should not affect the security described below when using this setup. Because the Interwiki logic uses URL's to link not internal databases the use has to go through the authentication process for the linked wiki. (Feature? Hell yeah!)

Locking down the second Wiki [edit]

I used the userCan hook to test who was looking to read the pages and determine if that user is in the group with access. So the first thing we have to do is create such a group. I chose to use the name "family" but you can do any one you wish.

To add a new group you have to use it programatically. By installing the below extension it will add the group to the wiki. Copy and paste ReadRestrict.php to the extensions directory of the second wiki.

Edit the first line to choose what group you want to use. (I chose family)

Add this at the end of your second wiki LocalSettings.php file.

require_once ("extensions/ReadRestrict.php");

While logged into the second wiki as a SysOp. Open Special:Userrights and add the group to each user you want to have access.

And that should complete the instalation.

Additional Resources and Tips [edit]

I would suggest changing the style for each wiki that way the users will have a better idea which wiki they are working on. I would hope it would lower the confusion and help prevent accidental content showing up in the unrestricted wiki which was meant for the restricted users.

You should also lockdown edits from anonymous at least on the restricted wiki.

$wgGroupPermissions['*']['edit'] = false;

I recomend the following extensions:

  • Extension:ConfirmEdit Captcha Extension (Works for user creation too. and tested on MediaWiki/1.9.3)
  • My Blog extension (For adding blogs to the wiki's)

ReadRestrict.php [edit]

<?php
# Which groups are allowed to access the pages.
$wgReadAllowableGroups = array (
    'sysop',
    'family'
);
 
# Which pages out trump any restrictions (Used to allow user login etc.)
# If you use [[Extension:ConfirmEdit]] you must add the following:
#    "Special:Captcha",
#    "Special:Captcha/help",
#    "Special:Captcha/image",
$wgWhitelistRead = array(
    "Special:Userlogin",
    "Special:Userlogout",
    "Special:Captcha",
    "Special:Captcha/help",
    "Special:Captcha/image",
    "-",
    "MediaWiki:Monobook.css"
);
 
# Stop all access to those not allowed.
function familyAuthUserCan ($title, $user, $action, $result)
{
    global $wgReadAllowableGroups, $wgWhitelistRead;
    // if null is returned then the other things in the hook chain are checked, the result is still undetermined
    // if true is returned then can the user do want he want to do, the result is true
    // if false is returned then the evaluation is aborted and the result is false
 
    if ($action == 'read')
    {
        if (in_array ($title->getPrefixedText(), $wgWhitelistRead))
        {
            // Allow everyone to access the pages in trumpTitles regardless
            $result = true;
        }
        elseif (0 < count(
            array_intersect ($wgReadAllowableGroups, $user->getGroups())))
        {
            // The $user is in the $wgReadAllowableGroups so allow access.
            $result = true;
        }
        else
        {
            // Deny access
            $result = false;
        }
    }
    // Else don't touch $result to leave the address to null
}
 
# Add the hook to userCan
$wgHooks['userCan'][] = 'familyAuthUserCan';
 
# Open up permission read for the groups in $wgReadAllowableGroups.
# FIXME: Is this needed when using the userCan hook?
foreach ($wgReadAllowableGroups as $group)
{
    $wgGroupPermissions[$group]['read'] = true;
}
 
# Lock down all read access.
$wgGroupPermissions['*']['read'] = false;
$wgGroupPermissions['user']['read'] = false;
 
# Add login pages to be accessed by anyone.
$wgWhitelistRead['*'] = $wgWhitelistRead;
$wgWhitelistRead['user'] = $wgWhitelistRead;

See also [edit]