Skip to Content.
Sympa Menu

DokuWiki plugin

This plugin offers a set of functionalities, from simple Sympa-based authorization to full coupling between lists and wiki farm.

Sympa based authorization for DokuWiki

The idea is to authorize a user to access part of the wiki if he/she is subscribed to a list.

Using Sympa's SOAP server one can get the user's lists, we just need a way for DokuWiki to automatically do it and add the lists as groups to the user definition.

This plugin does just that (you can copy-paste the link in your wiki's plugin manager page).

Configuration parameters

Name Type Signification
enabled boolean Connect user groups to Sympa
mailSource "user" or "mail" User info to use as email address while fetching lists from Sympa
exclusive boolean Sympa groups only (don't load wiki groups)
soapService string URL to the Sympa server's SOAP wsdl
applicationID string Application ID to be used for authentication
applicationPwd string Password to be used for authentication

DokuWiki based lists wikis

One can easily add list wikis by installing a DokuWiki farm along with a Sympa TT2 plugin allowing to interact with the farm API.

The wiki farm doesn't need to be located on the same machine as Sympa, being in the same domain may help for authentication sharing though.

Installing DokuWiki and the farm plugin

In order to setup the farm you must first install a recent DokuWiki, preferably under the following layout :

/<farm_root>
  /farmer => DokuWiki engine
  /barn => where all wiki data and configs will be stored

And have your webserver alias /wiki to /<farm_root>/farmer and AllowOverride All on this path.

Next you have to install the farm plugin (you can copy-paste the link in your wiki's plugin manager page).

You may also want to use sympagroups.

If you use SSO genericsso may also interest you.

Enabling farming

Add tweak files

You then have to install the preload file under /<farm_root>/farmer/inc (see DokuWiki preload mecanism) :

preload.php:

<?php
 
/**
 * DokuWiki farm oriented preloader
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Etienne MELEARD <etienne.meleard AT renater.fr>
 */
 
// Config section
 
define('DOKU_FARM_ROOT', '/var/www/wikifarm/');
define('DOKU_FARM_BARN', DOKU_FARM_ROOT.'barn/');
define('DOKU_FARM_MULTIHOSTING', true);
 
 
// /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\
// /!\                                                                     /!\
// /!\  Do not alter after this point unless you know what you are doing   /!\
// /!\                                                                     /!\
// /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\ /!\
 
if(!DOKU_FARM_ROOT || !@is_dir(DOKU_FARM_ROOT)) die('You must set the DOKU_FARM_ROOT constant in '.__FILE__.' in order for the wiki farm to work');
if(!DOKU_FARM_BARN || !@is_dir(DOKU_FARM_BARN)) die('You must set the DOKU_FARM_BARN constant in '.__FILE__.' in order for the wiki farm to work');
 
if(!defined('DOKU_FARM_MULTIHOSTING')) define('DOKU_FARM_MULTIHOSTING', false);
 
$host = $_SERVER['SERVER_NAME'];
$bits = array_values(array_filter(explode('/', $_SERVER['REQUEST_URI'])));
$id = (count($bits) > 1) ? $bits[1] : null;
if($id == '_farmer_') $id = null;
 
$animal_id = null;
if($host && $id) $animal_id = (DOKU_FARM_MULTIHOSTING ? $host.'/' : '').$id;
if(!@is_dir(DOKU_FARM_BARN.$animal_id)) $animal_id = null;
 
define('DOKU_FARM_ANIMAL_ID', $animal_id);
define('DOKU_FARM_IS_FARMER', !(bool)$animal_id);
 
define('DOKU_FARM_HOST', preg_replace('`\:[0-9]+$`', '', $_SERVER['SERVER_NAME']));
 
define('DOKU_FARM_BASEPATH', $animal_id ? DOKU_FARM_BARN.$animal_id.'/' : DOKU_INC);
define('DOKU_FARM_BASEURL', ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on')) ? 'https' : 'http').'://'.$_SERVER['SERVER_NAME'].'/');
define('DOKU_FARM_BASEDIR', '/wiki/'.($animal_id ? $id : '_farmer_').'/');
 
define('DOKU_DEFAULT', DOKU_INC.'conf/');
define('DOKU_CONF', DOKU_FARM_BASEPATH.'conf/');
 
$plugin_protected = array(DOKU_DEFAULT.'plugins.required.php');
if($animal_id) $plugin_protected[] = DOKU_DEFAULT.'plugins.barn.protected.php';
 
$conf_protected = array(DOKU_DEFAULT.'farm.protected.php');
if($animal_id) $conf_protected[] = DOKU_DEFAULT.'barn.protected.php';
 
$conf_protected[] = DOKU_CONF.'protected.php';
 
global $config_cascade;
$config_cascade = array(
    'main' => array(
        'default'   => array(DOKU_DEFAULT.'dokuwiki.php'),
        'local'     => array(DOKU_CONF.'local.php'),
        'protected' => $conf_protected,
    ),
    'acronyms'  => array(
        'default'   => array(DOKU_DEFAULT.'acronyms.conf'),
        'local'     => array(DOKU_CONF.'acronyms.conf'),
    ),
    'entities'  => array(
        'default'   => array(DOKU_DEFAULT.'entities.conf'),
        'local'     => array(DOKU_CONF.'entities.conf'),
    ),
    'interwiki' => array(
        'default'   => array(DOKU_DEFAULT.'interwiki.conf'),
        'local'     => array(DOKU_CONF.'interwiki.conf'),
    ),
    'license' => array(
        'default'   => array(DOKU_DEFAULT.'license.php'),
        'local'     => array(DOKU_CONF.'license.php'),
    ),
    'mediameta' => array(
        'default'   => array(DOKU_DEFAULT.'mediameta.php'),
        'local'     => array(DOKU_CONF.'mediameta.php'),
    ),
    'mime'      => array(
        'default'   => array(DOKU_DEFAULT.'mime.conf'),
        'local'     => array(DOKU_CONF.'mime.conf'),
    ),
    'scheme'    => array(
        'default'   => array(DOKU_DEFAULT.'scheme.conf'),
        'local'     => array(DOKU_CONF.'scheme.conf'),
    ),
    'smileys'   => array(
        'default'   => array(DOKU_DEFAULT.'smileys.conf'),
        'local'     => array(DOKU_CONF.'smileys.conf'),
    ),
    'wordblock' => array(
        'default'   => array(DOKU_DEFAULT.'wordblock.conf'),
        'local'     => array(DOKU_CONF.'wordblock.conf'),
    ),
    'acl'       => array(
        'default'   => DOKU_CONF.'acl.auth.php',
    ),
    'plainauth.users' => array(
        'default'   => DOKU_CONF.'users.auth.php',
    ),
    'plugins' => array(
        'local'     => array(DOKU_CONF.'plugins.local.php'),
        'protected' => $plugin_protected,
    ),
    'userstyle' => array(
        'default' => DOKU_CONF.'userstyle.css',
        'print'   => DOKU_CONF.'printstyle.css',
        'feed'    => DOKU_CONF.'feedstyle.css',
        'all'     => DOKU_CONF.'allstyle.css',
    ),
    'userscript' => array(
        'default' => DOKU_CONF.'userscript.js'
    ),
);

You will most likely have to tweak the first 3 define at the top of the file.

Then you have to install the farm dedicated rewrite file under /<farm_root>/farmer/.htaccess :

.htaccess:

## Enable this to restrict editing to logged in users only
 
## You should disable Indexes and MultiViews either here or in the
## global config. Symlinks maybe needed for URL rewriting.
Options -Indexes -MultiViews +FollowSymLinks
 
## make sure nobody gets the htaccess, README, COPYING or VERSION files
<Files ~ "^([\._]ht|README$|VERSION$|COPYING$)">
    Order allow,deny
    Deny from all
    Satisfy All
</Files>
 
## Uncomment these rules if you want to have nice URLs using
## $conf['userewrite'] = 1 - not needed for rewrite mode 2
RewriteEngine on
 
## Not all installations will require the following line.  If you do, 
## change "/dokuwiki" to the path to your dokuwiki directory relative
## to your document root.
#RewriteBase /wiki
 
## If you enable DokuWikis XML-RPC interface, you should consider to
## restrict access to it over HTTPS only! Uncomment the following two
## rules if your server setup allows HTTPS.
#RewriteCond %{HTTPS} !=on
#RewriteRule ^lib/exe/xmlrpc.php$      https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]
 
#RewriteRule ^_media/(.*)              lib/exe/fetch.php?media=$1  [QSA,L]
#RewriteRule ^_detail/(.*)             lib/exe/detail.php?media=$1  [QSA,L]
#RewriteRule ^_export/([^/]+)/(.*)     doku.php?do=export_$1&id=$2  [QSA,L]
#RewriteRule ^$                        doku.php  [L]
#RewriteCond %{REQUEST_FILENAME}       !-f
#RewriteCond %{REQUEST_FILENAME}       !-d
#RewriteRule (.*)                      doku.php?id=$1  [QSA,L]
#RewriteRule ^index.php$               doku.php
 
 
# Custom rules for sympa wiki farm
# --------------------------------
 
RewriteBase /
 
# Go to portal if no animal specified
RewriteRule ^$             https://%{HTTP_HOST} [L,DPI,R=301]
 
# Rewrite accesses to animal root to DW main entry point
RewriteCond %{REQUEST_FILENAME}         !-f
RewriteCond %{REQUEST_FILENAME}        !-d
RewriteRule ^([^/]+)/?$                        /wiki/doku.php [QSA,L,DPI]
 
# Rewrite accesses to animal sub-section to the sub-path (strip animal name)
RewriteCond %{REQUEST_FILENAME}            !-f
RewriteCond %{REQUEST_FILENAME}            !-d
RewriteRule ^([^/]+)/(.*)                  /$2 [QSA,DPI]
 
# Here the animal name have been stripped, we don't need to care about it anymore ...
 
# Redirect accesses to the xmlrpc endpoint to https
#RewriteCond %{HTTPS} !=on
#RewriteRule ^/?lib/exe/xmlrpc.php$   https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]
 
# DW shorthands
RewriteRule ^/?_media/(.*)         /wiki/lib/exe/fetch.php?media=$1  [QSA,L,DPI]
RewriteRule ^/?_detail/(.*)                                /wiki/lib/exe/detail.php?media=$1  [QSA,L,DPI]
RewriteRule ^/?_export/([^/]+)/(.*)                            /wiki/doku.php?do=export_$1&id=$2  [QSA,L,DPI]
 
# Home
RewriteRule ^/?$                   /wiki/doku.php  [L,DPI]
 
# DW lib endpoints should not be considered as sub-sections
RewriteCond %{REQUEST_FILENAME}            !-f
RewriteCond %{REQUEST_FILENAME}            !-d
RewriteRule ^/?lib/(.*)                            /wiki/lib/$1  [QSA,L,DPI]
 
# Sub-section
RewriteCond %{REQUEST_FILENAME}        !-f
RewriteCond %{REQUEST_FILENAME}            !-d
RewriteRule /?(.*)                             /wiki/doku.php?id=$1  [QSA,L,DPI]
 
# Home access through index.php endpoint
RewriteRule ^/?index.php$          /wiki/doku.php [QSA,L,DPI]

From this point your farmer will be accessible under /wiki/farmer instead of /wiki.

Configuration

You may now configure your farm using the following files (merged in the specified order) :

Some parameters you have to set :

farm.protected.php:

<?php
 
$conf['savedir']     = DOKU_FARM_BASEPATH.'data';          // where to store all the files, do not change
$conf['basedir']     = DOKU_FARM_BASEDIR;                // absolute dir from serveroot, do not change
$conf['baseurl']     = DOKU_FARM_BASEURL;                // URL to server including protocol, do not change
$conf['useacl']      = 1;                // Use Access Control Lists to restrict access
$conf['userewrite']  = 1;                //this makes nice URLs with .htaccess
 
// To use genericsso authentication with Shibboleth
//$conf['authtype']    = 'genericsso';
//$conf['disableactions'] = 'register,resendpwd,profile';
//$conf['plugin']['genericsso']['emailAttribute'] = 'mail';
//$conf['plugin']['genericsso']['alwaysCheck'] = 1;
//$conf['plugin']['genericsso']['loginURL'] = 'http://'.$_SERVER['SERVER_NAME'].'/Shibboleth.sso/Login?target={target}';
//$conf['plugin']['genericsso']['logoutURL'] = 'http://'.$_SERVER['SERVER_NAME'].'/Shibboleth.sso/Logout?return={target}';
 
// To use sympagroups
//$conf['plugin']['sympagroups']['enabled'] = 1;
//$conf['plugin']['sympagroups']['mailSource'] = 'mail';
//$conf['plugin']['sympagroups']['exclusive'] = 1;
//$conf['plugin']['sympagroups']['soapService'] = 'https://'.$_SERVER['SERVER_NAME'].'/sympa/wsdl';
//$conf['plugin']['sympagroups']['applicationId'] = 'wiki';
//$conf['plugin']['sympagroups']['applicationPwd'] = 'foobar';
</code>
 
<file php barn.protected.php>
<?php
 
$conf['updatecheck'] = 0;                  // no auto update check for animals
$conf['disableactions'] .= ',check';       // no check command for animals
$conf['remote']      = 0;                  // no API (recommended unless another plugin needs it)

plugins.barn.protected.php:

<?php
 
$plugins['plugin']      = 0;   // no plugin management for animals
$plugins['testing']      = 0;  // no testing either
 
// If using genericsso
//$plugins['usermanager'] = 0;

protected.php:

<?php
 
$conf['remote']      = 1;   // enable API for the farmer

Allow API access from Sympa

To enable access from the remote Sympa server to the farm API you must define the /<farm_root>/farmer/conf/farm_remote_applications.conf :

farm_remote_applications.conf:

# Farm remote management applications secrets and authorized methods
remote_application sympa
secret some_secret_of_your_choice
methods *

Tweak Sympa

TT2 plugin

You will need the following CPAN modules : RPC::XML, JSON::XS

Create the directory /<sympa_bin_path>/Sympa/Template/Plugin and create the folowing files under it :

Dokuwiki.pm:

#!/usr/bin/perl
 
package Sympa::Template::Plugin::Dokuwiki;
 
use base qw( Template::Plugin );
use Template::Plugin;
 
use RPC::XML;
use RPC::XML::Client;
use JSON;
use Digest::SHA;
 
# Called as Sympa::Template::Plugin::Dokuwiki->load($context)
# by TT2 engine
sub load {
    my ($class, $context) = @_;
    return $class;
}
 
# Called as Sympa::Template::Plugin::Dokuwiki->new($context, @params)
# when using [% USE Dokuwiki(param1, param2, ...) %] in TT2 template
sub new {
    my ($class, $context, @params) = @_;
 
    my $stash = $context->stash();
 
    my $self = {
        'context' => $context,
        'robot' => $stash->get('robot'),
        'base_url' => $stash->get('base_url'),
        'sympa_url' => $stash->get(['conf', 0, 'wwsympa_url', 0]),
        'in' => $stash->get(['plugin', 0, 'dokuwiki', 0]), # Get plugin.dokuwiki.* query vars
        'config' => {},
        'wikiname' => undef,
        'xmlrpc_client' => undef,
    };
 
    $self->{'in'} = {} unless defined $self->{'in'};
 
    my $config_structure = {
        'remote_dokuwiki' => {
            'occurrence' => '0-n',
            'format' => {
                'name' => {
                    'format' => '.+',
                    'occurrence' => '1',
                    'case' => 'insensitive',
                },
                'xmlrpc_service_url' => {
                    'format' => '.+',
                    'occurence' => '1',
                    'case' => 'insensitive',
                },
                'application_name' => {
                    'format' => '.+',
                    'occurrence' => '1',
                    'case' => 'insensitive',
                },
                'secret' => {
                    'format' => '.+',
                    'occurrence' => '1'
                }
            }
        }
    };
 
    my $file = $class;
    $file =~ s/::/\//g;
    my $config_file = Sympa::Constants::MODULEDIR.'/'.$file.'.conf';
 
    my $config = Conf::load_generic_conf_file($config_file, $config_structure, 'abort');
    return $class->error('Config error') unless($config);
 
    foreach my $key (keys %$config) {
        if($key eq 'remote_dokuwiki') {
            $self->{'config'}{'remote_dokuwiki'} = {};
            foreach my $remote (@{$config->{$key}}) {
                $self->{'config'}{'remote_dokuwiki'}{$remote->{'name'}} = $remote;
            }
        }else{
            $self->{'config'}{$key} = $config->{$key};
        }
    }
 
    bless $self, $class;
 
    return $class->error('No remote dokuwiki selected') if($#params == -1);
    return $class->error('Unknown remote dokuwiki') unless(defined $self->{'config'}{'remote_dokuwiki'}{$params[0]});
    $self->{'config'}{'remote_dokuwiki'} = $self->{'config'}{'remote_dokuwiki'}{$params[0]};
 
    foreach my $k (keys %{$self->{'config'}{'remote_dokuwiki'}}) {
        my $base_url = $self->{'base_url'};
        $self->{'config'}{'remote_dokuwiki'}{$k} =~ s/\(%\s*base_url\s*%\)/$base_url/g;
        my $robot = $self->{'robot'};
        $self->{'config'}{'remote_dokuwiki'}{$k} =~ s/\(%\s*robot\s*%\)/$robot/g;
    }
 
    $self->{'xmlrpc_client'} = RPC::XML::Client->new($self->{'config'}{'remote_dokuwiki'}{'xmlrpc_service_url'});
 
    if($#params > 0) {
        return $class->error('Wiki does not exist') unless $self->exists($params[1]);
        $self->{'wikiname'} = $params[1];
    }
 
    return $self;
}
 
# Hande wiki creation / deletion through form
sub handleform {
    my ($self, $wikiname, $list) = @_;
 
    my $create = defined $self->{'in'}{'create_wiki'};
    my $delete = defined $self->{'in'}{'delete_wiki'};
    my $template = $self->{'in'}{'template'};
    my $exists = $self->exists($wikiname, 1);
 
    return $self->throw('You can\'t create and delete a wiki at the same time') if($create and $delete);
 
    return $self->throw('The wiki already exists') if($create and $exists);
 
    return $self->throw('The wiki doesn\'t exists') if($delete and not $exists);
 
    if($create) {
        my $customizations = {
            'placeholders' => {
                'list' => $list,
                'robot' => $self->{'robot'}, 
                'sympa_url' => $self->{'sympa_url'}
            }
        };
 
        unless($self->create($wikiname, $template, $customizations)) {
            return $self->throw('Wiki creation failed');
        }
 
        return 'created';
    }
 
    if($delete) {
        unless($self->delete($wikiname)) {
            return $self->throw('Wiki deletion failed');
        }
 
        return 'deleted';
    }
}
 
# Check if a wiki exists
sub exists {
    my ($self, $wikiname, $ignoreinput) = @_;
 
    # We may be creating / deleting this wiki
    if(defined $self->{'in'}{'wikiname'} and $self->{'in'}{'wikiname'} eq $wikiname and not $ignoreinput) {
        return 1 if defined $self->{'in'}{'create_wiki'};
        return 0 if defined $self->{'in'}{'delete_wiki'};
    }
 
    return $self->_call('exists', $wikiname);
}
 
# Create a wiki
sub create {
    my ($self, $wikiname, $template, $customisations) = @_;
 
    return $self->_call('create', $wikiname, $template, $customisations);
}
 
# Deletes a wiki
sub delete {
    my ($self, $wikiname) = @_;
 
    return $self->_call('delete', $wikiname);
}
 
# Make a signed call to the XMLRPC service
sub _call {
    my $self = shift;
    my $method = shift;
    my @args = @_;
 
    my @data = ();
    foreach my $a (@args) {
        if(ref $a eq 'HASH' or ref $a eq 'ARRAY') {
            push @data, JSON->new->canonical()->encode($a);
        }else{
            push @data, $a;
        }
    }
 
    my $signature = Digest::SHA::hmac_sha1_hex(join('&', @data), $self->{'config'}{'remote_dokuwiki'}{'secret'});
    unshift @args, $signature;
    unshift @args, $self->{'config'}{'remote_dokuwiki'}{'application_name'};
 
    print STDERR 'Calling XMLRPC method '.$method.'('.join(', ', @args).')'."\n";
 
    $result = $self->{'xmlrpc_client'}->send_request('plugin.farm.'.$method, @args);
 
    unless(ref $result) {
        print STDERR 'RPC::XML::Client '.$result."\n";
        return $self->throw('Wiki farm request failed');
    }
 
    if($result->is_fault) {
        print STDERR 'RPC::XML::fault '.$result->string.' ('.$result->code.')'."\n";
        return $self->throw('Wiki farm request failed');
    }
 
    return $result->value;
}
 
sub throw {
    my ($self, $error) = @_;
    die (Template::Exception->new('File', $error));
}
 
# Package must return trueish value
1;

Dokuwiki.conf:

remote_dokuwiki
    name groupware_wikis
    xmlrpc_service_url (%base_url%)/wiki/_farmer_/lib/exe/xmlrpc.php
    application_name sympa
    secret the_secret_you_choose_previously

Sympa interface tweaking

Add create/delete wiki section in list admin

Add the folowing snippet to your admin.tt2 template :

[% IF is_privileged_owner %]
    [% TRY %]
        [% USE Dokuwiki("groupware_wikis") %]
<div>
    <form name="manage_list_wiki" action="[% path_cgi %]" method="post">
        <input type="hidden" name="action" value="admin" />
        <input type="hidden" name="list" value="[% list %]" />
        <input type="hidden" name="plugin.dokuwiki.wikiname" value="[% robot %]/[% list %]" />
        <input type="hidden" name="plugin.dokuwiki.template" value="[% robot %]/_template_" />
        <fieldset>
            [% TRY %]
                [% done = Dokuwiki.handleform("${robot}/${list}", "${list}") %]
                [% IF done %]
                    [% IF done == 'created' %]
            <p class="success">The wiki has been created.</p>
                    [% END %]
 
                    [% IF done == 'deleted' %]
            <p class="success">The wiki has been deleted.</p>
                    [% END %]
                [% END %]
                [% IF Dokuwiki.exists("${robot}/${list}") %]
            <input class="MainMenuLinks" type="submit" name="plugin.dokuwiki.delete_wiki" value="[%|loc%]Delete wiki[%END%]" onClick="return request_confirm('[% FILTER escape_quote %][%|loc(list)%]Are you sure you wish to delete this list's wiki ?[%END%][%END%]');"/> [%|loc%]Warning : deletion is final, data recovery is not possible once done.[%END%]
                [% ELSE %]
            <input class="MainMenuLinks" type="submit" name="plugin.dokuwiki.create_wiki" value="[%|loc%]Create wiki[%END%]" /> [%|loc%]Please check out <a href="#">the wiki service terms and conditions</a>[%END%]
                [% END %]
            [% CATCH %]
            <p class="error">[% error.info %]</p>
            [% END %]
        </fieldset>
    </form>
</div>
<br/>
    [% CATCH %]
    <p class="error">[% error.info %]</p>
    [% END %]
[% END %]

Add the folowing snippet to your additional_list_menu_links.tt2 template :

[% TRY %]
[% USE Dokuwiki("groupware_wikis", "${robot}/${list}") %]
<li><a href="/wiki/[%list%]/">Wiki</a></li>
[% CATCH %]
[% END %]

Using federated auth in the lists wikis

If you are using SSO login (like shibboleth) for Sympa you can use this plugin to add the same login to your wiki (genericsso is a plugin that fetch an environment variable and use it to create a DokuWiki session).

Configuration parameters

Name Type Signification
emailAttribute string Environment variable to use as the user id and email
alwaysCheck boolean Does the plugin needs to check if the SP session is still open for each request (otherwise the wiki session will only be closed at logout)
loginURL string URL to use for loging-in, may contain {target} placeholder that will be replaced by the URL to return to after login
logoutURL string URL to use for loging-out, may contain {target} placeholder that will be replaced by the URL to return to after logout
Top of Page