DokuWiki

It's better when it's simple

User Tools

Site Tools


plugin:ssocas

SSOCAS Plugin

Compatible with DokuWiki

2009-02-14, anteater, rincewind, angua

plugin Provides Yale CAS single sign-on using the plain auth backend for users

Last updated on
2012-05-04
Provides
Action
Repository
Source

This extension has not been updated in over 2 years. It may no longer be maintained or supported and may have compatibility issues.

Similar to authplaincas, openid

Tagged with authentication, cas

After looking at the existing CAS implementations for DokuWiki, I decided I would prefer a simpler alternative, where users are still defined in users.auth.php and only the actual authentication is supplied by the CAS server. This has the advantage of being able to maintain the local user file via (S)FTP but also means that users might occasionally try to change their password on your site, not at the CAS server.

Caveat: I am not a security professional and this code is provided as-is (under GPLv2) for your experimentation. It has been in use on a membership site I put together for several years without problems, but suggestions for improvements are always welcome on this page.

Thanks to Aurélien Veillas for spotting the need for act_permcheck.

Installation

:!: This version is currently not compatible with modern versions of CAS.php - see the development section for workarounds. I'm working on integrating the fixes.

:!: If your installation cannot download the zip file, SSL transfers may not be enabled for PHP.

Install the plugin using the Plugin Manager and the download URL above, which points to latest version of the plugin. Refer to Plugins on how to install plugins manually. The local plugin directory must have the same name as the plugin is named, otherwise the plugin won't work properly.

Usage

Once the plugin is installed, check your site's configuration settings, and you should find a new section for SSOCAS:

SettingDefaultHelp textComments
nameCAS LoginCAS login service nameWhat you want your users to see when the plugin refers to the CAS service
localnameLocal LoginLocal login service nameThis will be displayed on the normal login form to differentiate it from the CAS login.
server CAS server DNS nameJust the server name, e.g. sso.example.com.
port443CAS server portOnly needed if your CAS service is not on the normal HTTPS port.
uri CAS server pathOnly needed if the CAS pages are not in / on the CAS server, e.g. sso.
version2CAS version (1 or 2 supported)
caslogoutNoLogout from CAS as well as DokuWiki?:!: Currently not working, but a workaround is known (below) and will be incorporated.
stickysessionNoKeep logins valid after the browser session ends?This allows the session to go on for a year.
logourl URL to a logo for the CAS service. If serving login pages via HTTPS, make sure this is either relative (/…) or an HTTPS URL.E.g. /_media/cas-logo.png; URLs without protocol also work: //server/path.
jshidelocalYesUse JavaScript to hide the local login form until required?

Development

Changes

Known Issues

  • None of the strings apart from settings help are localised.
  • CAS logout is not called in recent versions of DokuWiki (see below).
  • White screen of error VS. Redirect loop of doom (with recent versions of CAS.php; see below).

CAS Logout for recent dokuwiki version

In action.php file, there are 2 lines checking if $_SESSION[DOKU_COOKIE]['auth']['pass'] == 'CAS', so that plugin can call CAS logout url (if $conf['caslogout'] is set to 1 in default.php). It seems that recent version of dokuwiki use more password hash formats, so that this condition is always false, and CAS logout is never called. Replacing 'CAS' by its hashed version 'ab38a28edf84c693b1e1a48eb39299086b5ebe50' should make logout work again.

White screen of error VS. Redirect loop of doom

Since release 2014-05-05 “Ponder Stibbons” you can modify session parameters in DokuWiki . See Start session customization. Could that solve some of these issues?
Klap-inKlap-in

2014/03/24 15:05

As some people may have noticed, this no longer works with the latest version of phpCAS and DokuWiki. I am not the most familiar with the inner-workings of DokuWiki, or phpCAS. The error is that phpCAS has introduced an error message if it is editing an already existing session. However, if you tell phpCAS not to do any session, it can result in a redirect loop.

As a quick fix, I removed the check to see if there is already an existing session in phpCAS (some lines in the 590s of client.php in phpCAS). If anybody comes up with a better fix, please let us know.

cletnickcletnick

2010/10/11 05:33

Half-working fix

This hack of inc/init.php makes the error go away and a login to be successful. Basically phpCAS::client is called before dokuwiki calls session_start, instead of being called in action.php when the session is already created.

inc/init.php
   129      include_once('CAS.php');
   130      phpCAS::client(CAS_VERSION_2_0,'cas.example.com',443,'');
   131      session_start();

Another idea is do define('NOSESSION', true); in conf/local.php. This makes dokuwiki skip session handling which then succeeds for phpCAS but it also has the same problems as above.

Update: A modification to handle_action to set authentication information at each page reload makes this work:

        if (phpCAS::checkAuthentication()) {
        global $ACT, $auth, $conf, $INFO, $USERINFO;
          // user logged in, fill auth info so dokuwiki is happy
         $casuser = phpCAS::getUser();
         $_SERVER['REMOTE_USER'] = $casuser;
         $USERINFO = $auth->getUserData($casuser);
         $INFO = pageinfo();
        }

This is probably not the right way to do it, so please contribute if you know how.

— alfs 2011/02/11 11:27

Proposal

I have been testing the plugin.

In the function handle_action of file action.php:

  • I had to add a last empty parameter (or set to false) when calling phpCAS::client(…), to avoid “phpCAS error: phpCAS::client(): Another session was started before phpcas.”
  • Just after calling phpCAS::client, I have added the instruction session_start();, which seems to solve the infinite loop issue in my case.

I don't know if it has bad impacts in the rest of the program…

Here is what I did:

[...]
function handle_action (&$event, $param) {
        global $ACT;
        require_once ('CAS.php');
        // AVEILLAS - 20110518 - CASsification dokuWiki: start of modif"
        //phpCAS::client($this->getConf('version').'.0',$this->getConf('server'),(integer) $this->getConf('port'),$this->getConf('uri'));
        phpCAS::client($this->getConf('version').'.0',$this->getConf('server'),(integer) $this->getConf('port'),$this->getConf('uri'), false);
        session_start();
        // AVEILLAS - 20110518 - CASsification... end of modif."
        phpCAS::setNoCasServerValidation();
[...]

Ca you tell me if it works for you too?

— Aurélien 2011/05/19

Reply

Thank you Aurélien for your help, it seems that the session_start () solves the multiredirection problems. In fact, when I put this line

  phpCAS::client($this->getConf('version').'.0',$this->getConf('server'),(integer) $this->getConf('port'),$this->getConf('uri'));

or this line

  phpCAS::client($this->getConf('version').'.0',$this->getConf('server'),(integer) $this->getConf('port'),$this->getConf('uri'), false);
  

there is an error like this

  phpCAS::client(): phpCAS::client() has already been called 
  

so I comment these two lines. Thank you again for your help —24/05/2011

Report

Now (after adding session_start(), adding last empty parameter in phpCAS::client call, changing 'CAS' password to hashed string, and check perms) everything seems to work well.

— Aurélien Veillas 2011/06/06

Can Haz WhiteList?

After defeating the phpCAS updates of doom and successfully logging in, it was time to decide if I wanted to add all the users I wanted to be able to use CAS. Or, do I want to extend the plugin to allow account creation with a whitelist? Is that a challenge With my longing to refresh my simple PHP skills, I concluded that latter was a good plan.

Changes

What I did:

  • See note in Comments regarding my change to phpCAS.
  • Updated the logout function to not check if CAS is currently authenticated (This is a pain when debugging because CAS tries to save itself some calls to the CAS server and caches. Rapid setting changes in the debugging process result in this producing anomalous results. Also, no harm in sending the user to the CAS page even if they already logged out from CAS some other way.)
  • Updated the login function for increased flow control.

Functionality added:

  • Account creation
  • Whitelist checking
  • Whitelist config option (Comma separated list of allowed usernames. If this is big, I suggest pasting into your favorite text editor to manipulate)
  • Email domain config option (When creating the user, the user's email will be set to username@this.setting. This is useful for many organizations that have email addresses based on usernames.)
  • WhiteList override. Do you want to allow all users to log in? Check if so. (Will disable whitelist functionality and just let everybody log in)
  • Enable account creation. Do you want accounts to be created by this plugin? Check if so. (If you have accounts in the whitelist and this is set to false, even those accounts won't be created)

Note that I changed my specific install to user server validation. I suggest you do this as well. Also, this means I never was able to “test” the specific code I am posting (since I changed validation back in this code). If there is an error, it would likely be syntactical. Please feel free to make corrections if needed.

The Code

Here is the full action file with a modified login and logout function.

plugins/ssocas/action.php
<?php
/**
 * DokuWiki SSO CAS Plugin
 * 
 * @licence	GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author	Iain Hallam, Chris Letnick
 * @version	0.1
 */
 
/**
 * Copyright (C) 2010 Iain Hallam and Chris Letnick
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
 
// must be run within DokuWiki
if(!defined('DOKU_INC')) die();
 
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'action.php');
 
class action_plugin_ssocas extends DokuWiki_Action_Plugin {
	function getInfo() {
		return array (
			'author' => 'Iain Hallam',
			'email' => 'iain@iainhallam.com',
			'date' => '2009-09-22',
			'name' => 'SSO CAS Plugin',
			'desc' => 'Authenticate DokuWiki users via CAS',
			'url' => 'http://www.dokuwiki.org/plugin:ssocas',
		);
	}
 
	function register (Doku_Event_Handler $controller) {
		if ($this->getConf('server') != '') {
			$controller->register_hook ('HTML_LOGINFORM_OUTPUT', 'BEFORE', $this, 'handle_login_form');
			$controller->register_hook ('ACTION_ACT_PREPROCESS', 'BEFORE', $this, 'handle_action');
			$controller->register_hook ('TPL_ACT_UNKNOWN', 'BEFORE', $this, 'handle_template');
		}
	}
 
	function _self () {
		global $ID;
		return wl($ID, '', true, '');
	}
 
	function _selfdo ($do) {
		global $ID;
		return wl($ID, 'do=' . $do, true, '&');
	}
 
	function _redirect ($url) {
		header ('Location: ' . $url);
		exit;
	}
 
	function handle_login_form (&$event, $param) {
		global $auth;
		global $conf;
		global $lang;
		global $ID;
 
		// Remove the register and resendpwd links, if they exist.
		for ($formPosition = 0; $formPosition < count($event->data->_content); $formPosition++) {
			$formElement = $event->data->getElementAt($formPosition);
			if ((! is_array($formElement)) and (substr($formElement, 0, 2) == '<p')) {
				$event->data->replaceElement ($formPosition, NULL);
			}
		}
 
		$insertElement = 5;
 
		if($auth && $auth->canDo('addUser') && actionOK('register')){
			$event->data->insertElement($insertElement,'<p>'.$lang['reghere'].': <a href="'.wl($ID,'do=register').'" rel="nofollow" class="wikilink1">'.$lang['register'].'</a></p>');
			$insertElement = 6;
		}
		if ($auth && $auth->canDo('modPass') && actionOK('resendpwd')) {
			$event->data->insertElement($insertElement,'<p>'.$lang['pwdforget'].': <a href="'.wl($ID,'do=resendpwd').'" rel="nofollow" class="wikilink1">'.$lang['btn_resendpwd'].'</a></p>');
		}
 
		if ($this->getConf('logourl') != '') {
			$caslogo = '<img src="'.$this->getConf('logourl').'" alt="" style="vertical-align: middle;" /> ';
		} else {
			$caslogo = '';
		}
 
		$event->data->insertElement(0,'<fieldset><legend>'.$this->getConf('name').'</legend>');
		$event->data->insertElement(1,'<p style="text-align: center;">'.$caslogo.'<a href="'.$this->_selfdo('caslogin').'">Login</a></p>');
		$event->data->insertElement(2,'</fieldset>');
		if ($this->getConf('jshidelocal')) {
			$event->data->insertElement(3,'<p id="normalLoginToggle" style="display: none; text-align: center;"><a href="#" onClick="javascript:document.getElementById(\'normalLogin\').style.display = \'block\'; document.getElementById(\'normalLoginToggle\').style.display = \'none\'; return false;">Show '.$this->getConf('localname').'</a></p><p style="text-align: center;">Only use this if you cannot use the '.$this->getConf('name').' above.</p>');
			$event->data->replaceElement(4,'<fieldset id="normalLogin" style="display: block;"><legend>'.$this->getConf('localname').'</legend><script type="text/javascript">document.getElementById(\'normalLoginToggle\').style.display = \'block\'; document.getElementById(\'normalLogin\').style.display = \'none\';</script>');
		} else {
			$event->data->replaceElement(3,'<fieldset><legend>'.$this->getConf('localname').'</legend>');
		}
	}
 
	function handle_caslogin () {
		global $ACT, $auth, $conf, $INFO, $USERINFO;
 
		//phpCAS::setFixedServiceURL(DOKU_URL . 'doku.php?id=' . $QUERY);
 
		phpCAS::forceAuthentication();
		if (phpCAS::checkAuthentication()) {
			// Successful
			$casuser = phpCAS::getUser();
			$USERINFO = $auth->getUserData($casuser);
 
			$wlstring = $this->getConf('wluserstring');
			$wlstring = preg_replace('/(\s|\n|\r|\t)/', '', $wlstring);
			$wlusers = explode(",", $wlstring);
 
			if ((in_array($casuser, $wlusers)) || $this->getConf('allowall')) {
				if (empty($USERINFO)) {
					//No account yet
					if ($this->getConf('makenew')) {
 
						// Try making an account
						$tmpuserdomain = ($casuser."@".trim($this->getConf('useredomain')));
						if (false == $auth->triggerUserMod('create', array($casuser, date(DATE_ATOM) ,$casuser, $tmpuserdomain))) {
							$ACT = 'denied';
							msg ('CAS to Doku user creation error', -1);
						}
						//update userinfo with the new user
						$USERINFO = $auth->getUserData($cassuser);
					} else {
						$ACT = 'denied';
						msg ('Your user has not been created, and CAS to Doku creation is disabled', -1);
						return;
					}
				}
				// Populate the session variables
				$_SERVER['REMOTE_USER'] = $casuser;
				if ($this->getConf('stickysession')) {
					$stickysession = true;
				} else {
					$stickysession = false;
				}
				auth_setCookie($casuser,'CAS',$stickysession);
 
				// Authentication info has changed: reset the page info
				$INFO = pageinfo();
 
				$ACT = 'show';
			} else {
				$ACT = 'denied';
				msg ('Sorry; you are not on the allowed user list', -1);
			}
 
		} else {
			// Failed
			$ACT = 'denied';
			msg ('Sorry; your login to '.$this->getConf('name').' failed.',-1);
		}
	}
 
	function handle_caslogout () {
		// Check CAS authentication and whether to log out of CAS completely, and do a phpCAS::logout if so.
		if ((isset($_SERVER['REMOTE_USER'])) && ($_SESSION[DOKU_COOKIE]['auth']['pass'] == 'CAS')) {
			if ($this->getConf('caslogout')) {
				phpCAS::logoutWithRedirectServiceAndUrl($this->_self(), $this->_self());
			}
		}
		auth_logoff();
	}
 
	function handle_action (&$event, $param) {
		global $ACT;
		require_once ('CAS.php');
		phpCAS::client($this->getConf('version').'.0',$this->getConf('server'),(integer) $this->getConf('port'),$this->getConf('uri'));
		phpCAS::setNoCasServerValidation();
 
		// Handle the case where the CAS session is finished but the user is still logged in to DokuWiki
		if (! $this->getConf('stickysession')) {
			if ((isset($_SERVER['REMOTE_USER'])) && ($_SESSION[DOKU_COOKIE]['auth']['pass'] == 'CAS')) {
				if (! phpCAS::checkAuthentication()) {
					// Authentication failed
					$event->preventDefault();
					$this->handle_caslogout();
					$this->_redirect($this->_self());
				}
			}
		}
 
		if ($event->data == 'caslogin') {
			$event->preventDefault();
			$this->handle_caslogin();
		}
		if ($event->data == 'logout') {
			$this->handle_caslogout();
		}
	}
 
	function handle_template (&$event, $param) {
		if ($event->data == 'caslogin') {
			$event->preventDefault();
		}
	}
}

Here is the new defualt.php

plugins/ssocas/conf/default.php
<?php
$conf['name'] = 'CAS Login';
$conf['localname'] = 'Local Login';
$conf['server'] = '';
$conf['port'] = '443';
$conf['uri'] = '';
$conf['version'] = '2';
$conf['caslogout'] = 0;
$conf['stickysession'] = 0;
$conf['logourl'] = '';
$conf['jshidelocal'] = 1;
$conf['wluserstring'] = '';
$conf['useredomain'] = 'notconfigured.org';
$conf['allowall'] = '1';
$conf['makenew'] = '1';

Here is the new metadata.php

plugins/ssocas/conf/metadata.php
<?php
$meta['name'] = array('string');
$meta['localname'] = array('string');
$meta['server'] = array('string');
$meta['port'] = array('numeric','_pattern'=>'/[0-9]*/');
$meta['uri'] = array('string');
$meta['version'] = array('string');
$meta['caslogout'] = array('onoff');
$meta['stickysession'] = array('onoff');
$meta['logourl'] = array('string');
$meta['jshidelocal'] = array('onoff');
$meta['wluserstring'] = array('string');
$meta['useredomain'] = array('string');
$meta['allowall'] = array('onoff');
$meta['makenew'] = array('onoff');;
plugins/ssocas/lang/en/settings.php
<?php
$lang['name'] = 'CAS login service name';
$lang['localname'] = 'Local login service name';
$lang['server'] = 'CAS server DNS name';
$lang['port'] = 'CAS server port';
$lang['uri'] = 'CAS server path';
$lang['version'] = 'CAS version (1 or 2 supported)';
$lang['caslogout'] = 'Logout from CAS as well as DokuWiki?';
$lang['stickysession'] = 'Keep logins valid after the browser session ends?';
$lang['logourl'] = 'URL to a logo for the CAS service. If serving login pages via HTTPS, make sure this is either relative (/...) or an HTTPS URL.';
$lang['jshidelocal'] = 'Use JavaScript to hide the local login form until required?';
$lang['wluserstring'] = 'Comma seperated list of users';
$lang['useredomain'] = 'Domain to create new accounts with';
$lang['allowall'] = 'Check if all CAS users will be allowed login. (Disable whitelisting)';
$lang['makenew'] = 'Check if CAS users should automatically have accounts created. (if disabled, even whitelisted users will not be made)';

I agree with the comment in known issues that this could use better localised string support. However, I think it is reasonable for the person who wishes to port this to another language to implement that. And when tehy do, it would be great if it would be shared. The most common way comunity efforts move forward is that people create what they need, and then share it. Let's keep up the good work. — cletnickcletnick

2010/10/11 05:32

Set CAS login as default mecanism for frontend

Hi, i've just configured the plugin.
My default setup allow @all group no read access.
How it will be possible to configure the CAS authentification as default (no choice) only for the frontend ?
The CAS auth for backend is not need, i prefer keep the choice (or better only default auth for backend).

— M4t 2015/13/28 00:00

plugin/ssocas.txt · Last modified: 2016-08-15 10:43 by 130.112.1.3