[pmwiki-users] External Authentication

Gary Spivey gspivey at georgefox.edu
Fri Mar 31 01:20:21 CST 2006

Below is a potential cookbook module to enable external authentication.
The idea being that you can use LDAP or whatever yourself, and then set
some $_SESSION variable. I use these variables to do authentication, and
if it doesn't succeed, then pass it on to the normal authentication
routine. The theory is that all of the DefaultPasswords are set so that
any special authentication is done via the attributes on each page.

I have a couple of questions.

1) In my implementation I have one hack that I don't like. It seems that
several authentication requests are performed on each page (due to some
stuff on the template page). When I read the page the first time, I SDV
it in a global(AuthPage) so that each subsequent call get's the
attributes for the page that is currently desired rather than the
attributes of the template snippet. This doesn't seem like the right
thing to do. I have some comments in the code about some security
concerns. Any thoughts on how I ought to be doing this?

2) I would like to have a default value for some of these upon page
creation. I would like that default value to be set depending on which
Wiki group the pages are in. So, for instance, if I create pages in
Main, I want it so that the page is open. If I create new pages in a
different group, edit would be disabled for all but certain users. Any
thoughts on how I go about setting defaults based on the group?

Thanks for any help,


<?php if (!defined('PmWiki')) exit();

// Change the default AuthFunction to ExternAuth
$AuthFunction = 'ExternAuth';

// This is the HandlAuth array from pmwiki.php
// I want to change this so that the source action
// has the permissions of edit and not read.
// There isn't any reason to be looking at the source
// if you can't change it. And there may be things
// that I want to hide in the source.
$HandleAuth = array(
  'browse' => 'read', 'source' => 'edit',
  'edit' => 'edit', 'attr' => 'attr', 'postattr' => 'attr',
  'logout' => 'read', 'login' => 'login');

// This is the HandlActions array from pmwiki.php
// I want to change this so that the it used my ExternAuthHandleAttr
// where I changed the message 
$HandleActions = array(
  'browse' => 'HandleBrowse',
  'edit' => 'HandleEdit', 'source' => 'HandleSource',
  'attr' => 'ExternAuthHandleAttr', 'postattr' => 'HandlePostAttr',
  'logout' => 'HandleLogoutA', 'login' => 'HandleLoginA');

// The following are the attributes that we will allow for external
$ExternalAuthAttributes = array('read', 'edit', 'attr');
// I need to add some attributes to the page ...
// I clear the PageAttributes so that the only page by page attributes
// the external ones.
foreach($ExternalAuthAttributes as $level) {
  $uservar  = 'externauth_' . $level . '_user';
  $groupvar = 'externauth_' . $level . '_group';
  $PageAttributes[$uservar]   = '$[' . $level . ' usernames:]';
  $PageAttributes[$groupvar]  = '$[' . $level . ' groups:]';

// I am adding in a condition to make conditional inclusion easier
// Now I can simply say (:if authenticated :) stuff ... (:ifend:)
// or (:if authenticated level:) stuff ... (:ifend:) 
// (where level is one of the ExternalAuthAttributes
$Conditions['authenticated']  = 'authenticated(trim($condparm))';

// The ExternAuth function is a copy of PmWikiAuth with a simple
// modifications to check for external authorizations.
// The only addition are the lines ...
  if (authenticated($level)) {
      return $page;
// Which call the authenticated function before passing control to 
// the standard function. The assumption is that all functions are 
// nailed down with passwords so that anything beyond what is 
// allowed by the authenticate function requires a password.
// There is also a hack for the AuthPage. I need to save the page that 
// I am on so that I can read its attributes. I am confused about this.

function ExternAuth($pagename, $level, $authprompt=true, $since=0) {
  global $DefaultPasswords, $AllowPassword, $GroupAttributesFmt,
    $AuthCascade, $FmtV, $AuthPromptFmt, $PageStartFmt, $PageEndFmt,
  static $grouppasswd, $authpw;

  $page = ReadPage($pagename, $since);
  if (!$page) { return false; }

  // I need to save the page when it is found so that the attributes for
this page
  // get used on all elements of the page. I don't know if there is a
better way
  // to do this. If I don't do this, then subsequent page reads - like
the template
  // pages - overwrite the attributes - and so everything is then based
on the
  // the new attribute pages. This doesn't yet make sense to me. It
would seem 
  // that we would have kicked out. This also causes another problem.
  // can I include pages that I don't have authorization for and have
them use the
  // initial pages attributes? This is disturbing.
  // So, here I authenticate.
  // If the user is authenticated, return the page
  if (authenticated($level)) {
      return $page;
  // But if they aren't, what do we do? 
  // We assume that the site is configured with default passwords on
  // except read - so just pass it on ...

  $groupattr = FmtPageName($GroupAttributesFmt, $pagename);
  if (!isset($grouppasswd[$groupattr])) {
    $grouppasswd[$groupattr] = array();
    $gp = ReadPage($groupattr, READPAGE_CURRENT);
    foreach($DefaultPasswords as $k=>$v) 
      if (isset($gp["passwd$k"])) 
        $grouppasswd[$groupattr][$k] = explode(' ', $gp["passwd$k"]);
  foreach ($DefaultPasswords as $k=>$v) {
    if (isset($page["passwd$k"])) {
      $passwd[$k] = explode(' ', $page["passwd$k"]); 
      $page['=pwsource'][$k] = 'page';
    } else if (isset($grouppasswd[$groupattr][$k])) {
      $passwd[$k] = $grouppasswd[$groupattr][$k];
      $page['=pwsource'][$k] = 'group';
    } else {
      $passwd[$k] = $v;
      if ($v) $page['=pwsource'][$k] = 'site';
  $page['=passwd'] = $passwd;
  foreach($AuthCascade as $k => $t) {
    if (!$passwd[$k] && $passwd[$t]) 
      { $passwd[$k] = $passwd[$t]; $page['=pwsource'][$k] =
"cascade:$t"; }
  if (!isset($authpw)) {
    $sid = session_id();
    if (@$_POST['authpw']) @$_SESSION['authpw'][$_POST['authpw']]++;
    $authpw = array_keys((array)@$_SESSION['authpw']);
    if (!isset($AuthId)) $AuthId = @$_SESSION['authid'];
    if (!$sid) session_write_close();
  foreach($passwd as $lv => $a) {
    if (!$a) { $page['=auth'][$lv]++; continue; }
    foreach((array)$a as $pwchal) {
      if ($AuthId && strncmp($pwchal, 'id:', 3) == 0) {
        $idlist = explode(',', substr($pwchal, 3));
        foreach($idlist as $id) {
          if ($id == $AuthId || $id == '*') 
            { $page['=auth'][$lv]++; continue 3; }
          if ($id == "-$AuthId") { continue 3; }
      if ($pwchal == '' || crypt($AllowPassword, $pwchal) == $pwchal) 
        { $page['=auth'][$lv]++; continue 2; }
      foreach ($authpw as $pwresp)
        if (crypt($pwresp, $pwchal) == $pwchal)
          { $page['=auth'][$lv]++; continue 3; }
  if ($page['=auth']['admin']) 
    foreach($passwd as $lv=>$a) $page['=auth'][$lv]++;
  if ($page['=auth'][$level]) return $page;

  if (!$authprompt) return false;
  PCache($pagename, $page);
  $postvars = '';
  foreach($_POST as $k=>$v) {
    if ($k == 'authpw') continue;
    $v = str_replace('$', '&#036;', 
             htmlspecialchars(stripmagic($v), ENT_COMPAT));
    $postvars .= "<input type='hidden' name='$k' value=\"$v\" />\n";
  $FmtV['$PostVars'] = $postvars;
    "<p><b>Please authenticate to view this page</b></p>
     <p>Use the login box on the left</p>
     <p>If you are the Wiki administrator, you may the Wiki password
      <form name='authform' action='{$_SERVER['REQUEST_URI']}'
        Password: <input tabindex='1' type='password' name='authpw'
value='' />
        <input type='submit' value='OK' />\$PostVars</form>
        <script language='javascript'><!--
          document.authform.authpw.focus() //--></script>",

// The authenticate function assumes that there has 
// been prior authentication done by some external element.
// The results of the authentication are stored in $_SESSION.
// Of interest are the following variables:
// $_SESSION['authenticated'] - 1 if the user has authenticated, 0
// $_SESSION['username'] - The users username - a scalar
// $_SESSION['groups'] - an array of the users groups
// The authenticate functions performs the following algorithm:
// If there is no level to authenticate, simply return the
// users authentication status.
// If the level is one of the attributes that we check
// and there are no attribute entries, consider the match successful.
// Compare the user groups to the authorized group array
// Return TRUE if there is a match.
// Compare the username  to the authorized username array
// Return TRUE if there is a match.
// If this fails, then return FALSE
// The AuthPage is set as a global - when the page is read the first
time in,
// it is saved so that subsequent calls for authentication can use it. 
// This is necessary for the Site.PageActions template.
// I would think that this is saved somewhere globally, but I can't find
// OK - one more problem. Of all of the actions, read doesn't have a
// password. What happens if the page has a read value and we don't
// We fail, and it goes back to the general authentication system. This
means that
// a read failure is the same as a pass. So, what we need to do is go
see if there
// is a specification on one of the levels that we are checking, and if
// we need to ensure that the global password is set to something - if
it isn't set,
// we need to set it to the admin password.
function authenticated($level) {
  $page = $AuthPage;
  //echo "page = "; print_r(array_values($page)); 

  // Let's just bail if there is no authentication. 
  if (! $_SESSION['authenticated']) return false;
  // If there is no level, return the user's authentication status
  if (! $level) return $_SESSION['authenticated'];

  // This variable counts the number of specified elements in 
  // each group If it is nonzero, there is a restriction
  $UseExternAuth[$level] = 0;
  $uservar  = 'externauth_' . $level . '_user';
  $groupvar = 'externauth_' . $level . '_group';
  //echo "uservar = $uservar <br>";
  //echo "groupvar = $groupvar <br>";
  if ($page[$uservar]) 
    $UseExternAuth[$level] += count($Users  =
  if ($page[$groupvar]) 
    $UseExternAuth[$level] += count($Groups =
  //echo "Checking $level<BR>";
  //echo "UseExternAuth[$level] = " . $UseExternAuth[$level] . "<br>";
  //echo "Groups = "; print_r(array_values($Groups)); 
  //echo "Users  = "; print_r(array_values($Users)); 

  // If there are no specifications, consider it a match if it is one of
  // the variables for which we have attributes.
  if ($UseExternAuth[$level] == 0) {
    // Don't think this is necessary - if there is a variable for it, 
    // then it is one of our attributes. But, just to make sure.
    if (in_array($level, $ExternalAuthAttributes)) {
      return true;

  // If we have specifications, see if they match
  if ((array_intersect($_SESSION['groups'],$Groups))  ||
    //echo "$level is authenticated<br>";
    return true;

  // Before returning the failure, ensure that the appropriate password
is set.
  if ($UseExternAuth[$level]) {
    if (! $DefaultPasswords[$level]) {
      $DefaultPasswords[$level] = $DefaultPasswords['admin'];
      //echo "DefaultPasswrods[$level] = $DefaultPasswords[$level]<br>";
  //echo "$level is NOT authenticated<br>";
  return false;

// This is a copy of the function in pmwiki.php. I just wanted to change
the message.
function ExternAuthHandleAttr($pagename, $auth = 'attr') {
  global $PageAttrFmt,$PageStartFmt,$PageEndFmt;
  $page = RetrieveAuthPage($pagename, $auth, true, READPAGE_CURRENT);
  if (!$page) { Abort("?unable to read $pagename"); }
  XLSDV('en', array('EnterAttributes' =>
    "Enter new attributes for this page below.  Leaving a field blank
    will leave the attribute unchanged.  To clear an attribute, enter
    'clear'. <br>There are three attributes that can be controlled,
reading, editing, and access to this  attribute form. To restrict access
to a given user, enter the username in the appropriate field. For
multiple usernames, use a comma separated list.<br>Example:
bruth,haaron,bbonds<br>Access can also be restricted to groups. The
following groups are available:
  SDV($PageAttrFmt,"<div class='wikiattr'>
    <h2 class='wikiaction'>$[{\$FullName} Attributes]</h2>


More information about the pmwiki-users mailing list