Using Interceptors to Override KnowledgeTree's Login Process

From KnowledgeTree Document Management Made Simple

Jump to: navigation, search

Contents

Interceptor Overview

KnowledgeTree has a feature called Interceptors which allow you to intercept the login process and re-direct it to another location, such as a third party portal. This allows you to bypass the KnowledgeTree login screen completely and use only your Portal for authentication. It also happens that this can be implemented in a plugin.

While there are many ways this can be done, for the purposes of this example it will be assumed that the Portal assigns each user a unique key in a cookie upon logging in, and this key can be used to validate the user's login.


Sample Interceptor

<?php
// PortalPlugin.php
require_once(KT_LIB_DIR . '/plugins/plugin.inc.php');
require_once(KT_LIB_DIR . '/plugins/pluginregistry.inc.php');
require_once(KT_LIB_DIR . '/authentication/interceptor.inc.php');
require_once(KT_LIB_DIR . '/authentication/interceptorregistry.inc.php');
require_once(KT_LIB_DIR . '/authentication/interceptorinstances.inc.php');


class PortalInterceptor extends KTInterceptor {
	var $sNamespace  = "test.Portal.interceptor";

	function authenticated() {
		// If keyless, don't log in
		if (!isset($_COOKIE["PortalKey"]))
			return;

		// Verify Portal key.
		/*
		 Send the key to your Portal for validation here.  For this example, the result is stored as $validKey.
		 Your portal should also send you information about the user such as their id, name, and email.
		 For this example that information is stored in $portalID, $name, and $email.
		 This info will be needed to create the local account.
		*/
		
		// If verification failed, abort login and delete the invalid key.
		if (!$validKey) {
			setcookie("PortalKey", "", time() - 86400, "/", ".example.com");
			unset($_COOKIE["PortalKey"]);
			return;
		}

		// Otherwise the key is valid, so get the local user or create it if not found.
		$oUser =& User::getByUsername($portalID);
	 	if (PEAR::isError($oUser) || ($oUser === false)) {
			$oUser =& User::createFromArray(array(
				"Username" => $portalID,
				"Name" => $name,
				"Email" => $mail,
				"EmailNotification" => true,
				"SmsNotification" => false,
				"MaxSessions" => 3,
				"password" => "",
			));
			// This is a good place to do any additional tasks,
			// such as creating a home folder or adding the user to roles/groups.
		}

		return $oUser;
	}

	function takeover() {
		// If keyless, redirect to Portal login
		if (!isset($_COOKIE["PortalKey"])) {
			header("Location: http://portal.example.com/Login?returnURL=http://ktdms.example.com/login.php");
			exit();
		}
	}
}

class PortalPlugin extends KTPlugin {
	var $sNamespace = "test.Portal.plugin";
	var $autoRegister = true;
	var $bAlwaysInclude = true;

	function PortalPlugin($sFilename = null) {
		$res = parent::KTPlugin($sFilename);
		$this->sFriendlyName = _kt("Portal Plugin");
		return $res;
	}

	function setup() {
		$this->registerInterceptor("PortalInterceptor", "test.Portal.interceptor", __FILE__);

               $aOptions = array(
                   'sName' => 'Test Portal Interceptor',
                   'sInterceptorNamespace' => 'test.Portal.interceptor',
                   'sConfig' => ''
		);
		KTInterceptorInstance::createFromArray($aOptions);
	}
}
?>


Sample Walkthrough

If that sample was a little overwhelming, here's a small explanation of it piece by piece.

<?php
// PortalPlugin.php
require_once(KT_LIB_DIR . '/plugins/plugin.inc.php');
require_once(KT_LIB_DIR . '/plugins/pluginregistry.inc.php');
require_once(KT_LIB_DIR . '/authentication/interceptor.inc.php');
require_once(KT_LIB_DIR . '/authentication/interceptorregistry.inc.php');
require_once(KT_LIB_DIR . '/authentication/interceptorinstances.inc.php');

To start off, these are the basic includes which must be defined for this plugin. Not much to see here. As always, the filename of your plugin must match the name of your plugin class.


class PortalInterceptor extends KTInterceptor {
	var $sNamespace  = "test.Portal.interceptor";

	function authenticated() {
		// If keyless, don't log in
		if (!isset($_COOKIE["PortalKey"]))
			return;

		// Verify Portal key.
		/*
		 Send the key to your Portal for validation here.  For this example, the result is stored as $validKey.
		 Your portal should also send you information about the user such as their id, name, and email.
		 For this example that information is stored in $portalID, $name, and $email.
		 This info will be needed to create the local account.
		*/

Next up is the Interceptor class declaration and the first interceptor function, authenticated(). authenticated() is called at the beginning of the login process and should return a User object if the user is logged in. This is your first chance to decide whether the user should be logged in or not.

The first step is to decide whether to even bother checking. Since the user must be logged in to the portal they must also have a valid key, so if the key is missing there's no point in continuing.

If the user does have a key then you need to check if it's valid. Your portal must have a way to validate your key and return some basic account information in order for this to work. How to do that depends entirely on your portal and is beyond the scope of this example.


		// If verification failed, abort login and delete the invalid key.
		if (!$validKey) {
			setcookie("PortalKey", "", time() - 86400, "/", ".example.com");
			unset($_COOKIE["PortalKey"]);
			return;
		}

		// Otherwise the key is valid, so get the local user or create it if not found.
		$oUser =& User::getByUsername($portalID);
	 	if (PEAR::isError($oUser) || ($oUser === false)) {
			$oUser =& User::createFromArray(array(
				"Username" => $portalID,
				"Name" => $name,
				"Email" => $mail,
				"EmailNotification" => true,
				"SmsNotification" => false,
				"MaxSessions" => 3,
				"password" => "",
			));
			// This is a good place to do any additional tasks,
			// such as creating a home folder or adding the user to roles/groups.
		}

		return $oUser;
	}

After validating your key it's time to decide what to do with it. If the key's invalid you don't want to log the user in, so abort the login and destroy the key so you won't have to process it again. If the key checks out, it's time to log the user in. Your portal should have returned a user id of some kind (preferably their login id), so check if that KnowledgeTree user exists. If they do, return the user and you're done. If the user doesn't exist you need to create them using additional information returned from your Portal (if available).

At this point you will either have aborted the function or returned a valid User which KnowledgeTree will then log in.


	function takeover() {
		// If keyless, redirect to Portal login
		if (!isset($_COOKIE["PortalKey"])) {
			header("Location: http://portal.example.com/Login?returnURL=http://ktdms.example.com/login.php");
			exit();
		}
	}

Next up is the takeover function. This is your chance to hijack the login process and spirit the user away to your login screen. As you can see the execution is pretty simple. If the user doesn't have a key, then they must not be logged in. So, send them to your login page to get one! Your Portal should also have a way to return the user to KnowledgeTree after logging in. For this example the user will be returned to the url in the returnURL variable.


class PortalPlugin extends KTPlugin {
	var $sNamespace = "test.Portal.plugin";
	var $autoRegister = true;
	var $bAlwaysInclude = true;

	function PortalPlugin($sFilename = null) {
		$res = parent::KTPlugin($sFilename);
		$this->sFriendlyName = _kt("Portal Plugin");
		return $res;
	}

	function setup() {
		$this->registerInterceptor("PortalInterceptor", "test.Portal.interceptor", __FILE__);

               $aOptions = array(
                   'sName' => 'Test Portal Interceptor',
                   'sInterceptorNamespace' => 'test.Portal.interceptor',
                   'sConfig' => ''
		);
		KTInterceptorInstance::createFromArray($aOptions);
	}
}

The final step is really the first step, declaring your plugin. This should be done the same as any other plugin, the only difference being that you need to register your interceptor in setup(). In order to turn your interceptor on you need to add it to the interceptor_instances table, this is done through the KTInterceptorInstance in the second half of setup().

Required Reading

There's one other piece as well. While all logins will be intercepted, logouts will not. This means that there's no way to destroy your Portal key when the user logs out, so if the user tries to login again the interceptor will pickup their key and log them in again no questions asked. In order to stop this you need to delete your key during logout, and this can be done by adding some code to /presentation/logout.php like so:

class KTLogoutDispatcher extends KTDispatcher {
    function do_main() {
        global $default;

        /*$oAuthenticator =& KTAuthenticationUtil::getAuthenticatorForUser($this->oUser);
        $oAuthenticator->logout($this->oUser);*/
        Session::destroy();

        // Destroy your key!
        setcookie("PortalKey", "", time() - 86400, "/", ".example.com");

        redirect((strlen($default->rootUrl) > 0 ? $default->rootUrl : "/"));
        exit(0);
    }
}

Once the database has been updated and your logout.php hacked, you're good to go. Just turn on your plugin and you're ready to start intercepting!


Return to Tutorials

Personal tools