Last year, after a comparison of Wikis to find a solution for the most fundamental of all IT problems (documentation), ultimately DokuWiki beat FosWiki due to its ease. All the time, however, I didn’t come around yet to properly integrate DokuWiki with my desired setup.
I want to:
- expose my Wiki on a SSL-secured Apache VHost publicly
- with restricted access (ie. with ACLs)
- such that unregistered users do not know that there’s a Wiki (ie. a standard 403 Forbidden page)
- and, as it might not stay the only Web application on that host, with Single Sign-On (ie. users should not have to reenter username and password when they change applications).
This means enabling HTTP authentication in the VHost configuration and tweaking DokuWiki for consistent user experience.
Now the Apache configuration part is rather trivial. Instead of a htpasswd file I use mod_authnz_external, mod_authz_unixgroup and pwauth instead so I can authenticate against PAM. Either way, this means that the user gets the known username/password dialog and if Apache allows the access, PHP Web applications will receive the (already successfully validated) username and password in the variables $PHP_AUTH_USER and $PHP_AUTH_PW.
The tricky part is DokuWiki. When ACLs are used (as in my case), DokuWiki has a concept of users and groups (and ACLs based on these users and groups, of course). Out of the box, DokuWiki comes with different authentication methods, that is, sources for information on defined users and groups.
With the default plain method, DokuWiki stores user and group information itself in a text file. Unless disabled by configuration, users can then register themselves, change their password and have it reset in case they forgot it. Admins can also create, modify and delete users and groups.
However, with the other methods add, ldap, mysql and pgsql information comes from an Active Directory, a LDAP server, a MySQL database or a PostgreSQL database. With these authentication methods it is not guaranteed that all of the described functionality remains available. For example, the ldap method validates the username and password against the configured LDAP server and pulls out user information such as the user’s email address and his or her groups from the LDAP directory. It does currently not, however, support creation, modification and deletion of users and groups. For example, users can no longer change their password from within DokuWiki. This is not really a problem since how password changing is implemented depends on the site’s particular LDAP setup and such a procedure would be necessary outside of DokuWiki anyway.
Technically, DokuWiki versions up to the latest stable version “Adora Belle” from October 13th, 2012 used the notion of authentication backends, in which the mentioned methods would be implemented as classes inheriting from the auth_basic class and residing below inc/auth. With the current RC1 “Weatherwax”, authentication is realized as auth plugins inheriting from the new DokuWiki_Auth_Plugin class and residing below lib/plugins so that they can now be installed, configured and uninstalled just like any other DokuWiki plugin. Internally not much has changed: for example, one still needs to implement a __construct method that sets the $canDo array representing the method’s capabilities (eg. can it create users). More details on converting auth backends to auth plugins will surely arrive on the DokuWiki site soon.
This post, however, is not so much about the changes in DokuWiki but rather focusses on my use case illustrated above. In my scenario, DokuWiki’s authentication concept does not quite work out at first sight since it is centered around the assumption that it will itself ask for login details and communicate them to an exchangeable authentication source, be it plain, ldap or some other authentication backend (Adora Belle) resp. plugin (Weatherwax). Whereas in my scenario username and password have already been provided and are available through PHP’s $_SERVER array as shown above.
Actually, DokuWiki is not too ignorant of this fact. All requests go through doku.php, which includes inc/init.php to “load and initialize the core system“. In that file, in turn, inc/auth.php’s auth_setup() is called unless NOSESSION has been defined (which is only the case for some DokuWiki scripts that perform special functions and do not need authentication such as lib/exe/js.php). In auth_setup() we find the following code block:
// if no credentials were given try to use HTTP auth (for SSO) if(!$INPUT->str('u') && empty($_COOKIE[DOKU_COOKIE]) && !empty($_SERVER['PHP_AUTH_USER'])) { $INPUT->set('u', $_SERVER['PHP_AUTH_USER']); $INPUT->set('p', $_SERVER['PHP_AUTH_PW']); $INPUT->set('http_credentials', true); }
At this point, $INPUT->str('u') is always unset. If DokuWiki’s cookie has not been set yet, DokuWiki would thus always pick up the username and password as provided by PHP and validate these using the authentication backend/plugin, using either that backend/plugin’s trustExternal() method, if defined, or its checkPass() method (called through an AUTH_LOGIN_CHECK event which triggers auth_login() which in turn calls on checkPass()).
While this surely prevents the user from having to enter his or her credentials again, it may have annoying effects that depend entirely on your setup:
- If you configure HTTP authentication in a truly minimal manner as described in the first half of HTTP-Auth-Passthru (ie. no Require directive), you’re relying on DokuWiki to do the actual authentication. Single Sign-On will work as long as DokuWiki’s authentication backend or plugin uses the same authentication source as your other Web applications. However, DokuWiki will generate the 403 Forbidden error page itself, which violates my goals laid out at the top of this post. And obviously plain wouldn’t be a candidate here since no other Web app will be able to access it, you’d rather go with LDAP or a database.
- If you configure HTTP authentication in a probably more common manner, including Require, you tell Apache to do the authentication. In this case, DokuWiki will still do authentication on its own as shown in the code block above. Now we have to differentiate in more detail:
- If Apache and DokuWiki use the same authentication sources, DokuWiki’s authentication could actually be omitted, but it isn’t. So from a resources view you sacrifice an extra query and get in return the desired level of privacy with Apache generating the 403 Forbidden page. Again, plain wouldn’t be of much use: even if you succeeded in getting Apache to successfully authenticate against DokuWiki’s password file, other web apps would either have to rely on $PHP_AUTH_USER and $PHP_AUTH_PW (which is exactly what DokuWiki itself does not) or also use the password file. Not very feasible. LDAP or a database come to rescue again. Single Sign-On works just like in the case above.
- You might however also want to configure Apache and DokuWiki to at least partially use different authentication sources.
There are actually a number of use cases for the second scenario:
- You let Apache authenticate against PAM, which doesn’t provide users’ email addresses. Although you could assume that user ID = mail address and always append a certain mail domain, but that’s kind of a hack.
- Maybe you don’t want the system groups a user is member of to show up in DokuWiki and use custom groups instead.
- Or you have employees stored in your Active Directory whereas customers that also need Wiki access should be maintained within DokuWiki only.
In all of these use cases, we want to combine some Apache authentication module with DokuWiki’s plain auth plugin (although nothing keeps you from using mysql or pgsql). Now here’s the catch: you can’t just configure the VHost accordingly and let DokuWiki use the appropriate auth plugin directly. If you just set $conf['authtype'] = 'plain' (or mysql or pgsql), DokuWiki will use that auth plugin for everything, including its own authentication! Which means two things:
- Just because users pass the HTTP authentication successfully doesn’t mean that they’ll automatically have a DokuWiki account. They’d have to register manually first, a process in which users specify their E-Mail address and DokuWiki sends them a password it generated on its own. Which will differ from the password used in the HTTP authentication, rendering it pretty useless.
- Even if DokuWiki re-used the HTTP authentication password internally as well, the user could still set a different password with the “Update profile” function which again would break Single Sign-On.
DokuWiki isn’t really to blame for this: it can not distinguish between the three scenarios discussed because the $PHP_AUTH_USER/$PHP_AUTH_PW variables are present in all three of them. And it can’t know what data source the Apache HTTP authentication beforehand used.
Now you could of course patch out the respective parts in DokuWiki’s sources (try searching for “logon” and “modPass”), but that’s of course not really a solution and it won’t survive upgrades to newer DokuWiki versions. Instead, what we need is a auth plugin that splits method calls to one of two auth plugins used in the background:
- The first auth plugin would be new as well and named http. It offers nothing but a checkPass() implementation which simply compares the supplied username and password to $PHP_AUTH_USER/$PHP_AUTH_PW, meaning that a DokuWiki login with anything but the HTTP authentication credentials is denied.
- For the second auth plugin we’d just use plain, mysql or pgsql, depending on your needs. It’ll take care of everything not handled by the http plugin.
This concept is nothing new: ggauth is a set of “experimental auth backends” already developed since 2008. It has been developed by Grant Gardner and comprises among others the mentioned split and http auth backends. This already states the only issue with them: while WeatherWax still supports the old auth backends, they could need porting to the new auth plugin concept. I might take a look at that.
Last not least, for reference here’s a table indicating the advertised capabilities of WeatherWax’s auth plugins (the data should apply to the older auth backend versions as well):
Capability | authplain | authad | authldap | authmysql | authpgsql |
---|---|---|---|---|---|
addUser | + | – | – | + | + |
delUser | + | – | – | + | + |
modLogin | + | – | – | + | + |
modPass | + | + | – | + | + |
modName | + | + | – | + | + |
modMail | + | + | – | + | + |
modGroups | + | – | – | + | + |
getUsers | + | + | – | + | + |
getUserCount | + | – | – | + | + |
getGroups | – | – | – | – | – |
external | – | – | – | – | – |
logoff | – | – | – | – | – |