captcha.test 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142
  1. <?php
  2. /**
  3. * @file
  4. * Tests for CAPTCHA module.
  5. */
  6. // TODO: write test for CAPTCHAs on admin pages
  7. // TODO: test for default challenge type
  8. // TODO: test about placement (comment form, node forms, log in form, etc)
  9. // TODO: test if captcha_cron does it work right
  10. // TODO: test custom CAPTCHA validation stuff
  11. // TODO: test if entry on status report (Already X blocked form submissions) works
  12. // TODO: test space ignoring validation of image CAPTCHA
  13. // TODO: refactor the 'comment_body[' . LANGUAGE_NONE . '][0][value]' stuff
  14. // Some constants for better reuse.
  15. define('CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE',
  16. 'The answer you entered for the CAPTCHA was not correct.');
  17. define('CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE',
  18. 'CAPTCHA session reuse attack detected.');
  19. define('CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE',
  20. 'CAPTCHA validation error: unknown CAPTCHA session ID. Contact the site administrator if this problem persists.');
  21. /**
  22. * Base class for CAPTCHA tests.
  23. *
  24. * Provides common setup stuff and various helper functions
  25. */
  26. abstract class CaptchaBaseWebTestCase extends DrupalWebTestCase {
  27. /**
  28. * User with various administrative permissions.
  29. * @var Drupal user
  30. */
  31. protected $admin_user;
  32. /**
  33. * Normal visitor with limited permissions
  34. * @var Drupal user;
  35. */
  36. protected $normal_user;
  37. /**
  38. * Form ID of comment form on standard (page) node
  39. * @var string
  40. */
  41. const COMMENT_FORM_ID = 'comment_node_page_form';
  42. /**
  43. * Drupal path of the (general) CAPTCHA admin page
  44. */
  45. const CAPTCHA_ADMIN_PATH = 'admin/config/people/captcha';
  46. function setUp() {
  47. // Load two modules: the captcha module itself and the comment module for testing anonymous comments.
  48. $modules = func_get_args();
  49. if (isset($modules[0]) && is_array($modules[0])) {
  50. $modules = $modules[0];
  51. }
  52. parent::setUp(array_merge(array('captcha', 'comment'), $modules));
  53. module_load_include('inc', 'captcha');
  54. // Create a normal user.
  55. $permissions = array(
  56. 'access comments', 'post comments', 'skip comment approval',
  57. 'access content', 'create page content', 'edit own page content',
  58. );
  59. $this->normal_user = $this->drupalCreateUser($permissions);
  60. // Create an admin user.
  61. $permissions[] = 'administer CAPTCHA settings';
  62. $permissions[] = 'skip CAPTCHA';
  63. $permissions[] = 'administer permissions';
  64. $permissions[] = 'administer content types';
  65. $this->admin_user = $this->drupalCreateUser($permissions);
  66. // Put comments on page nodes on a separate page (default in D7: below post).
  67. variable_set('comment_form_location_page', COMMENT_FORM_SEPARATE_PAGE);
  68. }
  69. /**
  70. * Assert that the response is accepted:
  71. * no "unknown CSID" message, no "CSID reuse attack detection" message,
  72. * no "wrong answer" message.
  73. */
  74. protected function assertCaptchaResponseAccepted() {
  75. // There should be no error message about unknown CAPTCHA session ID.
  76. $this->assertNoText(t(CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE),
  77. 'CAPTCHA response should be accepted (known CSID).',
  78. 'CAPTCHA');
  79. // There should be no error message about CSID reuse attack.
  80. $this->assertNoText(t(CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE),
  81. 'CAPTCHA response should be accepted (no CAPTCHA session reuse attack detection).',
  82. 'CAPTCHA');
  83. // There should be no error message about wrong response.
  84. $this->assertNoText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
  85. 'CAPTCHA response should be accepted (correct response).',
  86. 'CAPTCHA');
  87. }
  88. /**
  89. * Assert that there is a CAPTCHA on the form or not.
  90. * @param bool $presence whether there should be a CAPTCHA or not.
  91. */
  92. protected function assertCaptchaPresence($presence) {
  93. if ($presence) {
  94. $this->assertText(_captcha_get_description(),
  95. 'There should be a CAPTCHA on the form.', 'CAPTCHA');
  96. }
  97. else {
  98. $this->assertNoText(_captcha_get_description(),
  99. 'There should be no CAPTCHA on the form.', 'CAPTCHA');
  100. }
  101. }
  102. /**
  103. * Helper function to create a node with comments enabled.
  104. *
  105. * @return
  106. * Created node object.
  107. */
  108. protected function createNodeWithCommentsEnabled($type='page') {
  109. $node_settings = array(
  110. 'type' => $type,
  111. 'comment' => COMMENT_NODE_OPEN,
  112. );
  113. $node = $this->drupalCreateNode($node_settings);
  114. return $node;
  115. }
  116. /**
  117. * Helper function to generate a form values array for comment forms
  118. */
  119. protected function getCommentFormValues() {
  120. $edit = array(
  121. 'subject' => 'comment_subject ' . $this->randomName(32),
  122. 'comment_body[' . LANGUAGE_NONE . '][0][value]' => 'comment_body ' . $this->randomName(256),
  123. );
  124. return $edit;
  125. }
  126. /**
  127. * Helper function to generate a form values array for node forms
  128. */
  129. protected function getNodeFormValues() {
  130. $edit = array(
  131. 'title' => 'node_title ' . $this->randomName(32),
  132. 'body[' . LANGUAGE_NONE . '][0][value]' => 'node_body ' . $this->randomName(256),
  133. );
  134. return $edit;
  135. }
  136. /**
  137. * Get the CAPTCHA session id from the current form in the browser.
  138. */
  139. protected function getCaptchaSidFromForm() {
  140. $elements = $this->xpath('//input[@name="captcha_sid"]');
  141. $captcha_sid = (int) $elements[0]['value'];
  142. return $captcha_sid;
  143. }
  144. /**
  145. * Get the CAPTCHA token from the current form in the browser.
  146. */
  147. protected function getCaptchaTokenFromForm() {
  148. $elements = $this->xpath('//input[@name="captcha_token"]');
  149. $captcha_token = (int) $elements[0]['value'];
  150. return $captcha_token;
  151. }
  152. /**
  153. * Get the solution of the math CAPTCHA from the current form in the browser.
  154. */
  155. protected function getMathCaptchaSolutionFromForm() {
  156. // Get the math challenge.
  157. $elements = $this->xpath('//div[@class="form-item form-type-textfield form-item-captcha-response"]/span[@class="field-prefix"]');
  158. $challenge = (string) $elements[0];
  159. // Extract terms and operator from challenge.
  160. $matches = array();
  161. $ret = preg_match('/\\s*(\\d+)\\s*(-|\\+)\\s*(\\d+)\\s*=\\s*/', $challenge, $matches);
  162. // Solve the challenge
  163. $a = (int) $matches[1];
  164. $b = (int) $matches[3];
  165. $solution = $matches[2] == '-' ? $a - $b : $a + $b;
  166. return $solution;
  167. }
  168. /**
  169. * Helper function to allow comment posting for anonymous users.
  170. */
  171. protected function allowCommentPostingForAnonymousVisitors() {
  172. // Log in as admin.
  173. $this->drupalLogin($this->admin_user);
  174. // Post user permissions form
  175. $edit = array(
  176. '1[access comments]' => true,
  177. '1[post comments]' => true,
  178. '1[skip comment approval]' => true,
  179. );
  180. $this->drupalPost('admin/people/permissions', $edit, 'Save permissions');
  181. $this->assertText('The changes have been saved.');
  182. // Log admin out
  183. $this->drupalLogout();
  184. }
  185. }
  186. class CaptchaTestCase extends CaptchaBaseWebTestCase {
  187. public static function getInfo() {
  188. return array(
  189. 'name' => t('General CAPTCHA functionality'),
  190. 'description' => t('Testing of the basic CAPTCHA functionality.'),
  191. 'group' => t('CAPTCHA'),
  192. );
  193. }
  194. /**
  195. * Testing the protection of the user log in form.
  196. */
  197. function testCaptchaOnLoginForm() {
  198. // Create user and test log in without CAPTCHA.
  199. $user = $this->drupalCreateUser();
  200. $this->drupalLogin($user);
  201. // Log out again.
  202. $this->drupalLogout();
  203. // Set a CAPTCHA on login form
  204. captcha_set_form_id_setting('user_login', 'captcha/Math');
  205. // Check if there is a CAPTCHA on the login form (look for the title).
  206. $this->drupalGet('user');
  207. $this->assertCaptchaPresence(TRUE);
  208. // Try to log in, which should fail.
  209. $edit = array(
  210. 'name' => $user->name,
  211. 'pass' => $user->pass_raw,
  212. 'captcha_response' => '?',
  213. );
  214. $this->drupalPost('user', $edit, t('Log in'));
  215. // Check for error message.
  216. $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
  217. 'CAPTCHA should block user login form', 'CAPTCHA');
  218. // And make sure that user is not logged in: check for name and password fields on ?q=user
  219. $this->drupalGet('user');
  220. $this->assertField('name', t('Username field found.'), 'CAPTCHA');
  221. $this->assertField('pass', t('Password field found.'), 'CAPTCHA');
  222. }
  223. /**
  224. * Assert function for testing if comment posting works as it should.
  225. *
  226. * Creates node with comment writing enabled, tries to post comment
  227. * with given CAPTCHA response (caller should enable the desired
  228. * challenge on page node comment forms) and checks if the result is as expected.
  229. *
  230. * @param $captcha_response the response on the CAPTCHA
  231. * @param $should_pass boolean describing if the posting should pass or should be blocked
  232. * @param $message message to prefix to nested asserts
  233. */
  234. protected function assertCommentPosting($captcha_response, $should_pass, $message) {
  235. // Make sure comments on pages can be saved directely without preview.
  236. variable_set('comment_preview_page', DRUPAL_OPTIONAL);
  237. // Create a node with comments enabled.
  238. $node = $this->createNodeWithCommentsEnabled();
  239. // Post comment on node.
  240. $edit = $this->getCommentFormValues();
  241. $comment_subject = $edit['subject'];
  242. $comment_body = $edit['comment_body[' . LANGUAGE_NONE . '][0][value]'];
  243. $edit['captcha_response'] = $captcha_response;
  244. $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Save'));
  245. if ($should_pass) {
  246. // There should be no error message.
  247. $this->assertCaptchaResponseAccepted();
  248. // Get node page and check that comment shows up.
  249. $this->drupalGet('node/' . $node->nid);
  250. $this->assertText($comment_subject, $message .' Comment should show up on node page.', 'CAPTCHA');
  251. $this->assertText($comment_body, $message . ' Comment should show up on node page.', 'CAPTCHA');
  252. }
  253. else {
  254. // Check for error message.
  255. $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE), $message .' Comment submission should be blocked.', 'CAPTCHA');
  256. // Get node page and check that comment is not present.
  257. $this->drupalGet('node/' . $node->nid);
  258. $this->assertNoText($comment_subject, $message .' Comment should not show up on node page.', 'CAPTCHA');
  259. $this->assertNoText($comment_body, $message . ' Comment should not show up on node page.', 'CAPTCHA');
  260. }
  261. }
  262. /*
  263. * Testing the case sensistive/insensitive validation.
  264. */
  265. function testCaseInsensitiveValidation() {
  266. // Set Test CAPTCHA on comment form
  267. captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');
  268. // Log in as normal user.
  269. $this->drupalLogin($this->normal_user);
  270. // Test case sensitive posting.
  271. variable_set('captcha_default_validation', CAPTCHA_DEFAULT_VALIDATION_CASE_SENSITIVE);
  272. $this->assertCommentPosting('Test 123', TRUE, 'Case sensitive validation of right casing.');
  273. $this->assertCommentPosting('test 123', FALSE, 'Case sensitive validation of wrong casing.');
  274. $this->assertCommentPosting('TEST 123', FALSE, 'Case sensitive validation of wrong casing.');
  275. // Test case insensitive posting (the default)
  276. variable_set('captcha_default_validation', CAPTCHA_DEFAULT_VALIDATION_CASE_INSENSITIVE);
  277. $this->assertCommentPosting('Test 123', TRUE, 'Case insensitive validation of right casing.');
  278. $this->assertCommentPosting('test 123', TRUE, 'Case insensitive validation of wrong casing.');
  279. $this->assertCommentPosting('TEST 123', TRUE, 'Case insensitive validation of wrong casing.');
  280. }
  281. /**
  282. * Test if the CAPTCHA description is only shown if there are challenge widgets to show.
  283. * For example, when a comment is previewed with correct CAPTCHA answer,
  284. * a challenge is generated and added to the form but removed in the pre_render phase.
  285. * The CAPTCHA description should not show up either.
  286. *
  287. * \see testCaptchaSessionReuseOnNodeForms()
  288. */
  289. function testCaptchaDescriptionAfterCommentPreview() {
  290. // Set Test CAPTCHA on comment form.
  291. captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');
  292. // Log in as normal user.
  293. $this->drupalLogin($this->normal_user);
  294. // Create a node with comments enabled.
  295. $node = $this->createNodeWithCommentsEnabled();
  296. // Preview comment with correct CAPTCHA answer.
  297. $edit = $this->getCommentFormValues();
  298. $edit['captcha_response'] = 'Test 123';
  299. $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
  300. // Check that there is no CAPTCHA after preview.
  301. $this->assertCaptchaPresence(FALSE);
  302. }
  303. /**
  304. * Test if the CAPTCHA session ID is reused when previewing nodes:
  305. * node preview after correct response should not show CAPTCHA anymore.
  306. * The preview functionality of comments and nodes works slightly different under the hood.
  307. * CAPTCHA module should be able to handle both.
  308. *
  309. * \see testCaptchaDescriptionAfterCommentPreview()
  310. */
  311. function testCaptchaSessionReuseOnNodeForms() {
  312. // Set Test CAPTCHA on page form.
  313. captcha_set_form_id_setting('page_node_form', 'captcha/Test');
  314. // Log in as normal user.
  315. $this->drupalLogin($this->normal_user);
  316. // Page settings to post, with correct CAPTCHA answer.
  317. $edit = $this->getNodeFormValues();
  318. $edit['captcha_response'] = 'Test 123';
  319. // Preview the node
  320. $this->drupalPost('node/add/page', $edit, t('Preview'));
  321. // Check that there is no CAPTCHA after preview.
  322. $this->assertCaptchaPresence(FALSE);
  323. }
  324. /**
  325. * CAPTCHA should also be put on admin pages even if visitor
  326. * has no access
  327. */
  328. function testCaptchaOnLoginBlockOnAdminPagesIssue893810() {
  329. // Set a CAPTCHA on login block form
  330. captcha_set_form_id_setting('user_login_block', 'captcha/Math');
  331. // Check if there is a CAPTCHA on home page.
  332. $this->drupalGet('node');
  333. $this->assertCaptchaPresence(TRUE);
  334. // Check there is a CAPTCHA on "forbidden" admin pages
  335. $this->drupalGet('admin');
  336. $this->assertCaptchaPresence(TRUE);
  337. }
  338. }
  339. class CaptchaAdminTestCase extends CaptchaBaseWebTestCase {
  340. public static function getInfo() {
  341. return array(
  342. 'name' => t('CAPTCHA administration functionality'),
  343. 'description' => t('Testing of the CAPTCHA administration interface and functionality.'),
  344. 'group' => t('CAPTCHA'),
  345. );
  346. }
  347. /**
  348. * Test access to the admin pages.
  349. */
  350. function testAdminAccess() {
  351. $this->drupalLogin($this->normal_user);
  352. $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
  353. $this->assertText(t('Access denied'), 'Normal users should not be able to access the CAPTCHA admin pages', 'CAPTCHA');
  354. $this->drupalLogin($this->admin_user);
  355. $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
  356. $this->assertNoText(t('Access denied'), 'Admin users should be able to access the CAPTCHA admin pages', 'CAPTCHA');
  357. }
  358. /**
  359. * Test the CAPTCHA point setting getter/setter.
  360. */
  361. function testCaptchaPointSettingGetterAndSetter() {
  362. $comment_form_id = self::COMMENT_FORM_ID;
  363. // Set to 'none'.
  364. captcha_set_form_id_setting($comment_form_id, 'none');
  365. $result = captcha_get_form_id_setting($comment_form_id);
  366. $this->assertNotNull($result, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
  367. $this->assertNull($result->module, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
  368. $this->assertNull($result->captcha_type, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
  369. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  370. $this->assertEqual($result, 'none', 'Setting and symbolic getting CAPTCHA point: "none"', 'CAPTCHA');
  371. // Set to 'default'
  372. captcha_set_form_id_setting($comment_form_id, 'default');
  373. variable_set('captcha_default_challenge', 'foo/bar');
  374. $result = captcha_get_form_id_setting($comment_form_id);
  375. $this->assertNotNull($result, 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
  376. $this->assertEqual($result->module, 'foo', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
  377. $this->assertEqual($result->captcha_type, 'bar', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
  378. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  379. $this->assertEqual($result, 'default', 'Setting and symbolic getting CAPTCHA point: "default"', 'CAPTCHA');
  380. // Set to 'baz/boo'.
  381. captcha_set_form_id_setting($comment_form_id, 'baz/boo');
  382. $result = captcha_get_form_id_setting($comment_form_id);
  383. $this->assertNotNull($result, 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
  384. $this->assertEqual($result->module, 'baz', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
  385. $this->assertEqual($result->captcha_type, 'boo', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
  386. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  387. $this->assertEqual($result, 'baz/boo', 'Setting and symbolic getting CAPTCHA point: "baz/boo"', 'CAPTCHA');
  388. // Set to NULL (which should delete the CAPTCHA point setting entry).
  389. captcha_set_form_id_setting($comment_form_id, NULL);
  390. $result = captcha_get_form_id_setting($comment_form_id);
  391. $this->assertNull($result, 'Setting and getting CAPTCHA point: NULL', 'CAPTCHA');
  392. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  393. $this->assertNull($result, 'Setting and symbolic getting CAPTCHA point: NULL', 'CAPTCHA');
  394. // Set with object.
  395. $captcha_type = new stdClass;
  396. $captcha_type->module = 'baba';
  397. $captcha_type->captcha_type = 'fofo';
  398. captcha_set_form_id_setting($comment_form_id, $captcha_type);
  399. $result = captcha_get_form_id_setting($comment_form_id);
  400. $this->assertNotNull($result, 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
  401. $this->assertEqual($result->module, 'baba', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
  402. $this->assertEqual($result->captcha_type, 'fofo', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
  403. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  404. $this->assertEqual($result, 'baba/fofo', 'Setting and symbolic getting CAPTCHA point: "baba/fofo"', 'CAPTCHA');
  405. }
  406. /**
  407. * Helper function for checking CAPTCHA setting of a form.
  408. *
  409. * @param $form_id the form_id of the form to investigate.
  410. * @param $challenge_type what the challenge type should be:
  411. * NULL, 'none', 'default' or something like 'captcha/Math'
  412. */
  413. protected function assertCaptchaSetting($form_id, $challenge_type) {
  414. $result = captcha_get_form_id_setting(self::COMMENT_FORM_ID, TRUE);
  415. $this->assertEqual($result, $challenge_type,
  416. t('Check CAPTCHA setting for form: expected: @expected, received: @received.',
  417. array('@expected' => var_export($challenge_type, TRUE), '@received' => var_export($result, TRUE))),
  418. 'CAPTCHA');
  419. }
  420. /**
  421. * Testing of the CAPTCHA administration links.
  422. */
  423. function testCaptchAdminLinks() {
  424. // Log in as admin
  425. $this->drupalLogin($this->admin_user);
  426. // Enable CAPTCHA administration links.
  427. $edit = array(
  428. 'captcha_administration_mode' => TRUE,
  429. );
  430. $this->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
  431. // Create a node with comments enabled.
  432. $node = $this->createNodeWithCommentsEnabled();
  433. // Go to node page
  434. $this->drupalGet('node/' . $node->nid);
  435. // Click the add new comment link
  436. $this->clickLink(t('Add new comment'));
  437. $add_comment_url = $this->getUrl();
  438. // Remove fragment part from comment URL to avoid problems with later asserts
  439. $add_comment_url = strtok($add_comment_url, "#");
  440. ////////////////////////////////////////////////////////////
  441. // Click the CAPTCHA admin link to enable a challenge.
  442. $this->clickLink(t('Place a CAPTCHA here for untrusted users.'));
  443. // Enable Math CAPTCHA.
  444. $edit = array('captcha_type' => 'captcha/Math');
  445. $this->drupalPost($this->getUrl(), $edit, t('Save'));
  446. // Check if returned to original comment form.
  447. $this->assertUrl($add_comment_url, array(),
  448. 'After setting CAPTCHA with CAPTCHA admin links: should return to original form.', 'CAPTCHA');
  449. // Check if CAPTCHA was successfully enabled (on CAPTCHA admin links fieldset).
  450. $this->assertText(t('CAPTCHA: challenge "@type" enabled', array('@type' => 'Math')),
  451. 'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
  452. // Check if CAPTCHA was successfully enabled (through API).
  453. $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'captcha/Math');
  454. //////////////////////////////////////////////////////
  455. // Edit challenge type through CAPTCHA admin links.
  456. $this->clickLink(t('change'));
  457. // Enable Math CAPTCHA.
  458. $edit = array('captcha_type' => 'default');
  459. $this->drupalPost($this->getUrl(), $edit, t('Save'));
  460. // Check if returned to original comment form.
  461. $this->assertEqual($add_comment_url, $this->getUrl(),
  462. 'After editing challenge type CAPTCHA admin links: should return to original form.', 'CAPTCHA');
  463. // Check if CAPTCHA was successfully changed (on CAPTCHA admin links fieldset).
  464. // This is actually the same as the previous setting because the captcha/Math is the
  465. // default for the default challenge. TODO Make sure the edit is a real change.
  466. $this->assertText(t('CAPTCHA: challenge "@type" enabled', array('@type' => 'Math')),
  467. 'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
  468. // Check if CAPTCHA was successfully edited (through API).
  469. $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'default');
  470. //////////////////////////////////////////////////////
  471. // Disable challenge through CAPTCHA admin links.
  472. $this->clickLink(t('disable'));
  473. // And confirm.
  474. $this->drupalPost($this->getUrl(), array(), 'Disable');
  475. // Check if returned to original comment form.
  476. $this->assertEqual($add_comment_url, $this->getUrl(),
  477. 'After disablin challenge with CAPTCHA admin links: should return to original form.', 'CAPTCHA');
  478. // Check if CAPTCHA was successfully disabled (on CAPTCHA admin links fieldset).
  479. $this->assertText(t('CAPTCHA: no challenge enabled'),
  480. 'Disable challenge through the CAPTCHA admin links', 'CAPTCHA');
  481. // Check if CAPTCHA was successfully disabled (through API).
  482. $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'none');
  483. }
  484. function testUntrustedUserPosting() {
  485. // Set CAPTCHA on comment form.
  486. captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Math');
  487. // Create a node with comments enabled.
  488. $node = $this->createNodeWithCommentsEnabled();
  489. // Log in as normal (untrusted) user.
  490. $this->drupalLogin($this->normal_user);
  491. // Go to node page and click the "add comment" link.
  492. $this->drupalGet('node/' . $node->nid);
  493. $this->clickLink(t('Add new comment'));
  494. $add_comment_url = $this->getUrl();
  495. // Check if CAPTCHA is visible on form.
  496. $this->assertCaptchaPresence(TRUE);
  497. // Try to post a comment with wrong answer.
  498. $edit = $this->getCommentFormValues();
  499. $edit['captcha_response'] = 'xx';
  500. $this->drupalPost($add_comment_url, $edit, t('Preview'));
  501. $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
  502. 'wrong CAPTCHA should block form submission.', 'CAPTCHA');
  503. //TODO: more testing for untrusted posts.
  504. }
  505. /**
  506. * Test XSS vulnerability on CAPTCHA description.
  507. */
  508. function testXssOnCaptchaDescription() {
  509. // Set CAPTCHA on user register form.
  510. captcha_set_form_id_setting('user_register', 'captcha/Math');
  511. // Put JavaScript snippet in CAPTCHA description.
  512. $this->drupalLogin($this->admin_user);
  513. $xss = '<script type="text/javascript">alert("xss")</script>';
  514. $edit = array('captcha_description' => $xss);
  515. $this->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
  516. // Visit user register form and check if JavaScript snippet is there.
  517. $this->drupalLogout();
  518. $this->drupalGet('user/register');
  519. $this->assertNoRaw($xss, 'JavaScript should not be allowed in CAPTCHA description.', 'CAPTCHA');
  520. }
  521. /**
  522. * Test the CAPTCHA placement clearing.
  523. */
  524. function testCaptchaPlacementCacheClearing() {
  525. // Set CAPTCHA on user register form.
  526. captcha_set_form_id_setting('user_register_form', 'captcha/Math');
  527. // Visit user register form to fill the CAPTCHA placement cache.
  528. $this->drupalGet('user/register');
  529. // Check if there is CAPTCHA placement cache.
  530. $placement_map = variable_get('captcha_placement_map_cache', NULL);
  531. $this->assertNotNull($placement_map, 'CAPTCHA placement cache should be set.');
  532. // Clear the cache
  533. $this->drupalLogin($this->admin_user);
  534. $this->drupalPost(self::CAPTCHA_ADMIN_PATH, array(), t('Clear the CAPTCHA placement cache'));
  535. // Check that the placement cache is unset
  536. $placement_map = variable_get('captcha_placement_map_cache', NULL);
  537. $this->assertNull($placement_map, 'CAPTCHA placement cache should be unset after cache clear.');
  538. }
  539. /**
  540. * Helper function to get the CAPTCHA point setting straight from the database.
  541. * @param string $form_id
  542. * @return stdClass object
  543. */
  544. private function getCaptchaPointSettingFromDatabase($form_id) {
  545. $result = db_query(
  546. "SELECT * FROM {captcha_points} WHERE form_id = :form_id",
  547. array(':form_id' => $form_id)
  548. )->fetchObject();
  549. return $result;
  550. }
  551. /**
  552. * Method for testing the CAPTCHA point administration
  553. */
  554. function testCaptchaPointAdministration() {
  555. // Generate CAPTCHA point data:
  556. // Drupal form ID should consist of lowercase alphanumerics and underscore)
  557. $captcha_point_form_id = 'form_' . strtolower($this->randomName(32));
  558. // the Math CAPTCHA by the CAPTCHA module is always available, so let's use it
  559. $captcha_point_module = 'captcha';
  560. $captcha_point_type = 'Math';
  561. // Log in as admin
  562. $this->drupalLogin($this->admin_user);
  563. // Set CAPTCHA point through admin/user/captcha/captcha/captcha_point
  564. $form_values = array(
  565. 'captcha_point_form_id' => $captcha_point_form_id,
  566. 'captcha_type' => $captcha_point_module .'/'. $captcha_point_type,
  567. );
  568. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point', $form_values, t('Save'));
  569. $this->assertText(t('Saved CAPTCHA point settings.'),
  570. 'Saving of CAPTCHA point settings');
  571. // Check in database
  572. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  573. $this->assertEqual($result->module, $captcha_point_module,
  574. 'Enabled CAPTCHA point should have module set');
  575. $this->assertEqual($result->captcha_type, $captcha_point_type,
  576. 'Enabled CAPTCHA point should have type set');
  577. // Disable CAPTCHA point again
  578. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/disable', array(), t('Disable'));
  579. $this->assertRaw(t('Disabled CAPTCHA for form %form_id.', array('%form_id' => $captcha_point_form_id)), 'Disabling of CAPTCHA point');
  580. // Check in database
  581. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  582. $this->assertNull($result->module,
  583. 'Disabled CAPTCHA point should have NULL as module');
  584. $this->assertNull($result->captcha_type,
  585. 'Disabled CAPTCHA point should have NULL as type');
  586. // Set CAPTCHA point through admin/user/captcha/captcha/captcha_point/$form_id
  587. $form_values = array(
  588. 'captcha_type' => $captcha_point_module .'/'. $captcha_point_type,
  589. );
  590. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id, $form_values, t('Save'));
  591. $this->assertText(t('Saved CAPTCHA point settings.'),
  592. 'Saving of CAPTCHA point settings');
  593. // Check in database
  594. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  595. $this->assertEqual($result->module, $captcha_point_module,
  596. 'Enabled CAPTCHA point should have module set');
  597. $this->assertEqual($result->captcha_type, $captcha_point_type,
  598. 'Enabled CAPTCHA point should have type set');
  599. // Delete CAPTCHA point
  600. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/delete', array(), t('Delete'));
  601. $this->assertRaw(t('Deleted CAPTCHA for form %form_id.', array('%form_id' => $captcha_point_form_id)),
  602. 'Deleting of CAPTCHA point');
  603. // Check in database
  604. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  605. $this->assertFalse($result, 'Deleted CAPTCHA point should be in database');
  606. }
  607. /**
  608. * Method for testing the CAPTCHA point administration
  609. */
  610. function testCaptchaPointAdministrationByNonAdmin() {
  611. // First add a CAPTCHA point (as admin)
  612. $this->drupalLogin($this->admin_user);
  613. $captcha_point_form_id = 'form_' . strtolower($this->randomName(32));
  614. $captcha_point_module = 'captcha';
  615. $captcha_point_type = 'Math';
  616. $form_values = array(
  617. 'captcha_point_form_id' => $captcha_point_form_id,
  618. 'captcha_type' => $captcha_point_module .'/'. $captcha_point_type,
  619. );
  620. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/', $form_values, t('Save'));
  621. $this->assertText(t('Saved CAPTCHA point settings.'),
  622. 'Saving of CAPTCHA point settings');
  623. // Switch from admin to nonadmin
  624. $this->drupalGet(url('logout', array('absolute' => TRUE)));
  625. $this->drupalLogin($this->normal_user);
  626. // Try to set CAPTCHA point through admin/user/captcha/captcha/captcha_point
  627. $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point');
  628. $this->assertText(t('You are not authorized to access this page.'),
  629. 'Non admin should not be able to set a CAPTCHA point');
  630. // Try to set CAPTCHA point through admin/user/captcha/captcha/captcha_point/$form_id
  631. $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . 'form_' . strtolower($this->randomName(32)));
  632. $this->assertText(t('You are not authorized to access this page.'),
  633. 'Non admin should not be able to set a CAPTCHA point');
  634. // Try to disable the CAPTCHA point
  635. $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/disable');
  636. $this->assertText(t('You are not authorized to access this page.'),
  637. 'Non admin should not be able to disable a CAPTCHA point');
  638. // Try to delete the CAPTCHA point
  639. $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/delete');
  640. $this->assertText(t('You are not authorized to access this page.'),
  641. 'Non admin should not be able to delete a CAPTCHA point');
  642. // Switch from nonadmin to admin again
  643. $this->drupalGet(url('logout', array('absolute' => TRUE)));
  644. $this->drupalLogin($this->admin_user);
  645. // Check if original CAPTCHA point still exists in database
  646. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  647. $this->assertEqual($result->module, $captcha_point_module,
  648. 'Enabled CAPTCHA point should still have module set');
  649. $this->assertEqual($result->captcha_type, $captcha_point_type,
  650. 'Enabled CAPTCHA point should still have type set');
  651. // Delete CAPTCHA point
  652. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/delete', array(), t('Delete'));
  653. $this->assertRaw(t('Deleted CAPTCHA for form %form_id.', array('%form_id' => $captcha_point_form_id)),
  654. 'Deleting of CAPTCHA point');
  655. }
  656. }
  657. class CaptchaPersistenceTestCase extends CaptchaBaseWebTestCase {
  658. public static function getInfo() {
  659. return array(
  660. 'name' => t('CAPTCHA persistence functionality'),
  661. 'description' => t('Testing of the CAPTCHA persistence functionality.'),
  662. 'group' => t('CAPTCHA'),
  663. );
  664. }
  665. /**
  666. * Set up the persistence and CAPTCHA settings.
  667. * @param int $persistence the persistence value.
  668. */
  669. private function setUpPersistence($persistence) {
  670. // Log in as admin
  671. $this->drupalLogin($this->admin_user);
  672. // Set persistence.
  673. $edit = array('captcha_persistence' => $persistence);
  674. $this->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
  675. // Log admin out.
  676. $this->drupalLogout();
  677. // Set the Test123 CAPTCHA on user register and comment form.
  678. // We have to do this with the function captcha_set_form_id_setting()
  679. // (because the CATCHA admin form does not show the Test123 option).
  680. // We also have to do this after all usage of the CAPTCHA admin form
  681. // (because posting the CAPTCHA admin form would set the CAPTCHA to 'none').
  682. captcha_set_form_id_setting('user_login', 'captcha/Test');
  683. $this->drupalGet('user');
  684. $this->assertCaptchaPresence(TRUE);
  685. captcha_set_form_id_setting('user_register_form', 'captcha/Test');
  686. $this->drupalGet('user/register');
  687. $this->assertCaptchaPresence(TRUE);
  688. }
  689. protected function assertPreservedCsid($captcha_sid_initial) {
  690. $captcha_sid = $this->getCaptchaSidFromForm();
  691. $this->assertEqual($captcha_sid_initial, $captcha_sid,
  692. "CAPTCHA session ID should be preserved (expected: $captcha_sid_initial, found: $captcha_sid).");
  693. }
  694. protected function assertDifferentCsid($captcha_sid_initial) {
  695. $captcha_sid = $this->getCaptchaSidFromForm();
  696. $this->assertNotEqual($captcha_sid_initial, $captcha_sid,
  697. "CAPTCHA session ID should be different.");
  698. }
  699. function testPersistenceAlways(){
  700. // Set up of persistence and CAPTCHAs.
  701. $this->setUpPersistence(CAPTCHA_PERSISTENCE_SHOW_ALWAYS);
  702. // Go to login form and check if there is a CAPTCHA on the login form (look for the title).
  703. $this->drupalGet('user');
  704. $this->assertCaptchaPresence(TRUE);
  705. $captcha_sid_initial = $this->getCaptchaSidFromForm();
  706. // Try to with wrong user name and password, but correct CAPTCHA.
  707. $edit = array(
  708. 'name' => 'foobar',
  709. 'pass' => 'bazlaz',
  710. 'captcha_response' => 'Test 123',
  711. );
  712. $this->drupalPost(NULL, $edit, t('Log in'));
  713. // Check that there was no error message for the CAPTCHA.
  714. $this->assertCaptchaResponseAccepted();
  715. // Name and password were wrong, we should get an updated form with a fresh CAPTCHA.
  716. $this->assertCaptchaPresence(TRUE);
  717. $this->assertPreservedCsid($captcha_sid_initial);
  718. // Post from again.
  719. $this->drupalPost(NULL, $edit, t('Log in'));
  720. // Check that there was no error message for the CAPTCHA.
  721. $this->assertCaptchaResponseAccepted();
  722. $this->assertPreservedCsid($captcha_sid_initial);
  723. }
  724. function testPersistencePerFormInstance(){
  725. // Set up of persistence and CAPTCHAs.
  726. $this->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
  727. // Go to login form and check if there is a CAPTCHA on the login form.
  728. $this->drupalGet('user');
  729. $this->assertCaptchaPresence(TRUE);
  730. $captcha_sid_initial = $this->getCaptchaSidFromForm();
  731. // Try to with wrong user name and password, but correct CAPTCHA.
  732. $edit = array(
  733. 'name' => 'foobar',
  734. 'pass' => 'bazlaz',
  735. 'captcha_response' => 'Test 123',
  736. );
  737. $this->drupalPost(NULL, $edit, t('Log in'));
  738. // Check that there was no error message for the CAPTCHA.
  739. $this->assertCaptchaResponseAccepted();
  740. // There shouldn't be a CAPTCHA on the new form.
  741. $this->assertCaptchaPresence(FALSE);
  742. $this->assertPreservedCsid($captcha_sid_initial);
  743. // Start a new form instance/session
  744. $this->drupalGet('node');
  745. $this->drupalGet('user');
  746. $this->assertCaptchaPresence(TRUE);
  747. $this->assertDifferentCsid($captcha_sid_initial);
  748. // Check another form
  749. $this->drupalGet('user/register');
  750. $this->assertCaptchaPresence(TRUE);
  751. $this->assertDifferentCsid($captcha_sid_initial);
  752. }
  753. function testPersistencePerFormType(){
  754. // Set up of persistence and CAPTCHAs.
  755. $this->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_TYPE);
  756. // Go to login form and check if there is a CAPTCHA on the login form.
  757. $this->drupalGet('user');
  758. $this->assertCaptchaPresence(TRUE);
  759. $captcha_sid_initial = $this->getCaptchaSidFromForm();
  760. // Try to with wrong user name and password, but correct CAPTCHA.
  761. $edit = array(
  762. 'name' => 'foobar',
  763. 'pass' => 'bazlaz',
  764. 'captcha_response' => 'Test 123',
  765. );
  766. $this->drupalPost(NULL, $edit, t('Log in'));
  767. // Check that there was no error message for the CAPTCHA.
  768. $this->assertCaptchaResponseAccepted();
  769. // There shouldn't be a CAPTCHA on the new form.
  770. $this->assertCaptchaPresence(FALSE);
  771. $this->assertPreservedCsid($captcha_sid_initial);
  772. // Start a new form instance/session
  773. $this->drupalGet('node');
  774. $this->drupalGet('user');
  775. $this->assertCaptchaPresence(FALSE);
  776. $this->assertDifferentCsid($captcha_sid_initial);
  777. // Check another form
  778. $this->drupalGet('user/register');
  779. $this->assertCaptchaPresence(TRUE);
  780. $this->assertDifferentCsid($captcha_sid_initial);
  781. }
  782. function testPersistenceOnlyOnce(){
  783. // Set up of persistence and CAPTCHAs.
  784. $this->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL);
  785. // Go to login form and check if there is a CAPTCHA on the login form.
  786. $this->drupalGet('user');
  787. $this->assertCaptchaPresence(TRUE);
  788. $captcha_sid_initial = $this->getCaptchaSidFromForm();
  789. // Try to with wrong user name and password, but correct CAPTCHA.
  790. $edit = array(
  791. 'name' => 'foobar',
  792. 'pass' => 'bazlaz',
  793. 'captcha_response' => 'Test 123',
  794. );
  795. $this->drupalPost(NULL, $edit, t('Log in'));
  796. // Check that there was no error message for the CAPTCHA.
  797. $this->assertCaptchaResponseAccepted();
  798. // There shouldn't be a CAPTCHA on the new form.
  799. $this->assertCaptchaPresence(FALSE);
  800. $this->assertPreservedCsid($captcha_sid_initial);
  801. // Start a new form instance/session
  802. $this->drupalGet('node');
  803. $this->drupalGet('user');
  804. $this->assertCaptchaPresence(FALSE);
  805. $this->assertDifferentCsid($captcha_sid_initial);
  806. // Check another form
  807. $this->drupalGet('user/register');
  808. $this->assertCaptchaPresence(FALSE);
  809. $this->assertDifferentCsid($captcha_sid_initial);
  810. }
  811. }
  812. class CaptchaSessionReuseAttackTestCase extends CaptchaBaseWebTestCase {
  813. public static function getInfo() {
  814. return array(
  815. 'name' => t('CAPTCHA session reuse attack tests'),
  816. 'description' => t('Testing of the protection against CAPTCHA session reuse attacks.'),
  817. 'group' => t('CAPTCHA'),
  818. );
  819. }
  820. /**
  821. * Assert that the CAPTCHA session ID reuse attack was detected.
  822. */
  823. protected function assertCaptchaSessionIdReuseAttackDetection() {
  824. $this->assertText(t(CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE),
  825. 'CAPTCHA session ID reuse attack should be detected.',
  826. 'CAPTCHA');
  827. // There should be an error message about wrong response.
  828. $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
  829. 'CAPTCHA response should flagged as wrong.',
  830. 'CAPTCHA');
  831. }
  832. function testCaptchaSessionReuseAttackDetectionOnCommentPreview() {
  833. // Create commentable node
  834. $node = $this->createNodeWithCommentsEnabled();
  835. // Set Test CAPTCHA on comment form.
  836. captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Math');
  837. variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
  838. // Log in as normal user.
  839. $this->drupalLogin($this->normal_user);
  840. // Go to comment form of commentable node.
  841. $this->drupalGet('comment/reply/' . $node->nid);
  842. $this->assertCaptchaPresence(TRUE);
  843. // Get CAPTCHA session ID and solution of the challenge.
  844. $captcha_sid = $this->getCaptchaSidFromForm();
  845. $captcha_token = $this->getCaptchaTokenFromForm();
  846. $solution = $this->getMathCaptchaSolutionFromForm();
  847. // Post the form with the solution.
  848. $edit = $this->getCommentFormValues();
  849. $edit['captcha_response'] = $solution;
  850. $this->drupalPost(NULL, $edit, t('Preview'));
  851. // Answer should be accepted and further CAPTCHA ommitted.
  852. $this->assertCaptchaResponseAccepted();
  853. $this->assertCaptchaPresence(FALSE);
  854. // Post a new comment, reusing the previous CAPTCHA session.
  855. $edit = $this->getCommentFormValues();
  856. $edit['captcha_sid'] = $captcha_sid;
  857. $edit['captcha_token'] = $captcha_token;
  858. $edit['captcha_response'] = $solution;
  859. $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
  860. // CAPTCHA session reuse attack should be detected.
  861. $this->assertCaptchaSessionIdReuseAttackDetection();
  862. // There should be a CAPTCHA.
  863. $this->assertCaptchaPresence(TRUE);
  864. }
  865. function testCaptchaSessionReuseAttackDetectionOnNodeForm() {
  866. // Set CAPTCHA on page form.
  867. captcha_set_form_id_setting('page_node_form', 'captcha/Math');
  868. variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
  869. // Log in as normal user.
  870. $this->drupalLogin($this->normal_user);
  871. // Go to node add form.
  872. $this->drupalGet('node/add/page');
  873. $this->assertCaptchaPresence(TRUE);
  874. // Get CAPTCHA session ID and solution of the challenge.
  875. $captcha_sid = $this->getCaptchaSidFromForm();
  876. $captcha_token = $this->getCaptchaTokenFromForm();
  877. $solution = $this->getMathCaptchaSolutionFromForm();
  878. // Page settings to post, with correct CAPTCHA answer.
  879. $edit = $this->getNodeFormValues();
  880. $edit['captcha_response'] = $solution;
  881. // Preview the node
  882. $this->drupalPost(NULL, $edit, t('Preview'));
  883. // Answer should be accepted.
  884. $this->assertCaptchaResponseAccepted();
  885. // Check that there is no CAPTCHA after preview.
  886. $this->assertCaptchaPresence(FALSE);
  887. // Post a new comment, reusing the previous CAPTCHA session.
  888. $edit = $this->getNodeFormValues();
  889. $edit['captcha_sid'] = $captcha_sid;
  890. $edit['captcha_token'] = $captcha_token;
  891. $edit['captcha_response'] = $solution;
  892. $this->drupalPost('node/add/page', $edit, t('Preview'));
  893. // CAPTCHA session reuse attack should be detected.
  894. $this->assertCaptchaSessionIdReuseAttackDetection();
  895. // There should be a CAPTCHA.
  896. $this->assertCaptchaPresence(TRUE);
  897. }
  898. function testCaptchaSessionReuseAttackDetectionOnLoginForm() {
  899. // Set CAPTCHA on login form.
  900. captcha_set_form_id_setting('user_login', 'captcha/Math');
  901. variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
  902. // Go to log in form.
  903. $this->drupalGet('user');
  904. $this->assertCaptchaPresence(TRUE);
  905. // Get CAPTCHA session ID and solution of the challenge.
  906. $captcha_sid = $this->getCaptchaSidFromForm();
  907. $captcha_token = $this->getCaptchaTokenFromForm();
  908. $solution = $this->getMathCaptchaSolutionFromForm();
  909. // Log in through form.
  910. $edit = array(
  911. 'name' => $this->normal_user->name,
  912. 'pass' => $this->normal_user->pass_raw,
  913. 'captcha_response' => $solution,
  914. );
  915. $this->drupalPost(NULL, $edit, t('Log in'));
  916. $this->assertCaptchaResponseAccepted();
  917. $this->assertCaptchaPresence(FALSE);
  918. // If a "log out" link appears on the page, it is almost certainly because
  919. // the login was successful.
  920. $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $this->normal_user->name)), t('User login'));
  921. // Log out again.
  922. $this->drupalLogout();
  923. // Try to log in again, reusing the previous CAPTCHA session.
  924. $edit += array(
  925. 'captcha_sid' => $captcha_sid,
  926. 'captcha_token' => $captcha_token,
  927. );
  928. $this->drupalPost('user', $edit, t('Log in'));
  929. // CAPTCHA session reuse attack should be detected.
  930. $this->assertCaptchaSessionIdReuseAttackDetection();
  931. // There should be a CAPTCHA.
  932. $this->assertCaptchaPresence(TRUE);
  933. }
  934. public function testMultipleCaptchaProtectedFormsOnOnePage()
  935. {
  936. // Set Test CAPTCHA on comment form and login block
  937. captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');
  938. captcha_set_form_id_setting('user_login_block', 'captcha/Math');
  939. $this->allowCommentPostingForAnonymousVisitors();
  940. // Create a node with comments enabled.
  941. $node = $this->createNodeWithCommentsEnabled();
  942. // Preview comment with correct CAPTCHA answer.
  943. $edit = $this->getCommentFormValues();
  944. $comment_subject = $edit['subject'];
  945. $edit['captcha_response'] = 'Test 123';
  946. $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
  947. // Post should be accepted: no warnings,
  948. // no CAPTCHA reuse detection (which could be used by user log in block).
  949. $this->assertCaptchaResponseAccepted();
  950. $this->assertText($comment_subject);
  951. }
  952. }
  953. // Some tricks to debug:
  954. // drupal_debug($data) // from devel module
  955. // file_put_contents('tmp.simpletest.html', $this->drupalGetContent());