captcha.test in CAPTCHA 7
Same filename and directory in other branches
Tests for CAPTCHA module.
File
captcha.testView source
<?php
/**
* @file
* Tests for CAPTCHA module.
*/
// TODO: write test for CAPTCHAs on admin pages
// TODO: test for default challenge type
// TODO: test about placement (comment form, node forms, log in form, etc)
// TODO: test if captcha_cron does it work right
// TODO: test custom CAPTCHA validation stuff
// TODO: test if entry on status report (Already X blocked form submissions) works
// TODO: test space ignoring validation of image CAPTCHA
// TODO: refactor the 'comment_body[' . LANGUAGE_NONE . '][0][value]' stuff
// Some constants for better reuse.
define('CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE', 'The answer you entered for the CAPTCHA was not correct.');
define('CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE', 'CAPTCHA session reuse attack detected.');
define('CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE', 'CAPTCHA validation error: unknown CAPTCHA session ID. Contact the site administrator if this problem persists.');
/**
* Base class for CAPTCHA tests.
*
* Provides common setup stuff and various helper functions
*/
abstract class CaptchaBaseWebTestCase extends DrupalWebTestCase {
/**
* User with various administrative permissions.
* @var Drupal user
*/
protected $admin_user;
/**
* Normal visitor with limited permissions
* @var Drupal user;
*/
protected $normal_user;
/**
* Form ID of comment form on standard (page) node
* @var string
*/
const COMMENT_FORM_ID = 'comment_node_page_form';
/**
* Drupal path of the (general) CAPTCHA admin page
*/
const CAPTCHA_ADMIN_PATH = 'admin/config/people/captcha';
function setUp() {
// Load two modules: the captcha module itself and the comment module for testing anonymous comments.
$modules = func_get_args();
if (isset($modules[0]) && is_array($modules[0])) {
$modules = $modules[0];
}
parent::setUp(array_merge(array(
'captcha',
'comment',
), $modules));
module_load_include('inc', 'captcha');
// Create a normal user.
$permissions = array(
'access comments',
'post comments',
'skip comment approval',
'access content',
'create page content',
'edit own page content',
);
$this->normal_user = $this
->drupalCreateUser($permissions);
// Create an admin user.
$permissions[] = 'administer CAPTCHA settings';
$permissions[] = 'skip CAPTCHA';
$permissions[] = 'administer permissions';
$permissions[] = 'administer content types';
$this->admin_user = $this
->drupalCreateUser($permissions);
// Put comments on page nodes on a separate page (default in D7: below post).
variable_set('comment_form_location_page', COMMENT_FORM_SEPARATE_PAGE);
}
/**
* Assert that the response is accepted:
* no "unknown CSID" message, no "CSID reuse attack detection" message,
* no "wrong answer" message.
*/
protected function assertCaptchaResponseAccepted() {
// There should be no error message about unknown CAPTCHA session ID.
$this
->assertNoText(t(CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE), 'CAPTCHA response should be accepted (known CSID).', 'CAPTCHA');
// There should be no error message about CSID reuse attack.
$this
->assertNoText(t(CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE), 'CAPTCHA response should be accepted (no CAPTCHA session reuse attack detection).', 'CAPTCHA');
// There should be no error message about wrong response.
$this
->assertNoText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE), 'CAPTCHA response should be accepted (correct response).', 'CAPTCHA');
}
/**
* Assert that there is a CAPTCHA on the form or not.
* @param bool $presence whether there should be a CAPTCHA or not.
*/
protected function assertCaptchaPresence($presence) {
if ($presence) {
$this
->assertText(_captcha_get_description(), 'There should be a CAPTCHA on the form.', 'CAPTCHA');
}
else {
$this
->assertNoText(_captcha_get_description(), 'There should be no CAPTCHA on the form.', 'CAPTCHA');
}
}
/**
* Helper function to create a node with comments enabled.
*
* @return
* Created node object.
*/
protected function createNodeWithCommentsEnabled($type = 'page') {
$node_settings = array(
'type' => $type,
'comment' => COMMENT_NODE_OPEN,
);
$node = $this
->drupalCreateNode($node_settings);
return $node;
}
/**
* Helper function to generate a form values array for comment forms
*/
protected function getCommentFormValues() {
$edit = array(
'subject' => 'comment_subject ' . $this
->randomName(32),
'comment_body[' . LANGUAGE_NONE . '][0][value]' => 'comment_body ' . $this
->randomName(256),
);
return $edit;
}
/**
* Helper function to generate a form values array for node forms
*/
protected function getNodeFormValues() {
$edit = array(
'title' => 'node_title ' . $this
->randomName(32),
'body[' . LANGUAGE_NONE . '][0][value]' => 'node_body ' . $this
->randomName(256),
);
return $edit;
}
/**
* Get the CAPTCHA session id from the current form in the browser.
*/
protected function getCaptchaSidFromForm() {
$elements = $this
->xpath('//input[@name="captcha_sid"]');
$captcha_sid = (int) $elements[0]['value'];
return $captcha_sid;
}
/**
* Get the CAPTCHA token from the current form in the browser.
*/
protected function getCaptchaTokenFromForm() {
$elements = $this
->xpath('//input[@name="captcha_token"]');
$captcha_token = (int) $elements[0]['value'];
return $captcha_token;
}
/**
* Get the solution of the math CAPTCHA from the current form in the browser.
*/
protected function getMathCaptchaSolutionFromForm() {
// Get the math challenge.
$elements = $this
->xpath('//div[@class="form-item form-type-textfield form-item-captcha-response"]/span[@class="field-prefix"]');
$challenge = (string) $elements[0];
// Extract terms and operator from challenge.
$matches = array();
$ret = preg_match('/\\s*(\\d+)\\s*(-|\\+)\\s*(\\d+)\\s*=\\s*/', $challenge, $matches);
// Solve the challenge
$a = (int) $matches[1];
$b = (int) $matches[3];
$solution = $matches[2] == '-' ? $a - $b : $a + $b;
return $solution;
}
/**
* Helper function to allow comment posting for anonymous users.
*/
protected function allowCommentPostingForAnonymousVisitors() {
// Log in as admin.
$this
->drupalLogin($this->admin_user);
// Post user permissions form
$edit = array(
'1[access comments]' => true,
'1[post comments]' => true,
'1[skip comment approval]' => true,
);
$this
->drupalPost('admin/people/permissions', $edit, 'Save permissions');
$this
->assertText('The changes have been saved.');
// Log admin out
$this
->drupalLogout();
}
}
class CaptchaTestCase extends CaptchaBaseWebTestCase {
public static function getInfo() {
return array(
'name' => t('General CAPTCHA functionality'),
'description' => t('Testing of the basic CAPTCHA functionality.'),
'group' => t('CAPTCHA'),
);
}
/**
* Testing the protection of the user log in form.
*/
function testCaptchaOnLoginForm() {
// Create user and test log in without CAPTCHA.
$user = $this
->drupalCreateUser();
$this
->drupalLogin($user);
// Log out again.
$this
->drupalLogout();
// Set a CAPTCHA on login form
captcha_set_form_id_setting('user_login', 'captcha/Math');
// Check if there is a CAPTCHA on the login form (look for the title).
$this
->drupalGet('user');
$this
->assertCaptchaPresence(TRUE);
// Try to log in, which should fail.
$edit = array(
'name' => $user->name,
'pass' => $user->pass_raw,
'captcha_response' => '?',
);
$this
->drupalPost('user', $edit, t('Log in'));
// Check for error message.
$this
->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE), 'CAPTCHA should block user login form', 'CAPTCHA');
// And make sure that user is not logged in: check for name and password fields on ?q=user
$this
->drupalGet('user');
$this
->assertField('name', t('Username field found.'), 'CAPTCHA');
$this
->assertField('pass', t('Password field found.'), 'CAPTCHA');
}
/**
* Assert function for testing if comment posting works as it should.
*
* Creates node with comment writing enabled, tries to post comment
* with given CAPTCHA response (caller should enable the desired
* challenge on page node comment forms) and checks if the result is as expected.
*
* @param $captcha_response the response on the CAPTCHA
* @param $should_pass boolean describing if the posting should pass or should be blocked
* @param $message message to prefix to nested asserts
*/
protected function assertCommentPosting($captcha_response, $should_pass, $message) {
// Make sure comments on pages can be saved directely without preview.
variable_set('comment_preview_page', DRUPAL_OPTIONAL);
// Create a node with comments enabled.
$node = $this
->createNodeWithCommentsEnabled();
// Post comment on node.
$edit = $this
->getCommentFormValues();
$comment_subject = $edit['subject'];
$comment_body = $edit['comment_body[' . LANGUAGE_NONE . '][0][value]'];
$edit['captcha_response'] = $captcha_response;
$this
->drupalPost('comment/reply/' . $node->nid, $edit, t('Save'));
if ($should_pass) {
// There should be no error message.
$this
->assertCaptchaResponseAccepted();
// Get node page and check that comment shows up.
$this
->drupalGet('node/' . $node->nid);
$this
->assertText($comment_subject, $message . ' Comment should show up on node page.', 'CAPTCHA');
$this
->assertText($comment_body, $message . ' Comment should show up on node page.', 'CAPTCHA');
}
else {
// Check for error message.
$this
->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE), $message . ' Comment submission should be blocked.', 'CAPTCHA');
// Get node page and check that comment is not present.
$this
->drupalGet('node/' . $node->nid);
$this
->assertNoText($comment_subject, $message . ' Comment should not show up on node page.', 'CAPTCHA');
$this
->assertNoText($comment_body, $message . ' Comment should not show up on node page.', 'CAPTCHA');
}
}
/*
* Testing the case sensistive/insensitive validation.
*/
function testCaseInsensitiveValidation() {
// Set Test CAPTCHA on comment form
captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');
// Log in as normal user.
$this
->drupalLogin($this->normal_user);
// Test case sensitive posting.
variable_set('captcha_default_validation', CAPTCHA_DEFAULT_VALIDATION_CASE_SENSITIVE);
$this
->assertCommentPosting('Test 123', TRUE, 'Case sensitive validation of right casing.');
$this
->assertCommentPosting('test 123', FALSE, 'Case sensitive validation of wrong casing.');
$this
->assertCommentPosting('TEST 123', FALSE, 'Case sensitive validation of wrong casing.');
// Test case insensitive posting (the default)
variable_set('captcha_default_validation', CAPTCHA_DEFAULT_VALIDATION_CASE_INSENSITIVE);
$this
->assertCommentPosting('Test 123', TRUE, 'Case insensitive validation of right casing.');
$this
->assertCommentPosting('test 123', TRUE, 'Case insensitive validation of wrong casing.');
$this
->assertCommentPosting('TEST 123', TRUE, 'Case insensitive validation of wrong casing.');
}
/**
* Test if the CAPTCHA description is only shown if there are challenge widgets to show.
* For example, when a comment is previewed with correct CAPTCHA answer,
* a challenge is generated and added to the form but removed in the pre_render phase.
* The CAPTCHA description should not show up either.
*
* \see testCaptchaSessionReuseOnNodeForms()
*/
function testCaptchaDescriptionAfterCommentPreview() {
// Set Test CAPTCHA on comment form.
captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');
// Log in as normal user.
$this
->drupalLogin($this->normal_user);
// Create a node with comments enabled.
$node = $this
->createNodeWithCommentsEnabled();
// Preview comment with correct CAPTCHA answer.
$edit = $this
->getCommentFormValues();
$edit['captcha_response'] = 'Test 123';
$this
->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
// Check that there is no CAPTCHA after preview.
$this
->assertCaptchaPresence(FALSE);
}
/**
* Test if the CAPTCHA session ID is reused when previewing nodes:
* node preview after correct response should not show CAPTCHA anymore.
* The preview functionality of comments and nodes works slightly different under the hood.
* CAPTCHA module should be able to handle both.
*
* \see testCaptchaDescriptionAfterCommentPreview()
*/
function testCaptchaSessionReuseOnNodeForms() {
// Set Test CAPTCHA on page form.
captcha_set_form_id_setting('page_node_form', 'captcha/Test');
// Log in as normal user.
$this
->drupalLogin($this->normal_user);
// Page settings to post, with correct CAPTCHA answer.
$edit = $this
->getNodeFormValues();
$edit['captcha_response'] = 'Test 123';
// Preview the node
$this
->drupalPost('node/add/page', $edit, t('Preview'));
// Check that there is no CAPTCHA after preview.
$this
->assertCaptchaPresence(FALSE);
}
/**
* CAPTCHA should also be put on admin pages even if visitor
* has no access
*/
function testCaptchaOnLoginBlockOnAdminPagesIssue893810() {
// Set a CAPTCHA on login block form
captcha_set_form_id_setting('user_login_block', 'captcha/Math');
// Check if there is a CAPTCHA on home page.
$this
->drupalGet('node');
$this
->assertCaptchaPresence(TRUE);
// Check there is a CAPTCHA on "forbidden" admin pages
$this
->drupalGet('admin');
$this
->assertCaptchaPresence(TRUE);
}
}
class CaptchaAdminTestCase extends CaptchaBaseWebTestCase {
public static function getInfo() {
return array(
'name' => t('CAPTCHA administration functionality'),
'description' => t('Testing of the CAPTCHA administration interface and functionality.'),
'group' => t('CAPTCHA'),
);
}
/**
* Test access to the admin pages.
*/
function testAdminAccess() {
$this
->drupalLogin($this->normal_user);
$this
->drupalGet(self::CAPTCHA_ADMIN_PATH);
$this
->assertText(t('Access denied'), 'Normal users should not be able to access the CAPTCHA admin pages', 'CAPTCHA');
$this
->drupalLogin($this->admin_user);
$this
->drupalGet(self::CAPTCHA_ADMIN_PATH);
$this
->assertNoText(t('Access denied'), 'Admin users should be able to access the CAPTCHA admin pages', 'CAPTCHA');
}
/**
* Test the CAPTCHA point setting getter/setter.
*/
function testCaptchaPointSettingGetterAndSetter() {
$comment_form_id = self::COMMENT_FORM_ID;
// Set to 'none'.
captcha_set_form_id_setting($comment_form_id, 'none');
$result = captcha_get_form_id_setting($comment_form_id);
$this
->assertNotNull($result, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
$this
->assertNull($result->module, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
$this
->assertNull($result->captcha_type, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
$result = captcha_get_form_id_setting($comment_form_id, TRUE);
$this
->assertEqual($result, 'none', 'Setting and symbolic getting CAPTCHA point: "none"', 'CAPTCHA');
// Set to 'default'
captcha_set_form_id_setting($comment_form_id, 'default');
variable_set('captcha_default_challenge', 'foo/bar');
$result = captcha_get_form_id_setting($comment_form_id);
$this
->assertNotNull($result, 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
$this
->assertEqual($result->module, 'foo', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
$this
->assertEqual($result->captcha_type, 'bar', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
$result = captcha_get_form_id_setting($comment_form_id, TRUE);
$this
->assertEqual($result, 'default', 'Setting and symbolic getting CAPTCHA point: "default"', 'CAPTCHA');
// Set to 'baz/boo'.
captcha_set_form_id_setting($comment_form_id, 'baz/boo');
$result = captcha_get_form_id_setting($comment_form_id);
$this
->assertNotNull($result, 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
$this
->assertEqual($result->module, 'baz', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
$this
->assertEqual($result->captcha_type, 'boo', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
$result = captcha_get_form_id_setting($comment_form_id, TRUE);
$this
->assertEqual($result, 'baz/boo', 'Setting and symbolic getting CAPTCHA point: "baz/boo"', 'CAPTCHA');
// Set to NULL (which should delete the CAPTCHA point setting entry).
captcha_set_form_id_setting($comment_form_id, NULL);
$result = captcha_get_form_id_setting($comment_form_id);
$this
->assertNull($result, 'Setting and getting CAPTCHA point: NULL', 'CAPTCHA');
$result = captcha_get_form_id_setting($comment_form_id, TRUE);
$this
->assertNull($result, 'Setting and symbolic getting CAPTCHA point: NULL', 'CAPTCHA');
// Set with object.
$captcha_type = new stdClass();
$captcha_type->module = 'baba';
$captcha_type->captcha_type = 'fofo';
captcha_set_form_id_setting($comment_form_id, $captcha_type);
$result = captcha_get_form_id_setting($comment_form_id);
$this
->assertNotNull($result, 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
$this
->assertEqual($result->module, 'baba', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
$this
->assertEqual($result->captcha_type, 'fofo', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
$result = captcha_get_form_id_setting($comment_form_id, TRUE);
$this
->assertEqual($result, 'baba/fofo', 'Setting and symbolic getting CAPTCHA point: "baba/fofo"', 'CAPTCHA');
}
/**
* Helper function for checking CAPTCHA setting of a form.
*
* @param $form_id the form_id of the form to investigate.
* @param $challenge_type what the challenge type should be:
* NULL, 'none', 'default' or something like 'captcha/Math'
*/
protected function assertCaptchaSetting($form_id, $challenge_type) {
$result = captcha_get_form_id_setting(self::COMMENT_FORM_ID, TRUE);
$this
->assertEqual($result, $challenge_type, t('Check CAPTCHA setting for form: expected: @expected, received: @received.', array(
'@expected' => var_export($challenge_type, TRUE),
'@received' => var_export($result, TRUE),
)), 'CAPTCHA');
}
/**
* Testing of the CAPTCHA administration links.
*/
function testCaptchAdminLinks() {
// Log in as admin
$this
->drupalLogin($this->admin_user);
// Enable CAPTCHA administration links.
$edit = array(
'captcha_administration_mode' => TRUE,
);
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
// Create a node with comments enabled.
$node = $this
->createNodeWithCommentsEnabled();
// Go to node page
$this
->drupalGet('node/' . $node->nid);
// Click the add new comment link
$this
->clickLink(t('Add new comment'));
$add_comment_url = $this
->getUrl();
// Remove fragment part from comment URL to avoid problems with later asserts
$add_comment_url = strtok($add_comment_url, "#");
////////////////////////////////////////////////////////////
// Click the CAPTCHA admin link to enable a challenge.
$this
->clickLink(t('Place a CAPTCHA here for untrusted users.'));
// Enable Math CAPTCHA.
$edit = array(
'captcha_type' => 'captcha/Math',
);
$this
->drupalPost($this
->getUrl(), $edit, t('Save'));
// Check if returned to original comment form.
$this
->assertUrl($add_comment_url, array(), 'After setting CAPTCHA with CAPTCHA admin links: should return to original form.', 'CAPTCHA');
// Check if CAPTCHA was successfully enabled (on CAPTCHA admin links fieldset).
$this
->assertText(t('CAPTCHA: challenge "@type" enabled', array(
'@type' => 'Math',
)), 'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
// Check if CAPTCHA was successfully enabled (through API).
$this
->assertCaptchaSetting(self::COMMENT_FORM_ID, 'captcha/Math');
//////////////////////////////////////////////////////
// Edit challenge type through CAPTCHA admin links.
$this
->clickLink(t('change'));
// Enable Math CAPTCHA.
$edit = array(
'captcha_type' => 'default',
);
$this
->drupalPost($this
->getUrl(), $edit, t('Save'));
// Check if returned to original comment form.
$this
->assertEqual($add_comment_url, $this
->getUrl(), 'After editing challenge type CAPTCHA admin links: should return to original form.', 'CAPTCHA');
// Check if CAPTCHA was successfully changed (on CAPTCHA admin links fieldset).
// This is actually the same as the previous setting because the captcha/Math is the
// default for the default challenge. TODO Make sure the edit is a real change.
$this
->assertText(t('CAPTCHA: challenge "@type" enabled', array(
'@type' => 'Math',
)), 'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
// Check if CAPTCHA was successfully edited (through API).
$this
->assertCaptchaSetting(self::COMMENT_FORM_ID, 'default');
//////////////////////////////////////////////////////
// Disable challenge through CAPTCHA admin links.
$this
->clickLink(t('disable'));
// And confirm.
$this
->drupalPost($this
->getUrl(), array(), 'Disable');
// Check if returned to original comment form.
$this
->assertEqual($add_comment_url, $this
->getUrl(), 'After disablin challenge with CAPTCHA admin links: should return to original form.', 'CAPTCHA');
// Check if CAPTCHA was successfully disabled (on CAPTCHA admin links fieldset).
$this
->assertText(t('CAPTCHA: no challenge enabled'), 'Disable challenge through the CAPTCHA admin links', 'CAPTCHA');
// Check if CAPTCHA was successfully disabled (through API).
$this
->assertCaptchaSetting(self::COMMENT_FORM_ID, 'none');
}
function testUntrustedUserPosting() {
// Set CAPTCHA on comment form.
captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Math');
// Create a node with comments enabled.
$node = $this
->createNodeWithCommentsEnabled();
// Log in as normal (untrusted) user.
$this
->drupalLogin($this->normal_user);
// Go to node page and click the "add comment" link.
$this
->drupalGet('node/' . $node->nid);
$this
->clickLink(t('Add new comment'));
$add_comment_url = $this
->getUrl();
// Check if CAPTCHA is visible on form.
$this
->assertCaptchaPresence(TRUE);
// Try to post a comment with wrong answer.
$edit = $this
->getCommentFormValues();
$edit['captcha_response'] = 'xx';
$this
->drupalPost($add_comment_url, $edit, t('Preview'));
$this
->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE), 'wrong CAPTCHA should block form submission.', 'CAPTCHA');
//TODO: more testing for untrusted posts.
}
/**
* Test XSS vulnerability on CAPTCHA description.
*/
function testXssOnCaptchaDescription() {
// Set CAPTCHA on user register form.
captcha_set_form_id_setting('user_register', 'captcha/Math');
// Put JavaScript snippet in CAPTCHA description.
$this
->drupalLogin($this->admin_user);
$xss = '<script type="text/javascript">alert("xss")</script>';
$edit = array(
'captcha_description' => $xss,
);
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
// Visit user register form and check if JavaScript snippet is there.
$this
->drupalLogout();
$this
->drupalGet('user/register');
$this
->assertNoRaw($xss, 'JavaScript should not be allowed in CAPTCHA description.', 'CAPTCHA');
}
/**
* Test the CAPTCHA placement clearing.
*/
function testCaptchaPlacementCacheClearing() {
// Set CAPTCHA on user register form.
captcha_set_form_id_setting('user_register_form', 'captcha/Math');
// Visit user register form to fill the CAPTCHA placement cache.
$this
->drupalGet('user/register');
// Check if there is CAPTCHA placement cache.
$placement_map = variable_get('captcha_placement_map_cache', NULL);
$this
->assertNotNull($placement_map, 'CAPTCHA placement cache should be set.');
// Clear the cache
$this
->drupalLogin($this->admin_user);
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH, array(), t('Clear the CAPTCHA placement cache'));
// Check that the placement cache is unset
$placement_map = variable_get('captcha_placement_map_cache', NULL);
$this
->assertNull($placement_map, 'CAPTCHA placement cache should be unset after cache clear.');
}
/**
* Helper function to get the CAPTCHA point setting straight from the database.
* @param string $form_id
* @return stdClass object
*/
private function getCaptchaPointSettingFromDatabase($form_id) {
$result = db_query("SELECT * FROM {captcha_points} WHERE form_id = :form_id", array(
':form_id' => $form_id,
))
->fetchObject();
return $result;
}
/**
* Method for testing the CAPTCHA point administration
*/
function testCaptchaPointAdministration() {
// Generate CAPTCHA point data:
// Drupal form ID should consist of lowercase alphanumerics and underscore)
$captcha_point_form_id = 'form_' . strtolower($this
->randomName(32));
// the Math CAPTCHA by the CAPTCHA module is always available, so let's use it
$captcha_point_module = 'captcha';
$captcha_point_type = 'Math';
// Log in as admin
$this
->drupalLogin($this->admin_user);
// Set CAPTCHA point through admin/user/captcha/captcha/captcha_point
$form_values = array(
'captcha_point_form_id' => $captcha_point_form_id,
'captcha_type' => $captcha_point_module . '/' . $captcha_point_type,
);
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point', $form_values, t('Save'));
$this
->assertText(t('Saved CAPTCHA point settings.'), 'Saving of CAPTCHA point settings');
// Check in database
$result = $this
->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
$this
->assertEqual($result->module, $captcha_point_module, 'Enabled CAPTCHA point should have module set');
$this
->assertEqual($result->captcha_type, $captcha_point_type, 'Enabled CAPTCHA point should have type set');
// Disable CAPTCHA point again
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . $captcha_point_form_id . '/disable', array(), t('Disable'));
$this
->assertRaw(t('Disabled CAPTCHA for form %form_id.', array(
'%form_id' => $captcha_point_form_id,
)), 'Disabling of CAPTCHA point');
// Check in database
$result = $this
->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
$this
->assertNull($result->module, 'Disabled CAPTCHA point should have NULL as module');
$this
->assertNull($result->captcha_type, 'Disabled CAPTCHA point should have NULL as type');
// Set CAPTCHA point through admin/user/captcha/captcha/captcha_point/$form_id
$form_values = array(
'captcha_type' => $captcha_point_module . '/' . $captcha_point_type,
);
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . $captcha_point_form_id, $form_values, t('Save'));
$this
->assertText(t('Saved CAPTCHA point settings.'), 'Saving of CAPTCHA point settings');
// Check in database
$result = $this
->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
$this
->assertEqual($result->module, $captcha_point_module, 'Enabled CAPTCHA point should have module set');
$this
->assertEqual($result->captcha_type, $captcha_point_type, 'Enabled CAPTCHA point should have type set');
// Delete CAPTCHA point
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . $captcha_point_form_id . '/delete', array(), t('Delete'));
$this
->assertRaw(t('Deleted CAPTCHA for form %form_id.', array(
'%form_id' => $captcha_point_form_id,
)), 'Deleting of CAPTCHA point');
// Check in database
$result = $this
->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
$this
->assertFalse($result, 'Deleted CAPTCHA point should be in database');
}
/**
* Method for testing the CAPTCHA point administration
*/
function testCaptchaPointAdministrationByNonAdmin() {
// First add a CAPTCHA point (as admin)
$this
->drupalLogin($this->admin_user);
$captcha_point_form_id = 'form_' . strtolower($this
->randomName(32));
$captcha_point_module = 'captcha';
$captcha_point_type = 'Math';
$form_values = array(
'captcha_point_form_id' => $captcha_point_form_id,
'captcha_type' => $captcha_point_module . '/' . $captcha_point_type,
);
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/', $form_values, t('Save'));
$this
->assertText(t('Saved CAPTCHA point settings.'), 'Saving of CAPTCHA point settings');
// Switch from admin to nonadmin
$this
->drupalGet(url('logout', array(
'absolute' => TRUE,
)));
$this
->drupalLogin($this->normal_user);
// Try to set CAPTCHA point through admin/user/captcha/captcha/captcha_point
$this
->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point');
$this
->assertText(t('You are not authorized to access this page.'), 'Non admin should not be able to set a CAPTCHA point');
// Try to set CAPTCHA point through admin/user/captcha/captcha/captcha_point/$form_id
$this
->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . 'form_' . strtolower($this
->randomName(32)));
$this
->assertText(t('You are not authorized to access this page.'), 'Non admin should not be able to set a CAPTCHA point');
// Try to disable the CAPTCHA point
$this
->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . $captcha_point_form_id . '/disable');
$this
->assertText(t('You are not authorized to access this page.'), 'Non admin should not be able to disable a CAPTCHA point');
// Try to delete the CAPTCHA point
$this
->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . $captcha_point_form_id . '/delete');
$this
->assertText(t('You are not authorized to access this page.'), 'Non admin should not be able to delete a CAPTCHA point');
// Switch from nonadmin to admin again
$this
->drupalGet(url('logout', array(
'absolute' => TRUE,
)));
$this
->drupalLogin($this->admin_user);
// Check if original CAPTCHA point still exists in database
$result = $this
->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
$this
->assertEqual($result->module, $captcha_point_module, 'Enabled CAPTCHA point should still have module set');
$this
->assertEqual($result->captcha_type, $captcha_point_type, 'Enabled CAPTCHA point should still have type set');
// Delete CAPTCHA point
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . $captcha_point_form_id . '/delete', array(), t('Delete'));
$this
->assertRaw(t('Deleted CAPTCHA for form %form_id.', array(
'%form_id' => $captcha_point_form_id,
)), 'Deleting of CAPTCHA point');
}
}
class CaptchaPersistenceTestCase extends CaptchaBaseWebTestCase {
public static function getInfo() {
return array(
'name' => t('CAPTCHA persistence functionality'),
'description' => t('Testing of the CAPTCHA persistence functionality.'),
'group' => t('CAPTCHA'),
);
}
/**
* Set up the persistence and CAPTCHA settings.
* @param int $persistence the persistence value.
*/
private function setUpPersistence($persistence) {
// Log in as admin
$this
->drupalLogin($this->admin_user);
// Set persistence.
$edit = array(
'captcha_persistence' => $persistence,
);
$this
->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
// Log admin out.
$this
->drupalLogout();
// Set the Test123 CAPTCHA on user register and comment form.
// We have to do this with the function captcha_set_form_id_setting()
// (because the CATCHA admin form does not show the Test123 option).
// We also have to do this after all usage of the CAPTCHA admin form
// (because posting the CAPTCHA admin form would set the CAPTCHA to 'none').
captcha_set_form_id_setting('user_login', 'captcha/Test');
$this
->drupalGet('user');
$this
->assertCaptchaPresence(TRUE);
captcha_set_form_id_setting('user_register_form', 'captcha/Test');
$this
->drupalGet('user/register');
$this
->assertCaptchaPresence(TRUE);
}
protected function assertPreservedCsid($captcha_sid_initial) {
$captcha_sid = $this
->getCaptchaSidFromForm();
$this
->assertEqual($captcha_sid_initial, $captcha_sid, "CAPTCHA session ID should be preserved (expected: {$captcha_sid_initial}, found: {$captcha_sid}).");
}
protected function assertDifferentCsid($captcha_sid_initial) {
$captcha_sid = $this
->getCaptchaSidFromForm();
$this
->assertNotEqual($captcha_sid_initial, $captcha_sid, "CAPTCHA session ID should be different.");
}
function testPersistenceAlways() {
// Set up of persistence and CAPTCHAs.
$this
->setUpPersistence(CAPTCHA_PERSISTENCE_SHOW_ALWAYS);
// Go to login form and check if there is a CAPTCHA on the login form (look for the title).
$this
->drupalGet('user');
$this
->assertCaptchaPresence(TRUE);
$captcha_sid_initial = $this
->getCaptchaSidFromForm();
// Try to with wrong user name and password, but correct CAPTCHA.
$edit = array(
'name' => 'foobar',
'pass' => 'bazlaz',
'captcha_response' => 'Test 123',
);
$this
->drupalPost(NULL, $edit, t('Log in'));
// Check that there was no error message for the CAPTCHA.
$this
->assertCaptchaResponseAccepted();
// Name and password were wrong, we should get an updated form with a fresh CAPTCHA.
$this
->assertCaptchaPresence(TRUE);
$this
->assertPreservedCsid($captcha_sid_initial);
// Post from again.
$this
->drupalPost(NULL, $edit, t('Log in'));
// Check that there was no error message for the CAPTCHA.
$this
->assertCaptchaResponseAccepted();
$this
->assertPreservedCsid($captcha_sid_initial);
}
function testPersistencePerFormInstance() {
// Set up of persistence and CAPTCHAs.
$this
->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
// Go to login form and check if there is a CAPTCHA on the login form.
$this
->drupalGet('user');
$this
->assertCaptchaPresence(TRUE);
$captcha_sid_initial = $this
->getCaptchaSidFromForm();
// Try to with wrong user name and password, but correct CAPTCHA.
$edit = array(
'name' => 'foobar',
'pass' => 'bazlaz',
'captcha_response' => 'Test 123',
);
$this
->drupalPost(NULL, $edit, t('Log in'));
// Check that there was no error message for the CAPTCHA.
$this
->assertCaptchaResponseAccepted();
// There shouldn't be a CAPTCHA on the new form.
$this
->assertCaptchaPresence(FALSE);
$this
->assertPreservedCsid($captcha_sid_initial);
// Start a new form instance/session
$this
->drupalGet('node');
$this
->drupalGet('user');
$this
->assertCaptchaPresence(TRUE);
$this
->assertDifferentCsid($captcha_sid_initial);
// Check another form
$this
->drupalGet('user/register');
$this
->assertCaptchaPresence(TRUE);
$this
->assertDifferentCsid($captcha_sid_initial);
}
function testPersistencePerFormType() {
// Set up of persistence and CAPTCHAs.
$this
->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_TYPE);
// Go to login form and check if there is a CAPTCHA on the login form.
$this
->drupalGet('user');
$this
->assertCaptchaPresence(TRUE);
$captcha_sid_initial = $this
->getCaptchaSidFromForm();
// Try to with wrong user name and password, but correct CAPTCHA.
$edit = array(
'name' => 'foobar',
'pass' => 'bazlaz',
'captcha_response' => 'Test 123',
);
$this
->drupalPost(NULL, $edit, t('Log in'));
// Check that there was no error message for the CAPTCHA.
$this
->assertCaptchaResponseAccepted();
// There shouldn't be a CAPTCHA on the new form.
$this
->assertCaptchaPresence(FALSE);
$this
->assertPreservedCsid($captcha_sid_initial);
// Start a new form instance/session
$this
->drupalGet('node');
$this
->drupalGet('user');
$this
->assertCaptchaPresence(FALSE);
$this
->assertDifferentCsid($captcha_sid_initial);
// Check another form
$this
->drupalGet('user/register');
$this
->assertCaptchaPresence(TRUE);
$this
->assertDifferentCsid($captcha_sid_initial);
}
function testPersistenceOnlyOnce() {
// Set up of persistence and CAPTCHAs.
$this
->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL);
// Go to login form and check if there is a CAPTCHA on the login form.
$this
->drupalGet('user');
$this
->assertCaptchaPresence(TRUE);
$captcha_sid_initial = $this
->getCaptchaSidFromForm();
// Try to with wrong user name and password, but correct CAPTCHA.
$edit = array(
'name' => 'foobar',
'pass' => 'bazlaz',
'captcha_response' => 'Test 123',
);
$this
->drupalPost(NULL, $edit, t('Log in'));
// Check that there was no error message for the CAPTCHA.
$this
->assertCaptchaResponseAccepted();
// There shouldn't be a CAPTCHA on the new form.
$this
->assertCaptchaPresence(FALSE);
$this
->assertPreservedCsid($captcha_sid_initial);
// Start a new form instance/session
$this
->drupalGet('node');
$this
->drupalGet('user');
$this
->assertCaptchaPresence(FALSE);
$this
->assertDifferentCsid($captcha_sid_initial);
// Check another form
$this
->drupalGet('user/register');
$this
->assertCaptchaPresence(FALSE);
$this
->assertDifferentCsid($captcha_sid_initial);
}
}
class CaptchaSessionReuseAttackTestCase extends CaptchaBaseWebTestCase {
public static function getInfo() {
return array(
'name' => t('CAPTCHA session reuse attack tests'),
'description' => t('Testing of the protection against CAPTCHA session reuse attacks.'),
'group' => t('CAPTCHA'),
);
}
/**
* Assert that the CAPTCHA session ID reuse attack was detected.
*/
protected function assertCaptchaSessionIdReuseAttackDetection() {
$this
->assertText(t(CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE), 'CAPTCHA session ID reuse attack should be detected.', 'CAPTCHA');
// There should be an error message about wrong response.
$this
->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE), 'CAPTCHA response should flagged as wrong.', 'CAPTCHA');
}
function testCaptchaSessionReuseAttackDetectionOnCommentPreview() {
// Create commentable node
$node = $this
->createNodeWithCommentsEnabled();
// Set Test CAPTCHA on comment form.
captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Math');
variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
// Log in as normal user.
$this
->drupalLogin($this->normal_user);
// Go to comment form of commentable node.
$this
->drupalGet('comment/reply/' . $node->nid);
$this
->assertCaptchaPresence(TRUE);
// Get CAPTCHA session ID and solution of the challenge.
$captcha_sid = $this
->getCaptchaSidFromForm();
$captcha_token = $this
->getCaptchaTokenFromForm();
$solution = $this
->getMathCaptchaSolutionFromForm();
// Post the form with the solution.
$edit = $this
->getCommentFormValues();
$edit['captcha_response'] = $solution;
$this
->drupalPost(NULL, $edit, t('Preview'));
// Answer should be accepted and further CAPTCHA ommitted.
$this
->assertCaptchaResponseAccepted();
$this
->assertCaptchaPresence(FALSE);
// Post a new comment, reusing the previous CAPTCHA session.
$edit = $this
->getCommentFormValues();
$edit['captcha_sid'] = $captcha_sid;
$edit['captcha_token'] = $captcha_token;
$edit['captcha_response'] = $solution;
$this
->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
// CAPTCHA session reuse attack should be detected.
$this
->assertCaptchaSessionIdReuseAttackDetection();
// There should be a CAPTCHA.
$this
->assertCaptchaPresence(TRUE);
}
function testCaptchaSessionReuseAttackDetectionOnNodeForm() {
// Set CAPTCHA on page form.
captcha_set_form_id_setting('page_node_form', 'captcha/Math');
variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
// Log in as normal user.
$this
->drupalLogin($this->normal_user);
// Go to node add form.
$this
->drupalGet('node/add/page');
$this
->assertCaptchaPresence(TRUE);
// Get CAPTCHA session ID and solution of the challenge.
$captcha_sid = $this
->getCaptchaSidFromForm();
$captcha_token = $this
->getCaptchaTokenFromForm();
$solution = $this
->getMathCaptchaSolutionFromForm();
// Page settings to post, with correct CAPTCHA answer.
$edit = $this
->getNodeFormValues();
$edit['captcha_response'] = $solution;
// Preview the node
$this
->drupalPost(NULL, $edit, t('Preview'));
// Answer should be accepted.
$this
->assertCaptchaResponseAccepted();
// Check that there is no CAPTCHA after preview.
$this
->assertCaptchaPresence(FALSE);
// Post a new comment, reusing the previous CAPTCHA session.
$edit = $this
->getNodeFormValues();
$edit['captcha_sid'] = $captcha_sid;
$edit['captcha_token'] = $captcha_token;
$edit['captcha_response'] = $solution;
$this
->drupalPost('node/add/page', $edit, t('Preview'));
// CAPTCHA session reuse attack should be detected.
$this
->assertCaptchaSessionIdReuseAttackDetection();
// There should be a CAPTCHA.
$this
->assertCaptchaPresence(TRUE);
}
function testCaptchaSessionReuseAttackDetectionOnLoginForm() {
// Set CAPTCHA on login form.
captcha_set_form_id_setting('user_login', 'captcha/Math');
variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
// Go to log in form.
$this
->drupalGet('user');
$this
->assertCaptchaPresence(TRUE);
// Get CAPTCHA session ID and solution of the challenge.
$captcha_sid = $this
->getCaptchaSidFromForm();
$captcha_token = $this
->getCaptchaTokenFromForm();
$solution = $this
->getMathCaptchaSolutionFromForm();
// Log in through form.
$edit = array(
'name' => $this->normal_user->name,
'pass' => $this->normal_user->pass_raw,
'captcha_response' => $solution,
);
$this
->drupalPost(NULL, $edit, t('Log in'));
$this
->assertCaptchaResponseAccepted();
$this
->assertCaptchaPresence(FALSE);
// If a "log out" link appears on the page, it is almost certainly because
// the login was successful.
$pass = $this
->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array(
'%name' => $this->normal_user->name,
)), t('User login'));
// Log out again.
$this
->drupalLogout();
// Try to log in again, reusing the previous CAPTCHA session.
$edit += array(
'captcha_sid' => $captcha_sid,
'captcha_token' => $captcha_token,
);
$this
->drupalPost('user', $edit, t('Log in'));
// CAPTCHA session reuse attack should be detected.
$this
->assertCaptchaSessionIdReuseAttackDetection();
// There should be a CAPTCHA.
$this
->assertCaptchaPresence(TRUE);
}
public function testMultipleCaptchaProtectedFormsOnOnePage() {
// Set Test CAPTCHA on comment form and login block
captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');
captcha_set_form_id_setting('user_login_block', 'captcha/Math');
$this
->allowCommentPostingForAnonymousVisitors();
// Create a node with comments enabled.
$node = $this
->createNodeWithCommentsEnabled();
// Preview comment with correct CAPTCHA answer.
$edit = $this
->getCommentFormValues();
$comment_subject = $edit['subject'];
$edit['captcha_response'] = 'Test 123';
$this
->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
// Post should be accepted: no warnings,
// no CAPTCHA reuse detection (which could be used by user log in block).
$this
->assertCaptchaResponseAccepted();
$this
->assertText($comment_subject);
}
}
// Some tricks to debug:
// drupal_debug($data) // from devel module
// file_put_contents('tmp.simpletest.html', $this->drupalGetContent());
Constants
Classes
Name | Description |
---|---|
CaptchaAdminTestCase | |
CaptchaBaseWebTestCase | Base class for CAPTCHA tests. |
CaptchaPersistenceTestCase | |
CaptchaSessionReuseAttackTestCase | |
CaptchaTestCase |