<?php
namespace cymapgt\core\application\authentication\UserCredential\services;
/**
* Generated by PHPUnit_SkeletonGenerator on 2015-07-25 at 00:31:37.
*/
class UserCredentialSmsTokenLoginServiceTest extends \PHPUnit\Framework\TestCase {
/**
* @var UserCredentialSmsTotpService
*/
protected $object;
/**
* Sets up the fixture, for example, opens a network connection.
* This method is called before a test is executed.
*/
protected function setUp() : void {
$this->object = new UserCredentialSmsTokenLoginService;
/**
* This is the password that is stored in DB hashed with \password_hash function.
* PHP 5.4 will be supported because of ircmaxell/password-compat package
*/
$this->password = \password_hash('123456', \PASSWORD_DEFAULT);
}
/**
* Tears down the fixture, for example, closes a network connection.
* This method is called after a test is executed.
*/
protected function tearDown() : void {
}
/**
* @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::initialize
*/
public function testInitializeStage1Exception() {
$this->expectException('\cymapgt\Exception\UserCredentialException');
$this->expectExceptionMessage('The multi factor stages register is initialized with an an unknown state');
/**
* When you call setMultiFactor() to true, we require more info than just username, hashed password and password logged
*
* This additional parameters have to be set during Stage 1 are
* - The Multi Factor stages array, which indicates the current stage and has an additional state info. The structure of the
* array is :
*
* array (
* 'current' => $loginStage,
* 1 => array (
*
* )
* );
*
* Where 'curent' is the login stage of the multifactor auth transaction and array with key 1 contains stage info. Note
* that the state 1 indicates we are in stage 1. Each subsequent stage gets its index (see below)
*
* - The EncKey Length which is the length of the hash that will change with each login transaction. Default length is 16
* and it is generated using \openssl_pseudo_random_bytes. The client must return this hash together with login
* info for subsequent login stages e.g during stage 2 otherwise authentication will not occur even with correct token
*
*/
$this->object->setMultiFactor(true);
$this->object->initialize();
}
/**
* @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::initialize
*/
public function testInitializeStage1() {
$this->object->setMultiFactor(true);
//Set multifactor stages array as explained above
$this->object->setMultiFactorStages(array('current' => 1, 1 => array()));
//set EncKey Length as explained above
$this->object->setEncKeyLength(16);
//Username and password as required by UserCredential service
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword(\password_hash('123456', \PASSWORD_DEFAULT));
//initialize for multi-factor should now work l now work
$this->object->initialize();
//because we are in Stage 1 the statuss of multi-factor array should have been set false by the class because we are yet to authenticate
$mFactorStages = $this->object->getMultiFactorStages();
$this->assertIsBool($mFactorStages[1]['statuss']);
$this->assertEquals(false, $mFactorStages[1]['statuss']);
}
/**
* @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::authenticate
*/
public function testAuthenticateStage1() {
//initialize and authenticate password (stage 1)
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 1, 1 => array()));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$this->object->setPassword('1234567');
$this->object->initialize();
$authResult = $this->object->authenticate();
/**
* Note that the auth result should be an array for stages prior to the final stage
*
* The structure of a successful authResult is
*
* $authResult = array (
* 1 => array (
* 'statuss' => true
* ),
* 2 => array (
* 'enc_key' => \openssl_random_pseudo_bytes($this->getEncKeyLength()),
* 'statuss' => false
* )
* );
*
* The structure of an unsuccessful authResult is
*
* $authResult = array (
* 1 => array (
* 'statuss' => false
* )
* );
*
*/
//as per above structure, assert the unsuccessful login
$this->assertIsArray($authResult);
$this->assertEquals(false, $authResult[1]['statuss']);
$this->object->setPassword('123456');
$this->object->initialize();
$authResult2 = $this->object->authenticate();
//as per the above structure, assert successful login for Stage 1
$this->assertIsArray($authResult2);
$this->assertEquals(true, $authResult2[1]['statuss']);
$this->assertIsArray($authResult2[2]);
$this->assertArrayHasKey('enc_key', $authResult2[2]);
$this->assertArrayHasKey('statuss', $authResult2[2]);
}
/**
* @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::initialize
*/
public function testInitializeStage2Exception() {
$this->expectException('\cymapgt\Exception\UserCredentialException');
$this->expectExceptionMessage('The user TOTP profile is not initialized properly');
/**
* When you call setMultiFactor() to true, we require more info than just username, hashed password and password logged
*
* This additional parameters have to be set during Stage 2 are
*
* - The TOTP Profile array, which contains some information about the generated token. The structure of the TOTP array is:
*
* array (
* 'enc_key' => $encKey, //enc key generated in Stage 1
* 'totp_timestamp" => $totpTimestamp, //timestamp when the login transaction opened
* 'totp_timelimit' => $totpTimelimit //time limit for user to complete stage 2 (180 seconds by default)
* );
*
* - The Verification Hash. This is a hash computed with \crypt, and salted with the unique EncKey generated in stage 1
*
* - One Time Token: This is the one time token input by the user in the Login Screen / API
*
* - Current One Time Token: This is the one time token generated using MultiOTP and which user is expected to input.
* Note that this is Hashed using \password_hash
*/
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 2, 1 => array()));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$this->object->setPassword('1234567');
//This should generate Exception as we have not initialized with TOTP profile, verification hash, one time token and Current One Time Token
$this->object->initialize();
}
/**
* @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::initialize
*/
public function testInitializeStage2() {
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 1, 1 => array()));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$this->object->setPassword('123456');
$this->object->initialize();
$authResult = $this->object->authenticate();
$encKey = $authResult[2]['enc_key'];
$verificationHash = \crypt($this->object->getPassword(), $authResult[2]['enc_key']);
$nowObj = new \DateTime();
$nowObj->setTimestamp(($nowObj->getTimestamp() - 140));
$totpTimeLimit = 180;
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 2, 1 => array('statuss' => true)));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$totpProfile = array (
'enc_key' => $encKey,
'totp_timestamp' => $nowObj,
'totp_timelimit' => $totpTimeLimit
);
$this->object->setUserTotpProfile($totpProfile);
$this->object->setVerificationHash($verificationHash);
$oneTimeToken = \password_hash('123456', \PASSWORD_DEFAULT);
$this->object->setOneTimeToken($oneTimeToken);
$this->object->setCurrentOneTimeToken($oneTimeToken);
//This should go through
$this->object->initialize();
$this->assertEquals(1, true);
}
/**
* @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::authenticate
*/
public function testAuthenticateStage2() {
//Stage 1 Authentication
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 1, 1 => array()));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$this->object->setPassword('123456');
$this->object->initialize();
$authResult = $this->object->authenticate();
$encKey = $authResult[2]['enc_key'];
$verificationHash = \crypt($this->object->getCurrentPassword(), $authResult[2]['enc_key']);
$nowObj = new \DateTime();
$nowObj->setTimestamp(($nowObj->getTimestamp() - 140));
$totpTimeLimit = 180;
//Stage 2 Authentication
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 2, 1 => array('statuss' => true)));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$totpProfile = array (
'enc_key' => $encKey,
'totp_timestamp' => $nowObj,
'totp_timelimit' => $totpTimeLimit
);
/**
* For This stage of authentication, three things must be Validated
*
* 1) Token Input by user in Login Screen / API must match the Expected Token Generated
*
* 2) Token must have not been input later than totp_timelimit seconds
*
* 3) The VerificationHash recalculated by \crypt using the EncKey must match the one generated
* in Stage 1
*/
$this->object->setUserTotpProfile($totpProfile);
$this->object->setVerificationHash($verificationHash);
$oneTimeToken = \password_hash('827110', \PASSWORD_DEFAULT);
$this->object->setCurrentOneTimeToken($oneTimeToken);
$this->object->setOneTimeToken('827110');
$this->object->initialize();
$this->assertEquals(true, $this->object->authenticate());
}
/**
* @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::authenticate
*/
public function testAuthenticateStageTimelimit() {
//Correct detials, but Token was input 181 seconds later. Should fail
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 1, 1 => array()));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$this->object->setPassword('123456');
$this->object->initialize();
$authResult = $this->object->authenticate();
$encKey = $authResult[2]['enc_key'];
$verificationHash = \crypt($this->object->getCurrentPassword(), $authResult[2]['enc_key']);
//simulate delaying submission by 1 second
$nowObj = new \DateTime();
$nowObj->setTimestamp(($nowObj->getTimestamp() - 181));
$totpTimeLimit = 180;
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 2, 1 => array('statuss' => true)));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$totpProfile = array (
'enc_key' => $encKey,
'totp_timestamp' => $nowObj,
'totp_timelimit' => $totpTimeLimit
);
$this->object->setUserTotpProfile($totpProfile);
$this->object->setVerificationHash($verificationHash);
$oneTimeToken = \password_hash('827110', \PASSWORD_DEFAULT);
$this->object->setCurrentOneTimeToken($oneTimeToken);
$this->object->setOneTimeToken('827110');
$this->object->initialize();
$this->assertEquals(false, $this->object->authenticate());
}
/**
* @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::authenticate
*/
public function testAuthenticateStageTokenWrong() {
//Wrong Token has been keyed in. Should fail
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 1, 1 => array()));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$this->object->setPassword('123456');
$this->object->initialize();
$authResult = $this->object->authenticate();
$encKey = $authResult[2]['enc_key'];
$verificationHash = \crypt($this->object->getCurrentPassword(), $authResult[2]['enc_key']);
$nowObj = new \DateTime();
$nowObj->setTimestamp(($nowObj->getTimestamp() - 140));
$totpTimeLimit = 180;
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 2, 1 => array('statuss' => true)));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$totpProfile = array (
'enc_key' => $encKey,
'totp_timestamp' => $nowObj,
'totp_timelimit' => $totpTimeLimit
);
$this->object->setUserTotpProfile($totpProfile);
$this->object->setVerificationHash($verificationHash);
$oneTimeToken = \password_hash('827110', \PASSWORD_DEFAULT);
$this->object->setCurrentOneTimeToken($oneTimeToken);
$this->object->setOneTimeToken('827118');
$this->object->initialize();
$this->assertEquals(false, $this->object->authenticate());
}
/**
* @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::authenticate
*/
public function testAuthenticateStageEncKeyWrong() {
//Wrong EncKey provided, thus VerificationHash check fails. TODO: Does it mitigate Replay?
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 1, 1 => array()));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$this->object->setPassword('123456');
$this->object->initialize();
$authResult = $this->object->authenticate();
$verificationHash = \crypt($this->object->getCurrentPassword(), $authResult[2]['enc_key']);
$nowObj = new \DateTime();
$nowObj->setTimestamp(($nowObj->getTimestamp() - 140));
$totpTimeLimit = 180;
$this->object->setMultiFactor(true);
$this->object->setMultiFactorStages(array('current' => 2, 1 => array('statuss' => true)));
$this->object->setEncKeyLength(16);
$this->object->setCurrentUserName('rhossis');
$this->object->setCurrentPassword($this->password);
$totpProfile = array (
'enc_key' => 'hElLoThErEiAmAwRoNgEnCkEy',
'totp_timestamp' => $nowObj,
'totp_timelimit' => $totpTimeLimit
);
$this->object->setUserTotpProfile($totpProfile);
$this->object->setVerificationHash($verificationHash);
$oneTimeToken = \password_hash('827110', \PASSWORD_DEFAULT);
$this->object->setCurrentOneTimeToken($oneTimeToken);
$this->object->setOneTimeToken('827110');
$this->object->initialize();
$this->assertEquals(false, $this->object->authenticate());
}
}
|