PHP Classes
elePHPant
Icontem

File: tests/src/services/UserCredentialSmsTokenLoginServiceTest.php

Recommend this page to a friend!
  Classes of Cyril Ogana  >  PHP User Credentials  >  tests/src/services/UserCredentialSmsTokenLoginServiceTest.php  >  Download  
File: tests/src/services/UserCredentialSmsTokenLoginServiceTest.php
Role: Unit test script
Content type: text/plain
Description: Add MultiOTP wrapper and SMS Token 2 factor authentication service
Class: PHP User Credentials
Implement password authentication policies
Author: By
Last change: Update unit test for UserCredentialSmsTokenLoginService which break with upgrade to PHPUnit6
Date: 1 year ago
Size: 18,198 bytes
 

 

Contents

Class file image Download
<?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() {
        $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() {
        
    }

    /**
     * @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::initialize
     * @expectedException \cymapgt\Exception\UserCredentialException
     * @expectedExceptionMessage The multi factor stages register is initialized with an an unknown state
     */
    public function testInitializeStage1Exception() {
        /**
         *     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->assertInternalType('bool', $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->assertInternalType('array', $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->assertInternalType('array', $authResult2);
        $this->assertEquals(true, $authResult2[1]['statuss']);
        $this->assertInternalType('array', $authResult2[2]);
        $this->assertArrayHasKey('enc_key', $authResult2[2]);
        $this->assertArrayHasKey('statuss', $authResult2[2]);        
    }
    
    /**
     * @covers cymapgt\core\application\authentication\UserCredential\services\UserCredentialSmsTotpService::initialize
     * @expectedException \cymapgt\Exception\UserCredentialException
     * @expectedExceptionMessage The user TOTP profile is not initialized properly
     */
    public function testInitializeStage2Exception() {
        /**
         *     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());
    }       
}