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); } } 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); file_put_contents('tmp.simpletest.html', $this->drupalGetContent()); $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 = ''; $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 flushing. */ function testCaptchaPlacementCacheFlushing() { // 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.'); // Flush the cache $this->drupalLogin($this->admin_user); $this->drupalPost(self::CAPTCHA_ADMIN_PATH, array(), t('Flush 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 flush.'); } /** * 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());