You are here

tfa_basic.test in TFA Basic plugins 7

tfa_basic.test. Drupal test cases for TFA basic plugins.

File

tests/tfa_basic.test
View source
<?php

/**
 * @file tfa_basic.test.
 * Drupal test cases for TFA basic plugins.
 */

/**
 * Tests the functionality of the TFA basic plugins.
 */
class TfaBasicTestCase extends DrupalWebTestCase {

  /**
   * @var PHPGangsta_GoogleAuthenticator
   */
  protected $ga;

  /**
   * @var array
   */
  protected $recoveryCodes;

  /**
   * @var string
   */
  protected $seed;

  /**
   * @var stdClass
   */
  protected $web_user;

  /**
   * @var stdClass
   */
  protected $admin_user;
  public static function getInfo() {
    return array(
      'name' => 'TFA Basic tests',
      'description' => 'Test the TFA basic plugins.',
      'group' => 'TFA',
    );
  }
  public function setUp() {

    // Enable TFA module and the test module.
    parent::setUp('tfa', 'tfa_basic');

    // Use PHPGangsta_GoogleAuthenticator to create codes for TOTP seed.
    require_once drupal_get_path('module', 'tfa_basic') . '/includes/googleauthenticator/GoogleAuthenticator.php';
    $this->ga = new PHPGangsta_GoogleAuthenticator();
    $this->web_user = $this
      ->drupalCreateUser(array(
      'access content',
      'setup own tfa',
    ));
    $this->admin_user = $this
      ->drupalCreateUser(array(
      'access content',
      'administer users',
    ));
  }
  public function testTfaBasic() {

    // Use one test function to allow accounts to carry through testing.
    $this
      ->_testAppAndRecoverySetup();
    $this
      ->_testAdminDisable();
  }
  public function _testAppAndRecoverySetup() {
    variable_set('tfa_enabled', FALSE);
    $account = $this->web_user;
    $this
      ->drupalLogin($account);

    // Enable TFA and begin configuration.
    variable_set('tfa_enabled', TRUE);
    variable_set('tfa_validate_plugin', 'tfa_basic_totp');
    variable_set('tfa_fallback_plugins', array(
      'tfa_basic_recovery_code',
    ));
    $this
      ->drupalGet('user/' . $account->uid . '/security/tfa');
    $this
      ->assertLink($this
      ->uiStrings('setup-app'));

    // Set up application.
    $this
      ->drupalGet('user/' . $account->uid . '/security/tfa/app-setup');
    $this
      ->assertText($this
      ->uiStrings('password-request'));

    // Test incorrect password.
    $edit = array(
      'current_pass' => $this
        ->randomName(),
    );
    $this
      ->drupalPost(NULL, $edit, 'Confirm');
    $this
      ->assertText($this
      ->uiStrings('pass-error'));
    $edit = array(
      'current_pass' => $account->pass_raw,
    );
    $this
      ->drupalPost(NULL, $edit, 'Confirm');
    $this
      ->assertText($this
      ->uiStrings('app-step1'));
    $this
      ->assertFieldById('edit-seed', '', 'Seed input appears');
    $this
      ->assertFieldById('edit-code', '', 'Code input appears');

    // Extract and store seed to generate codes with.
    $result = $this
      ->xpath('//input[@name="seed"]');
    if (empty($result)) {
      $this
        ->fail('Unable to extract seed from page. Aborting test.');
      return;
    }
    $element = $result[0];
    $this->seed = (string) $element['value'];

    // Try invalid code.
    $edit = array(
      'code' => $this
        ->randomName(),
    );
    $this
      ->drupalPost(NULL, $edit, 'Verify and save');
    $this
      ->assertText($this
      ->uiStrings('invalid-code-retry'));

    // Submit valid code.
    $edit = array(
      'code' => $this->ga
        ->getCode($this->seed),
    );
    $this
      ->drupalPost(NULL, $edit, 'Verify and save');

    // Setup recovery codes.
    $this
      ->assertText($this
      ->uiStrings('set-recovery-codes'));

    // Store codes.
    $result = $this
      ->xpath('//li');
    while (list(, $node) = each($result)) {
      $this->recoveryCodes[] = (string) $node;
    }
    $this
      ->drupalPost(NULL, array(), 'Save');
    $this
      ->assertText($this
      ->uiStrings('setup-complete'));

    // Logout to test TFA app process.
    $this
      ->drupalGet('user/logout');
    $edit = array(
      'name' => $account->name,
      'pass' => $account->pass_raw,
    );

    // Do not use drupalLogin() since it tests for actual login.
    $this
      ->drupalPost('user/login', $edit, 'Log in');

    // Get login hash. Could user tfa_login_hash() but would require reloading
    // account.
    $url_parts = explode('/', $this->url);
    $login_hash = array_pop($url_parts);

    // Try invalid code.
    $edit = array(
      'code' => $this
        ->randomName(),
    );
    $this
      ->drupalPost('system/tfa/' . $account->uid . '/' . $login_hash, $edit, 'Verify');
    $this
      ->assertText($this
      ->uiStrings('invalid-code-retry'));

    // Submit valid code.
    $edit = array(
      'code' => $this->ga
        ->getCode($this->seed),
    );
    $this
      ->drupalPost('system/tfa/' . $account->uid . '/' . $login_hash, $edit, 'Verify');
    $this
      ->assertText('My account');

    // Logout to test recovery code process.
    $this
      ->drupalGet('user/logout');
    $edit = array(
      'name' => $account->name,
      'pass' => $account->pass_raw,
    );
    $this
      ->drupalPost('user/login', $edit, 'Log in');
    $url_parts = explode('/', $this->url);
    $login_hash = array_pop($url_parts);

    // Begin fallback.
    $this
      ->drupalPost('system/tfa/' . $account->uid . '/' . $login_hash, array(), $this
      ->uiStrings('fallback-button'));
    $this
      ->assertText($this
      ->uiStrings('recovery-prompt'));

    // Try invalid code.
    $edit = array(
      'recover' => $this
        ->randomName(),
    );
    $this
      ->drupalPost('system/tfa/' . $account->uid . '/' . $login_hash, $edit, 'Verify');
    $this
      ->assertText($this
      ->uiStrings('invalid-recovery-code'));

    // Submit valid code.
    $edit = array(
      'recover' => array_pop($this->recoveryCodes),
    );
    $this
      ->drupalPost('system/tfa/' . $account->uid . '/' . $login_hash, $edit, 'Verify');
    $this
      ->assertText('My account');
  }
  public function _testAdminDisable() {
    $this
      ->drupalLogin($this->admin_user);
    $this
      ->drupalGet('user/' . $this->web_user->uid . '/security/tfa');
    $this
      ->assertText($this->web_user->name, 'Account name appears');
    $this
      ->assertText($this
      ->uiStrings('tfa-status-enabled'));
    $this
      ->assertLink($this
      ->uiStrings('tfa-disable'));
    $this
      ->drupalGet('user/' . $this->web_user->uid . '/security/tfa/disable');
    $this
      ->assertText($this
      ->uiStrings('tfa-disable-confirm'));
    $edit = array(
      'current_pass' => $this->admin_user->pass_raw,
    );
    $this
      ->drupalPost('user/' . $this->web_user->uid . '/security/tfa/disable', $edit, 'Disable');
    $this
      ->assertText($this
      ->uiStrings('tfa-disabled'));
  }
  public function testTotpReplay() {
    variable_set('tfa_enabled', TRUE);
    variable_set('tfa_validate_plugin', 'tfa_basic_totp');
    $account = $this
      ->drupalCreateUser(array(
      'access content',
      'setup own tfa',
    ));
    $edit = array(
      'name' => $account->name,
      'pass' => $account->pass_raw,
    );
    $this
      ->drupalPost('user/login', $edit, 'Log in');

    // Set up application.
    $this
      ->drupalGet('user/' . $account->uid . '/security/tfa/app-setup');
    $pass_form = array(
      'current_pass' => $account->pass_raw,
    );
    $this
      ->drupalPost(NULL, $pass_form, 'Confirm');
    $result = $this
      ->xpath('//input[@name="seed"]');
    if (empty($result)) {
      $this
        ->fail('Unable to extract seed from page. Aborting test.');
      return;
    }
    $element = $result[0];
    $this->seed = (string) $element['value'];

    // Submit valid code.
    $code_form = array(
      'code' => $this->ga
        ->getCode($this->seed),
    );
    $this
      ->drupalPost(NULL, $code_form, 'Verify and save');
    $this
      ->drupalLogout();
    $edit = array(
      'name' => $account->name,
      'pass' => $account->pass_raw,
    );
    $this
      ->drupalPost('user/login', $edit, 'Log in');
    $url_parts = explode('/', $this->url);
    $login_hash = array_pop($url_parts);

    // Submit valid code.
    $code = $this->ga
      ->getCode($this->seed);
    $code_form = array(
      'code' => $code,
    );
    $this
      ->drupalPost('system/tfa/' . $account->uid . '/' . $login_hash, $code_form, 'Verify');
    $this
      ->assertText('My account');

    // Logout and retry same code.
    $this
      ->drupalLogout();
    $edit = array(
      'name' => $account->name,
      'pass' => $account->pass_raw,
    );
    $this
      ->drupalPost('user/login', $edit, 'Log in');
    $url_parts = explode('/', $this->url);
    $login_hash = array_pop($url_parts);
    $code_form = array(
      'code' => $code,
    );
    $this
      ->drupalPost('system/tfa/' . $account->uid . '/' . $login_hash, $code_form, 'Verify');
    $this
      ->assertNoText('My account');
    $this
      ->assertText($this
      ->uiStrings('tfa-replay'));

    // Set expire time and run cron to delete saved code to log in.
    variable_set('tfa_basic_accepted_code_expiration', '0');
    $this
      ->cronRun();
    $code_form = array(
      'code' => $code,
    );
    $this
      ->drupalPost('system/tfa/' . $account->uid . '/' . $login_hash, $code_form, 'Verify');
    $this
      ->assertText('My account');
  }
  public function testRequired() {
    variable_set('tfa_enabled', TRUE);
    variable_set('tfa_validate_plugin', 'tfa_basic_totp');
    $account = $this
      ->drupalCreateUser(array(
      'access content',
      'setup own tfa',
    ));
    $edit = array(
      'name' => $account->name,
      'pass' => $account->pass_raw,
    );
    $this
      ->drupalPost('user/login', $edit, 'Log in');

    // Set up application.
    $this
      ->drupalGet('user/' . $account->uid . '/security/tfa/app-setup');
    $pass_form = array(
      'current_pass' => $account->pass_raw,
    );
    $this
      ->drupalPost(NULL, $pass_form, 'Confirm');
    $result = $this
      ->xpath('//input[@name="seed"]');
    if (empty($result)) {
      $this
        ->fail('Unable to extract seed from page. Aborting test.');
      return;
    }
    $element = $result[0];
    $this->seed = (string) $element['value'];

    // Submit valid code.
    $code_form = array(
      'code' => $this->ga
        ->getCode($this->seed),
    );
    $this
      ->drupalPost(NULL, $code_form, 'Verify and save');

    // Set required for authenticated and confirm messages.
    variable_set('tfa_basic_roles_require', array(
      DRUPAL_AUTHENTICATED_RID => DRUPAL_AUTHENTICATED_RID,
    ));
    $this
      ->drupalGet('user/' . $account->uid . '/security/tfa/disable');
    $this
      ->assertText($this
      ->uiStrings('disable-required'));

    // Disable TFA.
    $this
      ->drupalPost(NULL, $pass_form, 'Disable');
    $this
      ->drupalGet('user/logout');

    // Confirm cannot log in.
    $this
      ->drupalPost('user/login', $edit, 'Log in');
    $this
      ->assertNoLink('Log out', 'Not authenticated');
    $this
      ->assertText($this
      ->uiStrings('required'), 'Required text shows');
  }

  /**
   * TFA module user interface strings.
   *
   * @param string $id
   * @return string
   */
  protected function uiStrings($id) {
    switch ($id) {
      case 'setup-app':
        return 'Set up application';
      case 'password-request':
        return 'Enter your current password to continue.';
      case 'pass-error':
        return 'Incorrect password';
      case 'app-step1':
        return 'Install authentication code application on your mobile or desktop device';
      case 'invalid-code-retry':
        return 'Invalid application code. Please try again.';
      case 'invalid-recovery-code':
        return 'Invalid recovery code.';
      case 'set-trust-skip':
        return 'Mark this browser as trusted or skip to continue and finish TFA setup';
      case 'set-recovery-codes':
        return 'Your recovery codes';
      case 'setup-complete':
        return 'TFA setup complete';
      case 'setup-trust':
        return 'Set trusted browsers';
      case 'setup-recovery':
        return 'Get recovery codes';
      case 'code-list':
        return 'View unused recovery codes';
      case 'app-desc':
        return 'Verification code is application generated and 6 digits long.';
      case 'fallback-button':
        return 'Can\'t access your account?';
      case 'recovery-prompt':
        return 'Enter one of your recovery codes';
      case 'tfa-status-enabled':
        return 'TFA enabled';
      case 'tfa-disable':
        return 'Disable TFA';
      case 'tfa-disable-confirm':
        return 'Are you sure you want to disable TFA on account';
      case 'tfa-disabled':
        return 'TFA has been disabled';
      case 'required':
        return 'Login disallowed. You are required to set up two-factor authentication.';
      case 'disable-required':
        return 'Your account must have at least one two-factor authentication method enabled. Continuing will disable your ability to log back into this site.';
      case 'tfa-replay':
        return 'Invalid code, it was recently used for a login. Please wait for the application to generate a new code.';
    }
  }

}

Classes

Namesort descending Description
TfaBasicTestCase Tests the functionality of the TFA basic plugins.