:: Version 2.7.0 ::

Authentication process

Prerequisite: You must be familiar with the Syntax used in Tutorials and have already created an extension.

learning:
Allow connection to iTop using a different authentication method
level:
Advanced
domains:
PHP, Access rights
min version:
2.7.0

Starting with version 2.7.0, it is now much easier to add new authentication methods in order to connect to iTop.

In this tutorial we will see:

  • How the login process is done in 2.7
  • What are the new interfaces (APIs) to extend the login process
  • How to extend the authentication process

Authentication Behavior

The authentication process of iTop 2.7 has been rewritten using a FSM (Finite State Machine) which is an automaton following different states.

General Algorithm

  • The automaton begins at the state start
  • For each state all the relevant plugins are called
  • When all the plugins have been called for one state, and have all accepted to go on, the automaton advance to the next state.

Detailed automaton algorithm

The simplified pseudo-code for the login automaton is:

LoginWebPage::Login()
$sLoginState = $_SESSION['login_state']; // defaulted to 'start'
while (true)
{
    // Select all the plugins corresponding to 'login_mode' and 'allowed_login_types' configuration
    $aLoginPlugins = self::GetLoginPluginList();
    foreach ($aLoginPlugins as $oLoginFSMExtensionInstance)
    {
        $iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode);
        if ($iResponse == self::LOGIN_FSM_RETURN_OK)
        {
            return self::EXIT_CODE_OK; // login OK, exit FSM
        }
        if ($iResponse == self::LOGIN_FSM_RETURN_ERROR)
        {
            $sLoginState = 'set_error'; // prepare to advance to 'error' state
            break;
        }
    }
    // Every plugin has nothing else to do in this state, go forward
    $sLoginState = self::AdvanceLoginFSMState($sLoginState);
    $_SESSION['login_state'] = $sLoginState;
}

Variables

The login process uses the session to store the state and other information. The main variables used by the login process are:

  • state stored in $_SESSION['login_state']
  • login mode stored in $_SESSION['login_mode']
  • can logoff stored in $_SESSION['can_logoff']
The variable login_state have to be managed only by iTop framework,
don't try to set it by yourself in your extension, it can lead to security issues
and weird login process behavior.

Login Plugin Selection

At every state of the login process, the list of login plugins is refreshed by calling LoginWebPage::GetLoginPluginList().

  • First the plugin list is filtered using the login_mode session variable, so only the plugin corresponding to the current login mode is active (or all the plugins if login_mode is undefined).
  • Plugins having the mode before and after are always selected, these are considered as “system” plugins having to be first or last in certain states.
  • The resulting list is ordered using before then allowed_login_types configuration settings then after.

Login states

The login automaton has the following states:

start

  • Entry state.
  • This is the default state, either this is the first call for the user or it follows an error or a logout.
  • In this state some initialization can be done, but depending on the session, the plugins are not guaranteed to be called in this state, so it's better for a plugin to ignore this state.

login mode detection

  • Detect which login plugin to use.
  • The idea is to let some rare and specialized plugins shunt the normal order of detection if they identify the request as non ambiguously releving from their own auth mecanism. (ie: http basic auth)
  • If it is possible to decide from a non ambiguous manner that the mode is the one managed by the plugin, then the login_mode session variable should be set.
  • If it is not possible to detect the current mode, do nothing here.

read credentials

  • Read the credentials.
  • The idea is to call each plugin in the configured order so that the first declared trigger its own login process.
  • the login_mode session variable should be set at this point.
  • The goal of this state is to read the credentials either by displaying a form or redirecting an external authentication system (in case of SSO).
  • If redirected to an external authentication system, then the plugin will exit the PHP allowing the user to enter its credentials.
  • It is the responsibility of the authentication plugin to call back iTop in order to continue the login process (generally returning to /pages/UI.php).
  • The concerned plugin will be called again with the same state (read credentials) in order to read the credentials.
  • The auth_user session variable is generally set at this point.

check credentials

  • Check if the credentials are valid.
  • When using an external authentication system, just controls that The auth_user session variable is set. this means that the external system gave back the info to iTop.

credentials ok

  • Check that the credentials correspond to a valid iTop User.
  • Call LoginWebPage::CheckUser() to check the validity of the iTop User corresponding to the credentials given.
  • If the plugin supports this feature, User provisioning can be done at this point
  • Call LoginWebPage::OnLoginSuccess() if the User is valid, in order to initialize the session

user ok

  • Additional User check
  • At this point a valid iTop User has been selected.
  • Additional checks like 2 factor authentication can be done at this point.

connected

  • User connected
  • Call LoginWebPage::CheckLoggedUser() to verify that the user is always valid
  • Session variable can_logoff must be set to true if the “log off” menu can be enabled

error

  • An error occurred, next state will be start
  • Some cleanup can be done at this point
  • An error form can also be displayed to the user by calling LoginWebPage::DisplayLogoutPage()

Authentication interfaces

An API is provided to ease the creation of new authentication extensions.

interface iLoginFSMExtension

Low level interface for the plugin process. This interface contains 2 methods:

  • ListSupportedLoginModes()
    • Called to determine the list of supported login modes by this plugin
    • Return:
      • an array of supported login modes
  • LoginAction($sLoginState, &$iErrorCode)
    • Called for every state when the plugin is concerned
    • Parameters:
      • $sLoginState The current state of the login process among:
        • LoginWebPage::LOGIN_STATE_START
        • LoginWebPage::LOGIN_STATE_MODE_DETECTION
        • LoginWebPage::LOGIN_STATE_READ_CREDENTIALS
        • LoginWebPage::LOGIN_STATE_CHECK_CREDENTIALS
        • LoginWebPage::LOGIN_STATE_CREDENTIALS_OK
        • LoginWebPage::LOGIN_STATE_USER_OK
        • LoginWebPage::LOGIN_STATE_CONNECTED
        • LoginWebPage::LOGIN_STATE_ERROR
      • $iErrorCode The error code returned in case of error
        • LoginWebPage::EXIT_CODE_OK
        • LoginWebPage::EXIT_CODE_MISSINGLOGIN
        • LoginWebPage::EXIT_CODE_MISSINGPASSWORD
        • LoginWebPage::EXIT_CODE_WRONGCREDENTIALS
        • LoginWebPage::EXIT_CODE_MUSTBEADMIN
        • LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED
        • LoginWebPage::EXIT_CODE_NOTAUTHORIZED
    • Return:
      • LoginWebPage::LOGIN_FSM_RETURN_ERROR An error occurred → $iErrorCode must be set
      • LoginWebPage::LOGIN_FSM_RETURN_OK The login process is OK and terminated
      • LoginWebPage::LOGIN_FSM_RETURN_IGNORE The login process will proceed to next plugin or state

abstract class AbstractLoginFSMExtension

Abstract class used as an helper, implementing iLoginFSMExtension and providing callback functions for every state.

  • ListSupportedLoginModes()
    • see iLoginFSMExtension
  • OnStart(&$iErrorCode)
  • OnModeDetection(&$iErrorCode)
  • OnReadCredentials(&$iErrorCode)
  • OnCheckCredentials(&$iErrorCode)
  • OnCredentialsOK(&$iErrorCode)
  • OnUsersOK(&$iErrorCode)
  • OnConnected(&$iErrorCode)
  • OnError(&$iErrorCode)
    • Callback functions corresponding to each login process state.
    • Same return than LoginAction().
  • LoginAction($sLoginState, &$iErrorCode)
    • Not necessary to override this function if callbacks are used
    • see iLoginFSMExtension

interface iLogoutExtension

Called when the user decides to logoff form the iTop screens.

  • ListSupportedLoginModes()
    • Called to determine the list of supported login modes by this plugin
    • Return:
      • an array of supported login modes
  • LogoutAction()
    • Called when the user decides to logout.

Extending the authentication process

In order to propose a new authentication method you'll have to:

  • Provide a PHP class extending AbstractLoginFSMExtension (prefered) or implementing iLoginFSMExtension
  • Optionally provide a PHP class implementing iLogoutExtension interface.
  • configure in allowed_login_types the new mode corresponding to your extension

The login process will use the provided PHP class in the login process.

Login/logoff/password management screens can also be customized.

You can provide blocks to be inserted in the login screen, CSS to override or complete the current one. You can even replace completely the screens.

Login and User Provisioning API

The login and provisioning APIs are provided by LoginWebPage class.

Login API

/**
 * Login API: Check that credentials correspond to a valid user
 * Used only during login process when the password is known
 *
 * @api
 *
 * @param string $sAuthUser
 * @param string $sAuthPassword
 * @param string $sAuthentication ('internal' or 'external')
 *
 * @return bool (true if User OK)
 *
 */
public static function LoginWebPage::CheckUser($sAuthUser, $sAuthPassword='', $sAuthentication='external')
/**
 * Login API: Store User info in the session when connection is OK
 *
 * @api
 *
 * @param $sAuthUser
 * @param $sAuthentication
 * @param $sLoginMode
 *
 */
public static function LoginWebPage::OnLoginSuccess($sAuthUser, $sAuthentication, $sLoginMode)
/**
 * Login API: Check that an already logger User is still valid
 *
 * @api
 *
 * @param int $iErrorCode
 *
 * @return int LOGIN_FSM_RETURN_OK or LOGIN_FSM_RETURN_ERROR
 */
public static function LoginWebPage::CheckLoggedUser(&$iErrorCode)

User Provisioning

User provisioning is done in AbstractLoginFSMExtension::OnCheckCredentials() method. You can create a User and a Person if they are missing.

Example of pseudo-code for User provisioning:

private function DoUserProvisioning($sLogin, $sEmail, $sFirstName, $sLastName, $sOrganization, $aProfiles)
{
    if (!Config::Get('synchronize_user')) {
        return; // Not configured
    }
    try {
        if (LoginWebPage::FindUser($sLogin, false)) {
            return; // Already a user, do nothing
        }
        $oPerson = LoginWebPage::FindPerson($sEmail);
        if ($oPerson == null) {
            if (!Config::Get('synchronize_contact')) {
                return; // Not configured
            }
            // Create the person
            $oPerson = LoginWebPage::ProvisionPerson($sFirstName, $sLastName, $sEmail, $sOrganization);
        }
        LoginWebPage::ProvisionUser($sLogin, $oPerson, $aProfiles);
    } catch (Exception $e) {
        IssueLog::Error($e->getMessage());
    }
}

User Provisioning API

API to use for User provisioning.

/**
 * Provisioning API: Find a User
 *
 * @api
 *
 * @param string $sAuthUser
 * @param bool $bMustBeValid
 * @param string $sType
 *
 * @return \User|null
 */
public static function LoginWebPage::FindUser($sAuthUser, $bMustBeValid = true, $sType = 'External')
/**
 * Provisioning API: Find a Person by email
 *
 * @api
 *
 * @param string $sEmail
 *
 * @return \Person|null
 */
public static function LoginWebPage::FindPerson($sEmail)
/**
 * Provisioning API: Create a person
 *
 * @api
 *
 * @param string $sFirstName
 * @param string $sLastName
 * @param string $sEmail
 * @param string $sOrganization
 * @param array $aAdditionalParams
 *
 * @return \Person
 */
public static function LoginWebPage::ProvisionPerson($sFirstName, $sLastName, $sEmail, $sOrganization, $aAdditionalParams = array())
/**
 * Provisioning API: Create a User
 *
 * @api
 *
 * @param string $sAuthUser
 * @param Person $oPerson
 * @param array $aRequestedProfiles profiles to add to the new user
 *
 * @return \UserExternal|null
 */
public static function LoginWebPage::ProvisionUser($sAuthUser, $oPerson, $aRequestedProfiles)

Login facility

Additional debug messages are available when setting the configuration variable login_debug to true.

Example of debug traces:

Login debug logs
2019-08-21 09:53:52 | Info | ---------------------------------
2019-08-21 09:53:52 | Info | --> Entering Login FSM with state: [start]
2019-08-21 09:53:52 | Info | SESSION: 6nnga05fgqarfhe658rfjr50jq Array
(
    [itop_env] => production
    [login_state] => start
)
 
2019-08-21 09:53:52 | Info | Login: state: [start] call: LoginDefaultBefore
2019-08-21 09:53:52 | Info | Login: state: [start] call: Combodo\iTop\Cas\CASLoginExtension
2019-08-21 09:53:52 | Info | Login: state: [start] call: LoginForm
2019-08-21 09:53:52 | Info | Login: state: [start] call: LoginExternal
2019-08-21 09:53:52 | Info | Login: state: [start] call: LoginBasic
2019-08-21 09:53:52 | Info | Login: state: [start] call: LoginDefaultAfter
2019-08-21 09:53:52 | Info | SESSION: 6nnga05fgqarfhe658rfjr50jq Array
(
    [itop_env] => production
    [login_state] => login mode detection
)
 
2019-08-21 09:53:52 | Info | Login: state: [login mode detection] call: LoginDefaultBefore
2019-08-21 09:53:52 | Info | Login: state: [login mode detection] call: Combodo\iTop\Cas\CASLoginExtension
2019-08-21 09:53:52 | Info | Login: state: [login mode detection] call: LoginForm
2019-08-21 09:53:52 | Info | Login: state: [login mode detection] call: LoginExternal
2019-08-21 09:53:52 | Info | Login: state: [login mode detection] call: LoginBasic
2019-08-21 09:53:52 | Info | Login: state: [login mode detection] call: LoginDefaultAfter
2019-08-21 09:53:52 | Info | SESSION: 6nnga05fgqarfhe658rfjr50jq Array
(
    [itop_env] => production
    [login_state] => read credentials
)
 
2019-08-21 09:53:52 | Info | Login: state: [read credentials] call: LoginDefaultBefore
2019-08-21 09:53:52 | Info | Login: state: [read credentials] call: Combodo\iTop\Cas\CASLoginExtension
2019-08-21 09:53:52 | Info | ---------------------------------
2019-08-21 09:53:52 | Info | --> Entering Login FSM with state: [read credentials]
2019-08-21 09:53:52 | Info | SESSION: 6nnga05fgqarfhe658rfjr50jq Array
(
    [itop_env] => production
    [login_state] => read credentials
    [login_mode] => cas
    [cas_count] => 1
)
 
2019-08-21 09:53:52 | Info | Login: state: [read credentials] call: LoginDefaultBefore
2019-08-21 09:53:52 | Info | Login: state: [read credentials] call: Combodo\iTop\Cas\CASLoginExtension
2019-08-21 09:53:53 | Info | ---------------------------------
2019-08-21 09:53:53 | Info | --> Entering Login FSM with state: [read credentials]
2019-08-21 09:53:53 | Info | SESSION: 6nnga05fgqarfhe658rfjr50jq Array
(
    [itop_env] => production
    [login_state] => read credentials
    [login_mode] => cas
    [cas_count] => 1
    [phpCAS] => Array
        (
            ...
        )
)
 
2019-08-21 09:53:53 | Info | Login: state: [read credentials] call: LoginDefaultBefore
2019-08-21 09:53:53 | Info | Login: state: [read credentials] call: Combodo\iTop\Cas\CASLoginExtension
2019-08-21 09:53:53 | Info | SESSION: 6nnga05fgqarfhe658rfjr50jq Array
(
    [itop_env] => production
    [login_state] => read credentials
    [login_mode] => cas
    [cas_count] => 1
    [phpCAS] => Array
        (
            ...
        )
    [auth_user] => agavalda
)
 
2019-08-21 09:53:53 | Info | Login: state: [read credentials] call: LoginDefaultAfter
2019-08-21 09:53:53 | Info | SESSION: 6nnga05fgqarfhe658rfjr50jq Array
(
    [itop_env] => production
    [login_state] => check credentials
    [login_mode] => cas
    [cas_count] => 1
    [phpCAS] => Array
        (
            ...
        )
    [auth_user] => agavalda
)
 
[...]
 
2019-08-21 09:53:53 | Info | SESSION: t6itov5mhsf6886clhenaj6q1f Array
(
    [itop_env] => production
    [login_state] => connected
    [login_mode] => cas
    [cas_count] => 1
    [phpCAS] => Array
        (
            ...
        )
    [auth_user] => agavalda
    [archive_mode] => 0
    [profile_list] => Array
        (
            [1] => Administrator
        )
)
 
2019-08-21 09:53:53 | Info | Login: state: [connected] call: LoginDefaultBefore
2019-08-21 09:53:53 | Info | Login: state: [connected] call: Combodo\iTop\Cas\CASLoginExtension

Screen customization

Authent-CAS

As an example, we will study the authent-cas module.

The authent-cas module (located in datamodels/2.x/authent-cas) is used to integrate Central Authentication Service (CAS) authentication to iTop. You must install it on your iTop server.

It mainly contains the class CASLoginExtension extending AbstractLoginFSMExtension and implementing iLogoutExtension interface.

ListSupportedLoginModes()

This extension provide “cas” login mode.

CASLoginExtension::ListSupportedLoginModes()
public function ListSupportedLoginModes()
{
    return array('cas');
}

OnStart()

This function only cleanup some session information if necessary, it is useful to remove unnecessary session variables when possible.

CASLoginExtension::OnStart()
protected function OnStart(&$iErrorCode)
{
    unset($_SESSION['phpCAS']); // generated by the CAS library
    return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}

OnModeDetection()

This function is not implemented in this extension.

OnReadCredentials()

This function is used to read the credentials from the CAS server. Note that the first time, the login_mode session variable may not be set. In this case the login mode is set for next state processing.

This function is called twice:

  • The first time the user is not yet authenticated, redirect to CAS for authentication and exit (phpCAS::forceAuthentication();).
  • The second time the user is provided in auth_user session variable
  • If this function is called a another time without being authenticated, an error is returned (see cas_count)
CASLoginExtension::OnReadCredentials()
protected function OnReadCredentials(&$iErrorCode)
{
    if (!isset($_SESSION['login_mode']) || ($_SESSION['login_mode'] == 'cas'))
    {
        $_SESSION['login_mode'] = 'cas';
        static::InitCASClient();
        if (phpCAS::isAuthenticated())
        {
            $_SESSION['auth_user'] = phpCAS::getUser();
        }
        else
        {
            if (!isset($_SESSION['cas_called']))
            {
                $_SESSION['cas_called'] = 1;
            }
            else
            {
                // CAS has already been called, warn the user
                unset($_SESSION['cas_called']);
                $iErrorCode = LoginWebPage::EXIT_CODE_MISSINGLOGIN;
                return LoginWebPage::LOGIN_FSM_RETURN_ERROR;
            }
            phpCAS::forceAuthentication(); // Redirect to CAS and exit
        }
    }
    return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}

OnCheckCredentials()

This function checks that auth_user session variable and if cas_user_synchro configuration variable is true, provision the User if possible.

CASLoginExtension::OnCheckCredentials()
protected function OnCheckCredentials(&$iErrorCode)
{
    if ($_SESSION['login_mode'] == 'cas')
    {
        if (!isset($_SESSION['auth_user']))
        {
            $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
            return LoginWebPage::LOGIN_FSM_RETURN_ERROR;
        }
        if (Config::Get('cas_user_synchro' ))
        {
            self::DoUserProvisioning($_SESSION['auth_user']);
        }
    }
    return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}

OnCredentialsOK()

This function checks that the User in iTop is valid

CASLoginExtension::OnCredentialsOK()
protected function OnCredentialsOK(&$iErrorCode)
{
        if ($_SESSION['login_mode'] == 'cas')
        {
                $sAuthUser = $_SESSION['auth_user'];
                if (!LoginWebPage::CheckUser($sAuthUser, '', 'external'))
                {
                        $iErrorCode = LoginWebPage::EXIT_CODE_NOTAUTHORIZED;
                        return LoginWebPage::LOGIN_FSM_RETURN_ERROR;
                }
                LoginWebPage::OnLoginSuccess($sAuthUser, 'external', $_SESSION['login_mode']);
        }
        return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}

OnConnected()

This function is called when the user is connected (for every itop page web or ajax). It checks that the User is still valid.

CASLoginExtension::OnConnected()
protected function OnConnected(&$iErrorCode)
{
    if ($_SESSION['login_mode'] == 'cas')
    {
        $_SESSION['can_logoff'] = true;
        return LoginWebPage::CheckLoggedUser($iErrorCode);
    }
    return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}

OnError()

This function is called on error. It generally display a logout/error page with a link to reconnect.

CASLoginExtension::OnError()
protected function OnError(&$iErrorCode)
{
    if ($_SESSION['login_mode'] == 'cas')
    {
        unset($_SESSION['phpCAS']); // Generated by CAS library
        if ($iErrorCode != LoginWebPage::EXIT_CODE_MISSINGLOGIN)
        {
            $oLoginWebPage = new LoginWebPage();
            $oLoginWebPage->DisplayLogoutPage(false, Dict::S('CAS:Error:UserNotAllowed'));
        }
        exit();
    }
    return LoginWebPage::LOGIN_FSM_RETURN_CONTINUE;
}

LogoutAction()

This function is part of the iLogoutExtension interface. For this extension the user will also be disconnected from the CAS server.

CASLoginExtension::LogoutAction()
public function LogoutAction()
{
    $sCASLogoutUrl = Config::Get('cas_logout_redirect_service');
    if (empty($sCASLogoutUrl))
    {
        $sCASLogoutUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php';
    }
    static::InitCASClient();
    phpCAS::logoutWithRedirectService($sCASLogoutUrl); // Redirects to the CAS logout page
}
2_7_0/customization/authentication.txt · Last modified: 2020/04/15 15:23 (external edit)
Back to top
Contact us