Single sign-on integration with SimpleSAMLphp

From TNG_Wiki
Jump to navigation Jump to search
Ambox notice.png The methodology described on this page is new and needs other users with advanced PHP skills to assist with testing and debugging. The methodology should be considered an 'ALPHA RELEASE'. To date, the methodology has only been used with TNG10 but is likely to work with older TNG versions for sites running >=PHP5.3.
TNG 10.0


TNG webmasters often have the need/desire to integrate TNG with other software. This frequently stems from wanting to integrate the User databases of both software products so that end-users only need to sign-on once. This ability is known as Single sign-on (SSO). With SSO, a user logs in once and gains access to multiple related, but independent, software systems without being prompted to log in again at each of them.

This page describes how the SimpleSAMLphp application can be used to achieve SSO for a website running both MediaWiki and TNG. The approach uses TNG as the identity provider (IdP) to authenticate Users for both Mediawiki and TNG. It uses the TNG User database as the authority. No modifications are required to the TNG software, nor TNG's database (tng_users table). A SimpleSAMLphp module written expressly for TNG achieves this.

Alternatively, if a lightweight extension was written to adapt TNG (similar to Extension:SimpleSamlAuth), SimpleSAMLphp could be used for TNG Users to authenticate via:

  • Mediawiki (i.e. vice-versa, with Mediawiki as the IdP instead of TNG as described on this page).
  • Joomla (SimpleSAMLphp module would need to be written).
  • Facebook*
  • LinkedIn*
  • Windows Live*
  • MySpace*
  • YubiKey*
  • SQL authentication*
  • OpenID*
  • LDAP*
  • Twitter*, or
  • SimpleSAMLphp itself - replacing/mimicking the TNG registration/log-in/log-out operations.

(* SimpleSAMLphp modules already exist.)


The SimpleSAMLphp Application

SimpleSAMLphp (https://simplesamlphp.org/) is a multi-lingual application that deals with authentication based on the SAML standard. It is free software licensed under the CC-GNU LGPL version 2.1. SimpleSAMLphp is written in PHP and can integrate web-based PHP applications into a federation.

Security Assertion Markup Language (SAML), pronounced sam-el, is an XML-based open standard data format for exchanging authentication and authorisation data between parties, in particular, between an identity provider (IdP) and a service provider (SP).

The single most important requirement that SAML addresses is web browser single sign-on (SSO). Single sign-on solutions are common at the intranet level (using cookies, for example) but extending these solutions beyond the intranet has been problematic. This has led to the proliferation of non-interoperable proprietary technologies.

SimpleSAMLphp can act as an IdP or SP itself, however the main focus of SimpleSAMLphp is to provide support for:

  • SAML 2.0 as a Service Provider (SP).
  • SAML 2.0 as a Identity Provider (IdP).

SimpleSAMLphp also supports some other identity protocols, such as Shibboleth 1.3, A-Select, CAS, OpenID, WS-Federation and OAuth.


SimpleSAMLphp as a Service Provider (SP)

SimpleSAMLphp supports local authentication with one of its authentication modules, and can be used to provide service provider functionality. As a service provider, SimpleSAMLphp will communicate and delegate authentication to an Identity Provider. SimpleSAMLphp may connect to both a Shibboleth or a SAML 2.0 Identity Provider.

SimpleSAMLphp has a built-in SAML 2.0 Discovery Service that can connect the SP to multiple IdPs, and can permit users to select between the IdPs.


SimpleSAMLphp as an Identity Provider (IdP)

With an existing directory of users, a database, an LDAP or a Radius interface, SimpleSAMLphp can be setup to provide the webmaster with their own federated Single Sign-On environment. As an Identity Provider, both Shibboleth and SAML 2.0 services can connect to SimpleSAMLphp.


Extending SimpleSAMLphp

SimpleSAMLphp contains an Extension API allowing third-party modules to extend parts of SimpleSAMLphp. It has multiple session handlers and can use the session handling built-in to PHP or can use memcache.


Example Websites

The following websites have been set up with the SimpleSAMLphp single sign-on federation described on this page:


Prerequisites and Assumptions

The following are prerequisites for setting up a single sign-on federation with TNG and MediaWiki using SimpleSAMLphp:

  • A working installation of TNG 10. (Older versions of TNG are likely to work but have not been tested.)
  • A working installation of MediaWiki 1.23. (Older versions, since MediaWiki 1.15, are likely to work but have not been tested.)
  • PHP version >= 5.3.0.

The following assumptions represent the setup/syntax/methodology/folder directories used to implement a working federation. Other setups departing from these assumptions may also work.

  • TNG and Mediawiki data tables installed in the same database.
  • TNG installed in the /home/example/public_html/tng folder resulting with an external URL of http://www.example.com/tng/.
  • MediWiki installed in the /home/example/public_html/mw folder resulting with an external URL of http://www.example.com/mw/. (This is before any Apache short URLs are applied, which are still possible and unaffected.)
  • A hosted environment is being used where you do not have full permissions to the server, and cannot edit Apache configuration for some reason, polictics, policy or whatever.


STEP 1 - Install SimpleSAMLphp

  1. Download the latest stable version of SimpleSAMLphp from https://simplesamlphp.org/download.
  2. After downloading, extract the compressed archive file.
  3. Upload the extracted files and folders to your site's /home/example/simplesamlphp folder. Note that this puts the whole simplesamlphp folder outside the webroot (public_html) folder as per the alternative recommended configuration - see https://simplesamlphp.org/docs/stable/simplesamlphp-install#section_13.
  4. Move the whole www folder from inside the simplesamlphp folder (i.e. /home/example/simplesamlphp/www ) and place it in the public_html folder. That is, move it so that it is then found at /home/example/example/public_html/www.
  5. Rename the now moved www folder to become simplesaml (i.e. /home/example/public_html/simplesaml).


STEP 2 - Configure SimpleSAMLphp

Step 2.1 Edit _include.php

At row 26 of /home/public_html/simplesaml/_include.php change the code from:

require_once(dirname(dirname(__FILE__)) . '/lib/_autoload.php');

to :

require_once('/home/example/simplesamlphp/lib/_autoload.php');


At row 100, change the code from:

$configdir = dirname(dirname(__FILE__)) . '/config';

to :

$configdir = '/home/example/simplesamlphp/config';


Step 2.2 Edit config.php

At row 24 of /home/simplesamlphp/config.php insert your base URL path to the simplesaml folder:

'baseurlpath' => 'http://example.com/simplesaml/',


At row 78, set an administrator password for accessing and testing your simplesamlphp setup:

'auth.adminpassword' => 'setnewpasswordhere',


At row 90, set a random string used by simpleSAMLphp to generate cryptographically secure hashes:

'secretsalt' => 'randombytesinsertedhere',


At row, 97 and 98 set your technical contact information available in the generated metadata. The e-mail address is important as it is also used for receiving error reports sent automatically by simpleSAMLphp:

'technicalcontact_name' => 'Administrator',
'technicalcontact_email' => 'webmaster@example.com',


At row 107, set your timezone:

'timezone' => 'Australia/Adelaide',


At row 312, you may need to change the enable.http_post parameter from false to true:

'enable.http_post' => true,


At row 360, set your default language:

'language.default' => 'en',


IMPORTANT, at row 608 change the store.type parameter from phpsession to sql:

'store.type' => 'sql',


From row 617, insert your host name (probably localhost, your database name, your database username, your database password and provide a SQL table prefix such as ssp:

'store.sql.dsn' => 'mysql:host=localhost;dbname=yourdatabasename',

/*
 * The username and password to use when connecting to the database.
 */
'store.sql.username' => 'yourdatabaseusername',
'store.sql.password' => 'yourdatabasepassword',
/*
 * The prefix we should use on our tables.
 */
'store.sql.prefix' => 'ssp',


At row 759, add your domain as trusted:

'trusted.url.domains' => array('example.com'),


Step 2.3 Preliminary test of simpleSAMLphp

At this point you can access the homepage of your simpleSAMLphp installation at the following URL (based on the example setup assumed above) http://example.com/simplesaml/. The simpleSAMLphp homepage contains some configuration information and some links to the test services. An additional authentication link will appear under the Authentication Tab after setting up the additional authentication source in the following step.


Step 2.4 Edit authorsources.php

At row 3 of /home/simplesamlphp/authorsources.php add the following tngpidauth array at the beginning of the $config array:

$config = array(
 'tngidpauth' => array(
  'tngextauth:External',
        'dsn'       => 'mysql:host=localhost;dbname=yourdatabasename',
        'username'  => 'yourdatabaseusername',
        'password'  => 'yourdatabasepassword',
        'tngpath'   => '/home/example/public_html/tng/',    // the TNG path from (and including) the 'home' directory; ends with a slash.
        'loginpage' => 'http://example.com/tng/login.php',  // full URL to TNG's login page.
        'logoutpage'=> 'http://example.com/tng/logout.php', // full URL to TNG's logout page.
 ),
    /* Other authentication sources follow. */


STEP 3 - Create your SimpleSAMLphp module

Step 3.1 Create your own module directory and sub-directories

Create the following directories:

  • /home/example/simplesamlphp/modules/tngextauth/
  • /home/example/simplesamlphp/modules/tngextauth/www/
  • /home/example/simplesamlphp/modules/tngextauth/lib/
  • /home/example/simplesamlphp/modules/tngextauth/lib/Auth/
  • /home/example/simplesamlphp/modules/tngextauth/lib/Auth/Source/


Step 3.2 create file default-enable

Create an empty file called default-enable, with no file extension, and place the file in the /home/example/simplesamlphp/modules/tngextauth/ folder.

An easy way to do this is to copy the default-enable file from the /home/example/simplesamlphp/modules/sqlauth/ folder.


Step 3.3 Copy and modify the file resume.php

  1. Copy the resume.php file from the /home/example/simplesamlphp/modules/exampleauth/www/ folder and place it in the /home/example/simplesamlphp/modules/tngextauth/www/ folder that you created.
  2. Modify the code in resume.php so that it reads as follows:


<?php

/**
 * This page serves as the point where the user's authentication
 * process is resumed after the login page.
 *
 * It simply passes control back to the class.
 *
 * @package simpleSAMLphp
 */
sspmod_tngextauth_Auth_Source_External::resume();


Step 3.4 Copy and modify the file External.php

  1. Copy the External.php file from the /home/example/simplesamlphp/modules/exampleauth/lib/Auth/Source/ folder and place it in the /home/example/simplesamlphp/modules/tngextauth/lib/Auth/Source/ folder that you created.
  2. Modify the code in External.php so that it reads as follows:


<?php

/**
 * External authentication source.
 *
 * This class is an authentication source which is designed to
 * hook into an external authentication system.
 *
 * @package simpleSAMLphp
 */
class sspmod_tngextauth_Auth_Source_External extends SimpleSAML_Auth_Source {
    /* The database DSN. */
    private $dsn;
    /* The database username & password. */
    private $username;
    private $password;
    /* The path to the TNG folder. */
    private $tngpath;
    /* The paths to the TNG Log-in and Log-out pages. */
    private $loginpage;
    private $logoutpage;
	
	/**
	 * Constructor for this authentication source.
	 *
	 * @param array $info  Information about this authentication source.
	 * @param array $config  Configuration.
	 */
	public function __construct($info, $config) {
		assert('is_array($info)');
		assert('is_array($config)');

		/* Call the parent constructor first, as required by the interface. */
		parent::__construct($info, $config);

		/* Do any other configuration we need here. */
        if (!is_string($config['dsn'])) {
            throw new Exception('Missing or invalid dsn option in config.');
        }
        $this->dsn = $config['dsn'];
        if (!is_string($config['username'])) {
            throw new Exception('Missing or invalid username option in config.');
        }
        $this->username = $config['username'];
        if (!is_string($config['password'])) {
            throw new Exception('Missing or invalid password option in config.');
        }
        $this->password = $config['password'];
        $this->tngpath = $config['tngpath'];
        $this->loginpage = $config['loginpage'];
        $this->logoutpage = $config['logoutpage'];
	}

	/**
	 * Retrieve attributes for the user.
	 *
	 * @return array|NULL  The user's attributes, or NULL if the user isn't authenticated.
	 */
	private function getUser() {

		/*
		 * We obtain the attributes stored in the users PHP session
		 */
		include($this->tngpath."begin.php");
		include($this->tngpath."genlib.php");
		include($this->tngpath."checklogin.php");
		if (!isset($_SESSION['currentuser']) || ($_SESSION['currentuser'] == "Administrator-No-Users-Yet"))
		{
		/* The user is not authenticated. */
		//	unset($attributes);  /* Did not end up needing this */
		}
		else
		{

		/*
		 * TNG does not store the email address in the users PHP session. 
		 * Therefore we connect to the database to obtain user's email address.
		 */
        $db = new PDO($this->dsn, $this->username, $this->password);
        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        /* Ensure that we are operating with UTF-8 encoding.
         * This command is for MySQL. Other databases may need different commands.
         */
        $db->exec("SET NAMES 'utf8'");

        /* With PDO we use prepared statements. This saves us from having to escape
         * the username in the database query.
         */
        $st = $db->prepare("SELECT username, email FROM tng_users WHERE username='$username'");
        if (!$st->execute(array('username' => $username))) {
            throw new Exception('Failed to query database for user.');
        }

        /* Retrieve the row from the database. */
        $row = $st->fetch(PDO::FETCH_ASSOC);
        if (!$row) {
            /* User not found. */
            SimpleSAML_Logger::warning('TNGAuth: Could not find user ' . var_export($username, TRUE) . '.');
            throw new SimpleSAML_Error_Error('WRONGUSERPASS');
        }

		/*
		 * Find the attributes for the user.
		 * Note that all attributes in simpleSAMLphp are multivalued, so we need
		 * to store them as arrays.
		 * Only the 'mail', 'uid' and 'displayname' attributes are actually used
		 * for authenticating with MediaWiki but the remaining SESSION parameters
		 * are collected for potential future integrations.
		 */

        $attributes = array(
            'mail' => array($row['email']),
            'uid' => array($_SESSION['currentuser']),
            'displayname' => array($_SESSION['currentuserdesc']),
            'mygedcom' => array($_SESSION['mygedcom']),
            'mypersonID' => array($_SESSION['mypersonID']),
            'assignedtree' => array($_SESSION['assignedtree']),
            'ismemberof' => array($_SESSION['tngrole']),
            'logged_in' => array($_SESSION['logged_in']),
            'session_rp' => array($_SESSION['session_rp']),
            'allow_admin' => array($_SESSION['allow_admin']),
            'allow_edit' => array($_SESSION['allow_edit']),
            'allow_add' => array($_SESSION['allow_add']),
            'tentative_edit' => array($_SESSION['tentative_edit']),
            'allow_delete' => array($_SESSION['allow_delete']),
            'allow_media_edit' => array($_SESSION['allow_media_edit']),
            'allow_media_add' => array($_SESSION['allow_media_add']),
            'allow_media_delete' => array($_SESSION['allow_media_delete']),
            'allow_living' => array($_SESSION['allow_living']),
            'allow_private' => array($_SESSION['allow_private']),
            'allow_ged' => array($_SESSION['allow_ged']),
            'allow_pdf' => array($_SESSION['allow_pdf']),
            'allow_lds' => array($_SESSION['allow_lds']),
            'allow_profile' => array($_SESSION['allow_profile']),
            'assignedbranch' => array($_SESSION['assignedbranch']),
            'session_language' => array($_SESSION['session_language']),
            'session_charset' => array($_SESSION['session_charset']),
            'destinationpage8' => array($_SESSION['destinationpage8']),
        );
		}
		return $attributes;
	}

	/**
	 * Log in using an external authentication helper.
	 *
	 * @param array &$state  Information about the current authentication.
	 */
	public function authenticate(&$state) {
		assert('is_array($state)');

		$attributes = $this->getUser();
		if ($attributes !== NULL) {
			/*
			 * The user is already authenticated.
			 *
			 * Add the users attributes to the $state-array, and return control
			 * to the authentication process.
			 */
			$state['Attributes'] = $attributes;
			return;
		}

		/*
		 * The user isn't authenticated. We therefore need to
		 * send the user to the login page.
		 */

		/*
		 * First we add the identifier of this authentication source
		 * to the state array, so that we know where to resume.
		 */
		$state['tngextauth:AuthID'] = $this->authId;

		/*
		 * We need to save the $state-array, so that we can resume the
		 * login process after authentication.
		 *
		 * Note the second parameter to the saveState-function. This is a
		 * unique identifier for where the state was saved, and must be used
		 * again when we retrieve the state.
		 *
		 * The reason for it is to prevent
		 * attacks where the user takes a $state-array saved in one location
		 * and restores it in another location, and thus bypasses steps in
		 * the authentication process.
		 */
		$stateId = SimpleSAML_Auth_State::saveState($state, 'tngextauth:External');

		/*
		 * Now we generate a URL the user should return to after authentication.
		 * We assume that whatever authentication page we send the user to has an
		 * option to return the user to a specific page afterwards.
		 */
		$returnTo = SimpleSAML_Module::getModuleURL('tngextauth/resume.php', array(
			'State' => $stateId,
		));

		/*
		 * The redirect to the TNG login page.
		 */
		SimpleSAML_Utilities::redirectTrustedURL($this->loginpage, array(
			'ReturnTo' => $returnTo,
		));

		/*
		 * The redirect function never returns, so we never get this far.
		 */
		assert('FALSE');
	}

	/**
	 * Resume authentication process.
	 *
	 * This function resumes the authentication process after the user has
	 * entered his or her credentials.
	 *
	 * @param array &$state  The authentication state.
	 */
	public static function resume() {

		/*
		 * First we need to restore the $state-array. We should have the identifier for
		 * it in the 'State' request parameter.
		 */
		if (!isset($_REQUEST['State'])) {
			throw new SimpleSAML_Error_BadRequest('Missing "State" parameter.');
		}
		$stateId = (string)$_REQUEST['State'];

		// sanitize the input
		$sid = SimpleSAML_Utilities::parseStateID($stateId);
		if (!is_null($sid['url'])) {
			SimpleSAML_Utilities::checkURLAllowed($sid['url']);
		}

		/*
		 * Once again, note the second parameter to the loadState function. This must
		 * match the string we used in the saveState-call above.
		 */
		$state = SimpleSAML_Auth_State::loadState($stateId, 'tngextauth:External');

		/*
		 * Now we have the $state-array, and can use it to locate the authentication
		 * source.
		 */
		$source = SimpleSAML_Auth_Source::getById($state['tngextauth:AuthID']);
		if ($source === NULL) {
			/*
			 * The only way this should fail is if we remove or rename the authentication source
			 * while the user is at the login page.
			 */
			throw new SimpleSAML_Error_Exception('Could not find authentication source with id ' . $state[self::AUTHID]);
		}

		/*
		 * Make sure that we haven't switched the source type while the
		 * user was at the authentication page. This can only happen if we
		 * change config/authsources.php while an user is logging in.
		 */
		if (! ($source instanceof self)) {
			throw new SimpleSAML_Error_Exception('Authentication source type changed.');
		}

		/*
		 * OK, now we know that our current state is sane. Time to actually log the user in.
		 *
		 * First we check that the user is acutally logged in, and didn't simply skip the login page.
		 */
		$attributes = $source->getUser();
		if ($attributes === NULL) {
			/*
			 * The user isn't authenticated.
			 *
			 * Here we simply throw an exception, but we could also redirect the user back to the
			 * login page.
			 */
			throw new SimpleSAML_Error_Exception('User not authenticated after login page.');
		}

		/*
		 * So, we have a valid user. Time to resume the authentication process where we
		 * paused it in the authenticate()-function above.
		 */

		$state['Attributes'] = $attributes;
		SimpleSAML_Auth_Source::completeAuth($state);

		/*
		 * The completeAuth-function never returns, so we never get this far.
		 */
		assert('FALSE');
	}

	/**
	 * This function is called when the user start a logout operation, for example
	 * by logging out of a SP that supports single logout.
	 *
	 * @param array &$state  The logout state array.
	 */
	public function logout(&$state) {
		assert('is_array($state)');

		/*
		 * The redirect to the logout page.
		 */
		SimpleSAML_Utilities::redirectTrustedURL($this->logoutpage
		);

		/*
		 * The logout function never returns, so we never get this far.
		 */
		assert('FALSE');
	}
}


STEP 4 - Install Mediawiki Extension:SimpleSamlAuth

  1. Download the latest stable version of Mediawiki Extension:SimpleSamlAuth from https://www.mediawiki.org/wiki/Extension:SimpleSamlAuth.
  2. After downloading, extract the compressed archive file.
  3. Upload the extracted files and folders to your site's /home/example/public_html/mw/extensions folder.
  4. Add the following configuration to your LocalSettings.php with values appropriate to your installation:


require_once "$IP/extensions/SimpleSamlAuth/SimpleSamlAuth.php";
// SAML_OPTIONAL // SAML_LOGIN_ONLY // SAML_REQUIRED //
$wgSamlRequirement = SAML_LOGIN_ONLY;
// Should users be created if they don't exist in the database yet?
$wgSamlCreateUser = true;

// SAML attributes
$wgSamlUsernameAttr = 'uid';
$wgSamlRealnameAttr = 'cn';
$wgSamlMailAttr = 'mail';

// SimpleSamlPhp settings
$wgSamlSspRoot = '/home/example/simplesamlphp';
$wgSamlAuthSource = 'tngidpauth';
$wgSamlPostLogoutRedirect = NULL;

// Array: [MediaWiki group][SAML attribute name][SAML expected value]
// If the SAML assertion matches, the user is added to the MediaWiki group
$wgSamlGroupMap = array(
    'user' => array(
        'groups' => array('registered'),
    ),
);


STEP 5 - Test

By this stage, you should now have an operational SimpleSAMLphp single sign-on federation of MediaWiki and TNG. TNG will operate as per normal. However, if you log-on from a TNG page and then visit one of your wiki pages, you should find yourself also logged in to MediaWiki. If you click on the Log out link on a wiki-page, you will be logged out of both MediaWiki and TNG. If you use the Log in link on a wiki page, you will be redirected to your TNG log-in page where you can login before being redirected back to your wiki page.


References


See also

The following articles are not directly related to the methodology described on this page, but may be useful references:

Preventing Access to MediaWiki pages

You may optionally want to view the MediaWiki Preventing Access page on how to prevent users from ...


Disclaimer

The original author of this page Clippership possesses a limited knowledge of PHP; only 'enough to get his face slapped'. The methodology and coding presented on this page may need further refinements by more advanced PHP users.