altcha-1.0.0/tests/src/Functional/AltchaBasicTest.php
tests/src/Functional/AltchaBasicTest.php
<?php
namespace Drupal\Tests\altcha\Functional;
use Drupal\Component\Utility\Html;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\UserInterface;
/**
* Test basic functionality for the ALTCHA module.
*
* @group altcha
*
* @dependencies captcha
*/
class AltchaBasicTest extends BrowserTestBase {
use StringTranslationTrait;
/**
* A normal user.
*
* @var \Drupal\user\UserInterface
*/
protected UserInterface $normalUser;
/**
* An admin user.
*
* @var \Drupal\user\UserInterface
*/
protected UserInterface $adminUser;
/**
* The hmac key.
*
* @var string
*/
protected string $secretKey;
/**
* Modules to enable.
*
* @var string[]
*/
protected static $modules = ['altcha', 'captcha', 'user'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The ALTCHA widget xpath selector.
*
* @var string
*/
protected string $altchaSelector = '//input[@name="captcha_token"]';
/**
* The default ALTCHA library, included with the module.
*
* @var string
*/
protected string $defaultLibrary = 'assets/vendor/altcha/altcha.min.js';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
\Drupal::moduleHandler()->loadInclude('captcha', 'inc');
// Create a normal user.
$this->normalUser = $this->drupalCreateUser();
// Create an admin user.
$permissions = [
'access administration pages',
'administer site configuration',
'administer CAPTCHA settings',
'skip CAPTCHA',
'administer permissions',
'administer altcha',
];
$this->adminUser = $this->drupalCreateUser($permissions);
}
/**
* The hmac secret key.
*/
protected function testInstallation(): void {
$this->assertNotEmpty($this->secretKey);
$this->assertEquals(64, strlen($this->secretKey));
}
/**
* Test access to the administration page.
*/
public function testAdminAccess(): void {
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/people/captcha/altcha');
$this->assertSession()->pageTextNotContains($this->t('Access denied'));
$this->drupalLogout();
}
/**
* Test the ALTCHA settings form.
*/
public function testSettingsForm(): void {
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/config/people/captcha/altcha');
$this->drupalLogout();
}
/**
* Testing the protection of the user login form.
*/
public function testLoginForm(): void {
// Validate login process.
$this->drupalLogin($this->normalUser);
$this->drupalLogout();
$this->drupalGet('user/login');
// ALTCHA should not be configured yet.
$this->assertSession()->elementNotExists('xpath', $this->altchaSelector);
// Enable 'altcha/ALTCHA' on login form.
captcha_set_form_id_setting('user_login_form', 'altcha/ALTCHA');
$result = captcha_get_form_id_setting('user_login_form');
// ALTCHA should be configured.
$this->assertNotNull($result, 'A configuration has been found for CAPTCHA point: user_login_form');
$this->assertEquals($result->getCaptchaType(), 'altcha/ALTCHA', 'Altcha type has been configured for CAPTCHA point: user_login_form');
// Test the sentinel API version.
$this->config('altcha.settings')
->set('integration_type', 'sentinel_api')
->save();
$this->config('altcha.settings')->set('sentinel_api_url', 'https://example.com')->save();
$this->config('altcha.settings')->set('sentinel_api_key', 'key_hello')->save();
$this->config('altcha.settings')->set('sentinel_api_secret', 'sec_test')->save();
$options = [
'query' => [
'apiKey' => 'key_hello',
],
];
$this->drupalGet('user/login');
// An ALTCHA should exist with challenge url matching the configuration.
$this->assertSession()->elementExists('xpath', $this->altchaSelector);
$this->assertSession()
->responseContains(Html::escape(Url::fromUri('https://example.com/v1/challenge', $options)
->toString()));
// Test the API SAAS version.
$this->config('altcha.settings')
->set('integration_type', 'saas_api')
->save();
$this->config('altcha.settings')->set('saas_api_key', 'test')->save();
$this->config('altcha.settings')->set('saas_api_region', 'eu')->save();
$this->config('altcha.settings')->set('max_number', 20000)->save();
$options = [
'query' => [
'apiKey' => 'test',
'maxnumber' => 20000,
],
];
$this->drupalGet('user/login');
// An ALTCHA should exist with challenge url matching the configuration.
$this->assertSession()->elementExists('xpath', $this->altchaSelector);
$this->assertSession()
->responseContains(Html::escape(Url::fromUri('https://eu.altcha.org/api/v1/challenge', $options)
->toString()));
// Test the Self-hosted version.
$this->config('altcha.settings')
->set('integration_type', 'self_hosted')
->save();
\Drupal::service('altcha.secret_manager')->generateSecretKey();
$this->drupalGet('user/login');
$this->assertSession()->elementExists('xpath', $this->altchaSelector);
$this->assertSession()
->responseContains(Html::escape(Url::fromRoute('altcha.challenge')
->toString()));
// Check auto verification attribute.
$this->config('altcha.settings')
->set('auto_verification', 'onsubmit')
->save();
$this->drupalGet('user/login');
$element = $this->xpath('//altcha-widget[@auto="onsubmit"]');
$this->assertNotEmpty($element, 'auto verification should be enabled and onsubmit.');
// Check the maxnumber attribute.
$this->config('altcha.settings')->set('max_number', 10000)->save();
$this->drupalGet('user/login');
$element = $this->xpath('//altcha-widget[@maxnumber="10000"]');
$this->assertNotEmpty($element, 'maxnumber should be enabled and equal to 10000');
// Validate that the login attempt fails.
$edit['name'] = $this->normalUser->getAccountName();
$edit['pass'] = $this->normalUser->getPassword();
$this->drupalGet('user/login');
$this->submitForm($edit, $this->t('Log in'));
$this->assertSession()
->pageTextContains($this->t('The answer you entered for the CAPTCHA was not correct.'));
// Make sure the user did not start a session.
$this->assertFalse($this->drupalUserIsLoggedIn($this->normalUser));
}
/**
* Tests if the library override works.
*
* By default, the module library should be added to an ALTCHA form.
* When a library override is configured the override library should be added
* to the form and not the default library.
*
* Test the 4 possible override methods:
* - CDN
* - Stream wrapper (file uri)
* - Path relative to drupal root
* - Path relative to server root
*/
public function testLibraryOverrideUrl() {
// Enable 'altcha/ALTCHA' on login form.
captcha_set_form_id_setting('user_login_form', 'altcha/ALTCHA');
$result = captcha_get_form_id_setting('user_login_form');
// ALTCHA should be configured.
$this->assertNotNull($result, 'A configuration has been found for CAPTCHA point: user_login_form');
$this->assertEquals($result->getCaptchaType(), 'altcha/ALTCHA', 'Altcha type has been configured for CAPTCHA point: user_login_form');
// Now go to the login page where the ALTCHA form will be rendered.
$this->drupalGet('user/login');
// An ALTCHA should exist.
$this->assertSession()->elementExists('xpath', $this->altchaSelector);
// The default library should be loaded via script tag.
$this->assertSession()->elementExists('xpath', "//head//script[contains(@src, '{$this->defaultLibrary}')]");
// 1. Library override CDN url.
$this->validateLibraryOverride(
'https://cdn.example.com/js/altcha.min.js',
'https://cdn.example.com/js/altcha.min.js',
);
// 2. Library override public file uri.
$this->validateLibraryOverride(
'public://libraries/altcha/js/altcha-public-fs-library.min.js',
'files/libraries/altcha/js/altcha-public-fs-library.min.js',
);
// 3. Library override url relative to the drupal web root.
$this->validateLibraryOverride(
'libraries/js/altcha-relative-path-library.min.js',
'libraries/js/altcha-relative-path-library.min.js',
);
// 4. Library override url absolute to the server root.
$this->validateLibraryOverride(
\Drupal::root() . '/libraries/js/altcha-absolute-path-library.min.js',
'libraries/js/altcha-absolute-path-library.min.js',
);
}
/**
* Helper function to validate library overrides.
*
* @param string $override
* The override to be configured in ALTCHA settings.
* @param string $expectation
* The expected script src to be loaded in the html head.
*/
protected function validateLibraryOverride(string $override, string $expectation): void {
$this->config('altcha.settings')->set('library_override', $override)->save();
// Reload the login page to apply the changes.
$this->drupalGet('user/login');
// An ALTCHA should still exist on the form.
$this->assertSession()->elementExists('xpath', $this->altchaSelector);
// Verify that the script tag with the library URL is added to the page.
// We expect this to be in the <head> section of the page.
$this->assertSession()->elementExists('xpath', "//head//script[contains(@src, '$expectation')]");
// The default library should not be available.
$this->assertSession()->elementNotExists('xpath', "//head//script[contains(@src, '{$this->defaultLibrary}')]");
// When the override does not exactly match the expectation, make sure the
// override is not just included in the page without any manipulation.
// Example: "//head//script[contains(@src, 'libraries/altcha.js')]" xpath
// would also match the override "/var/www/html/libraries/altcha.js".
if ($override !== $expectation) {
$this->assertSession()->elementNotExists('xpath', "//head//script[contains(@src, '$override')]");
}
}
}
