Thanks Patrick for the answer.
I followed this approach and it worked for me but I am still evaluating whether this kind of implementation will leave any grey area on overall authentication.
I basically created a new auth source similar to 'multiauth', and configured multiple IDP based sub authentication sources and then I called each of them from loginCompleted except the first one which I called from authentication of my new module.
<?php
$config = array(
'admin' => array(
'core:AdminPassword',
),
'multifactor-idp-auth' => array(
'multifactoridpauth:MultiFactorIdP',
'sources' => array('cert', 'userpw'),
),
'thirdparty' => array(
'saml:SP',
'certificate' => '/var/simplesamlphp/cert/anshuman-proxy.example.net.crt',
'privatekey' => '/var/simplesamlphp/cert/anshuman-proxy.example.net.pem',
'discoURL' => null,
'redirect.sign' => false,
'sign.authnrequest' => true,
'NameIDPolicy' => null,
'AuthnContextClassRef' => 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified',
),
'cert' => array(
'saml:SP',
'certificate' => '/var/simplesamlphp/cert/anshuman-proxy.example.net.crt',
'privatekey' => '/var/simplesamlphp/cert/anshuman-proxy.example.net.pem',
'discoURL' => null,
'redirect.sign' => false,
'sign.authnrequest' => false,
),
'userpw' => array(
'saml:SP',
'certificate' => '/var/simplesamlphp/cert/anshuman-proxy.example.net.crt',
'privatekey' => '/var/simplesamlphp/cert/anshuman-proxy.example.net.pem',
'discoURL' => null,
'redirect.sign' => false,
'sign.authnrequest' => false,
),
);
<?php
class sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP extends SimpleSAML_Auth_Source {
private $sources;
public function __construct($info, $config) {
assert('is_array($info)');
assert('is_array($config)');
parent::__construct($info, $config);
if (!array_key_exists('sources', $config)) {
throw new Exception('The required "sources" config option was not found');
}
$this->sources = $config['sources'];
}
public function authenticate(&$state) {
$state['sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP.sources'] = $this->sources;
$state['sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP.completed'] = array();
assert('is_array($state)');
foreach ($this->sources as $authId) {
$state['sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP.current'] = $authId;
$as = SimpleSAML_Auth_Source::getById($authId);
if ($as === NULL) {
throw new Exception('Invalid authentication source: ' . $authId);
}
$as->authenticate($state);
}
}
public function initLogin($return, $errorURL = null, array $params = array()) {
SimpleSAML_Logger::debug('Anshuman - MultiFactorIdP->initLogin is called for ');
assert('is_string($return) || is_array($return)');
assert('is_string($errorURL) || is_null($errorURL)');
$state = array_merge($params, array(
'SimpleSAML_Auth_Default.id' => $this->authId, // TODO: remove in 2.0
'SimpleSAML_Auth_Source.id' => $this->authId,
'SimpleSAML_Auth_Default.Return' => $return, // TODO: remove in 2.0
'SimpleSAML_Auth_Source.Return' => $return,
'SimpleSAML_Auth_Default.ErrorURL' => $errorURL, // TODO: remove in 2.0
'SimpleSAML_Auth_Source.ErrorURL' => $errorURL,
'LoginCompletedHandler' => array(get_class(), 'loginCompleted'),
'LogoutCallback' => array(get_class(), 'logoutCallback'),
'LogoutCallbackState' => array(
'SimpleSAML_Auth_Default.logoutSource' => $this->authId, // TODO: remove in 2.0
'SimpleSAML_Auth_Source.logoutSource' => $this->authId,
),
));
if (is_string($return)) {
$state['SimpleSAML_Auth_Default.ReturnURL'] = $return; // TODO: remove in 2.0
$state['SimpleSAML_Auth_Source.ReturnURL'] = $return;
}
if ($errorURL !== null) {
$state[SimpleSAML_Auth_State::EXCEPTION_HANDLER_URL] = $errorURL;
}
try {
$this->authenticate($state);
} catch (SimpleSAML_Error_Exception $e) {
SimpleSAML_Auth_State::throwException($state, $e);
} catch (Exception $e) {
$e = new SimpleSAML_Error_UnserializableException($e);
SimpleSAML_Auth_State::throwException($state, $e);
}
self::loginCompleted($state);
}
public static function loginCompleted($state) {
assert('is_array($state)');
assert('array_key_exists("SimpleSAML_Auth_Source.Return", $state)');
assert('array_key_exists("SimpleSAML_Auth_Source.id", $state)');
assert('array_key_exists("Attributes", $state)');
assert('!array_key_exists("LogoutState", $state) || is_array($state["LogoutState"])');
$return = $state['SimpleSAML_Auth_Source.Return'];
$session = SimpleSAML_Session::getSessionFromRequest();
$authId = $state['SimpleSAML_Auth_Source.id'];
$sources = $state['sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP.sources'];
$completed = $state['sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP.completed'];
$current = $state['sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP.current'];
SimpleSAML_Logger::debug('Last completed source' . $current);
$completed = array_merge($completed, array($current));
$state['sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP.completed'] = $completed;
SimpleSAML_Logger::debug('All available sources ' . var_export($sources, true));
SimpleSAML_Logger::debug('Completed sources ' . var_export($completed, true));
foreach(array_diff_key($sources, $completed) as $authId) {
$state['sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP.current'] = $authId;
$as = SimpleSAML_Auth_Source::getById($authId);
$as->authenticate($state);
assert('false');
}
$state['sspmod_multifactoridpauth_Auth_Source_MultiFactorIdP.completed'] = $completed;
$session->doLogin($authId, SimpleSAML_Auth_State::getPersistentAuthData($state));
SimpleSAML_Logger::debug('Final state object ' . var_export($state, true));
if (is_string($return)) { // redirect...
\SimpleSAML\Utils\HTTP::redirectTrustedURL($return);
} else {
call_user_func($return, $state);
}
assert('false');
}
}
?>
This is half completed yet though.