captcha.test 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143
  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. file_put_contents('tmp.simpletest.html', $this->drupalGetContent());
  354. $this->assertText(t('Access denied'), 'Normal users should not be able to access the CAPTCHA admin pages', 'CAPTCHA');
  355. $this->drupalLogin($this->admin_user);
  356. $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
  357. $this->assertNoText(t('Access denied'), 'Admin users should be able to access the CAPTCHA admin pages', 'CAPTCHA');
  358. }
  359. /**
  360. * Test the CAPTCHA point setting getter/setter.
  361. */
  362. function testCaptchaPointSettingGetterAndSetter() {
  363. $comment_form_id = self::COMMENT_FORM_ID;
  364. // Set to 'none'.
  365. captcha_set_form_id_setting($comment_form_id, 'none');
  366. $result = captcha_get_form_id_setting($comment_form_id);
  367. $this->assertNotNull($result, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
  368. $this->assertNull($result->module, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
  369. $this->assertNull($result->captcha_type, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
  370. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  371. $this->assertEqual($result, 'none', 'Setting and symbolic getting CAPTCHA point: "none"', 'CAPTCHA');
  372. // Set to 'default'
  373. captcha_set_form_id_setting($comment_form_id, 'default');
  374. variable_set('captcha_default_challenge', 'foo/bar');
  375. $result = captcha_get_form_id_setting($comment_form_id);
  376. $this->assertNotNull($result, 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
  377. $this->assertEqual($result->module, 'foo', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
  378. $this->assertEqual($result->captcha_type, 'bar', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
  379. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  380. $this->assertEqual($result, 'default', 'Setting and symbolic getting CAPTCHA point: "default"', 'CAPTCHA');
  381. // Set to 'baz/boo'.
  382. captcha_set_form_id_setting($comment_form_id, 'baz/boo');
  383. $result = captcha_get_form_id_setting($comment_form_id);
  384. $this->assertNotNull($result, 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
  385. $this->assertEqual($result->module, 'baz', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
  386. $this->assertEqual($result->captcha_type, 'boo', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
  387. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  388. $this->assertEqual($result, 'baz/boo', 'Setting and symbolic getting CAPTCHA point: "baz/boo"', 'CAPTCHA');
  389. // Set to NULL (which should delete the CAPTCHA point setting entry).
  390. captcha_set_form_id_setting($comment_form_id, NULL);
  391. $result = captcha_get_form_id_setting($comment_form_id);
  392. $this->assertNull($result, 'Setting and getting CAPTCHA point: NULL', 'CAPTCHA');
  393. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  394. $this->assertNull($result, 'Setting and symbolic getting CAPTCHA point: NULL', 'CAPTCHA');
  395. // Set with object.
  396. $captcha_type = new stdClass;
  397. $captcha_type->module = 'baba';
  398. $captcha_type->captcha_type = 'fofo';
  399. captcha_set_form_id_setting($comment_form_id, $captcha_type);
  400. $result = captcha_get_form_id_setting($comment_form_id);
  401. $this->assertNotNull($result, 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
  402. $this->assertEqual($result->module, 'baba', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
  403. $this->assertEqual($result->captcha_type, 'fofo', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
  404. $result = captcha_get_form_id_setting($comment_form_id, TRUE);
  405. $this->assertEqual($result, 'baba/fofo', 'Setting and symbolic getting CAPTCHA point: "baba/fofo"', 'CAPTCHA');
  406. }
  407. /**
  408. * Helper function for checking CAPTCHA setting of a form.
  409. *
  410. * @param $form_id the form_id of the form to investigate.
  411. * @param $challenge_type what the challenge type should be:
  412. * NULL, 'none', 'default' or something like 'captcha/Math'
  413. */
  414. protected function assertCaptchaSetting($form_id, $challenge_type) {
  415. $result = captcha_get_form_id_setting(self::COMMENT_FORM_ID, TRUE);
  416. $this->assertEqual($result, $challenge_type,
  417. t('Check CAPTCHA setting for form: expected: @expected, received: @received.',
  418. array('@expected' => var_export($challenge_type, TRUE), '@received' => var_export($result, TRUE))),
  419. 'CAPTCHA');
  420. }
  421. /**
  422. * Testing of the CAPTCHA administration links.
  423. */
  424. function testCaptchAdminLinks() {
  425. // Log in as admin
  426. $this->drupalLogin($this->admin_user);
  427. // Enable CAPTCHA administration links.
  428. $edit = array(
  429. 'captcha_administration_mode' => TRUE,
  430. );
  431. $this->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
  432. // Create a node with comments enabled.
  433. $node = $this->createNodeWithCommentsEnabled();
  434. // Go to node page
  435. $this->drupalGet('node/' . $node->nid);
  436. // Click the add new comment link
  437. $this->clickLink(t('Add new comment'));
  438. $add_comment_url = $this->getUrl();
  439. // Remove fragment part from comment URL to avoid problems with later asserts
  440. $add_comment_url = strtok($add_comment_url, "#");
  441. ////////////////////////////////////////////////////////////
  442. // Click the CAPTCHA admin link to enable a challenge.
  443. $this->clickLink(t('Place a CAPTCHA here for untrusted users.'));
  444. // Enable Math CAPTCHA.
  445. $edit = array('captcha_type' => 'captcha/Math');
  446. $this->drupalPost($this->getUrl(), $edit, t('Save'));
  447. // Check if returned to original comment form.
  448. $this->assertUrl($add_comment_url, array(),
  449. 'After setting CAPTCHA with CAPTCHA admin links: should return to original form.', 'CAPTCHA');
  450. // Check if CAPTCHA was successfully enabled (on CAPTCHA admin links fieldset).
  451. $this->assertText(t('CAPTCHA: challenge "@type" enabled', array('@type' => 'Math')),
  452. 'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
  453. // Check if CAPTCHA was successfully enabled (through API).
  454. $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'captcha/Math');
  455. //////////////////////////////////////////////////////
  456. // Edit challenge type through CAPTCHA admin links.
  457. $this->clickLink(t('change'));
  458. // Enable Math CAPTCHA.
  459. $edit = array('captcha_type' => 'default');
  460. $this->drupalPost($this->getUrl(), $edit, t('Save'));
  461. // Check if returned to original comment form.
  462. $this->assertEqual($add_comment_url, $this->getUrl(),
  463. 'After editing challenge type CAPTCHA admin links: should return to original form.', 'CAPTCHA');
  464. // Check if CAPTCHA was successfully changed (on CAPTCHA admin links fieldset).
  465. // This is actually the same as the previous setting because the captcha/Math is the
  466. // default for the default challenge. TODO Make sure the edit is a real change.
  467. $this->assertText(t('CAPTCHA: challenge "@type" enabled', array('@type' => 'Math')),
  468. 'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
  469. // Check if CAPTCHA was successfully edited (through API).
  470. $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'default');
  471. //////////////////////////////////////////////////////
  472. // Disable challenge through CAPTCHA admin links.
  473. $this->clickLink(t('disable'));
  474. // And confirm.
  475. $this->drupalPost($this->getUrl(), array(), 'Disable');
  476. // Check if returned to original comment form.
  477. $this->assertEqual($add_comment_url, $this->getUrl(),
  478. 'After disablin challenge with CAPTCHA admin links: should return to original form.', 'CAPTCHA');
  479. // Check if CAPTCHA was successfully disabled (on CAPTCHA admin links fieldset).
  480. $this->assertText(t('CAPTCHA: no challenge enabled'),
  481. 'Disable challenge through the CAPTCHA admin links', 'CAPTCHA');
  482. // Check if CAPTCHA was successfully disabled (through API).
  483. $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'none');
  484. }
  485. function testUntrustedUserPosting() {
  486. // Set CAPTCHA on comment form.
  487. captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Math');
  488. // Create a node with comments enabled.
  489. $node = $this->createNodeWithCommentsEnabled();
  490. // Log in as normal (untrusted) user.
  491. $this->drupalLogin($this->normal_user);
  492. // Go to node page and click the "add comment" link.
  493. $this->drupalGet('node/' . $node->nid);
  494. $this->clickLink(t('Add new comment'));
  495. $add_comment_url = $this->getUrl();
  496. // Check if CAPTCHA is visible on form.
  497. $this->assertCaptchaPresence(TRUE);
  498. // Try to post a comment with wrong answer.
  499. $edit = $this->getCommentFormValues();
  500. $edit['captcha_response'] = 'xx';
  501. $this->drupalPost($add_comment_url, $edit, t('Preview'));
  502. $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
  503. 'wrong CAPTCHA should block form submission.', 'CAPTCHA');
  504. //TODO: more testing for untrusted posts.
  505. }
  506. /**
  507. * Test XSS vulnerability on CAPTCHA description.
  508. */
  509. function testXssOnCaptchaDescription() {
  510. // Set CAPTCHA on user register form.
  511. captcha_set_form_id_setting('user_register', 'captcha/Math');
  512. // Put JavaScript snippet in CAPTCHA description.
  513. $this->drupalLogin($this->admin_user);
  514. $xss = '<script type="text/javascript">alert("xss")</script>';
  515. $edit = array('captcha_description' => $xss);
  516. $this->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
  517. // Visit user register form and check if JavaScript snippet is there.
  518. $this->drupalLogout();
  519. $this->drupalGet('user/register');
  520. $this->assertNoRaw($xss, 'JavaScript should not be allowed in CAPTCHA description.', 'CAPTCHA');
  521. }
  522. /**
  523. * Test the CAPTCHA placement clearing.
  524. */
  525. function testCaptchaPlacementCacheClearing() {
  526. // Set CAPTCHA on user register form.
  527. captcha_set_form_id_setting('user_register_form', 'captcha/Math');
  528. // Visit user register form to fill the CAPTCHA placement cache.
  529. $this->drupalGet('user/register');
  530. // Check if there is CAPTCHA placement cache.
  531. $placement_map = variable_get('captcha_placement_map_cache', NULL);
  532. $this->assertNotNull($placement_map, 'CAPTCHA placement cache should be set.');
  533. // Clear the cache
  534. $this->drupalLogin($this->admin_user);
  535. $this->drupalPost(self::CAPTCHA_ADMIN_PATH, array(), t('Clear the CAPTCHA placement cache'));
  536. // Check that the placement cache is unset
  537. $placement_map = variable_get('captcha_placement_map_cache', NULL);
  538. $this->assertNull($placement_map, 'CAPTCHA placement cache should be unset after cache clear.');
  539. }
  540. /**
  541. * Helper function to get the CAPTCHA point setting straight from the database.
  542. * @param string $form_id
  543. * @return stdClass object
  544. */
  545. private function getCaptchaPointSettingFromDatabase($form_id) {
  546. $result = db_query(
  547. "SELECT * FROM {captcha_points} WHERE form_id = :form_id",
  548. array(':form_id' => $form_id)
  549. )->fetchObject();
  550. return $result;
  551. }
  552. /**
  553. * Method for testing the CAPTCHA point administration
  554. */
  555. function testCaptchaPointAdministration() {
  556. // Generate CAPTCHA point data:
  557. // Drupal form ID should consist of lowercase alphanumerics and underscore)
  558. $captcha_point_form_id = 'form_' . strtolower($this->randomName(32));
  559. // the Math CAPTCHA by the CAPTCHA module is always available, so let's use it
  560. $captcha_point_module = 'captcha';
  561. $captcha_point_type = 'Math';
  562. // Log in as admin
  563. $this->drupalLogin($this->admin_user);
  564. // Set CAPTCHA point through admin/user/captcha/captcha/captcha_point
  565. $form_values = array(
  566. 'captcha_point_form_id' => $captcha_point_form_id,
  567. 'captcha_type' => $captcha_point_module .'/'. $captcha_point_type,
  568. );
  569. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point', $form_values, t('Save'));
  570. $this->assertText(t('Saved CAPTCHA point settings.'),
  571. 'Saving of CAPTCHA point settings');
  572. // Check in database
  573. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  574. $this->assertEqual($result->module, $captcha_point_module,
  575. 'Enabled CAPTCHA point should have module set');
  576. $this->assertEqual($result->captcha_type, $captcha_point_type,
  577. 'Enabled CAPTCHA point should have type set');
  578. // Disable CAPTCHA point again
  579. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/disable', array(), t('Disable'));
  580. $this->assertRaw(t('Disabled CAPTCHA for form %form_id.', array('%form_id' => $captcha_point_form_id)), 'Disabling of CAPTCHA point');
  581. // Check in database
  582. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  583. $this->assertNull($result->module,
  584. 'Disabled CAPTCHA point should have NULL as module');
  585. $this->assertNull($result->captcha_type,
  586. 'Disabled CAPTCHA point should have NULL as type');
  587. // Set CAPTCHA point through admin/user/captcha/captcha/captcha_point/$form_id
  588. $form_values = array(
  589. 'captcha_type' => $captcha_point_module .'/'. $captcha_point_type,
  590. );
  591. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id, $form_values, t('Save'));
  592. $this->assertText(t('Saved CAPTCHA point settings.'),
  593. 'Saving of CAPTCHA point settings');
  594. // Check in database
  595. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  596. $this->assertEqual($result->module, $captcha_point_module,
  597. 'Enabled CAPTCHA point should have module set');
  598. $this->assertEqual($result->captcha_type, $captcha_point_type,
  599. 'Enabled CAPTCHA point should have type set');
  600. // Delete CAPTCHA point
  601. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/delete', array(), t('Delete'));
  602. $this->assertRaw(t('Deleted CAPTCHA for form %form_id.', array('%form_id' => $captcha_point_form_id)),
  603. 'Deleting of CAPTCHA point');
  604. // Check in database
  605. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  606. $this->assertFalse($result, 'Deleted CAPTCHA point should be in database');
  607. }
  608. /**
  609. * Method for testing the CAPTCHA point administration
  610. */
  611. function testCaptchaPointAdministrationByNonAdmin() {
  612. // First add a CAPTCHA point (as admin)
  613. $this->drupalLogin($this->admin_user);
  614. $captcha_point_form_id = 'form_' . strtolower($this->randomName(32));
  615. $captcha_point_module = 'captcha';
  616. $captcha_point_type = 'Math';
  617. $form_values = array(
  618. 'captcha_point_form_id' => $captcha_point_form_id,
  619. 'captcha_type' => $captcha_point_module .'/'. $captcha_point_type,
  620. );
  621. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/', $form_values, t('Save'));
  622. $this->assertText(t('Saved CAPTCHA point settings.'),
  623. 'Saving of CAPTCHA point settings');
  624. // Switch from admin to nonadmin
  625. $this->drupalGet(url('logout', array('absolute' => TRUE)));
  626. $this->drupalLogin($this->normal_user);
  627. // Try to set CAPTCHA point through admin/user/captcha/captcha/captcha_point
  628. $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point');
  629. $this->assertText(t('You are not authorized to access this page.'),
  630. 'Non admin should not be able to set a CAPTCHA point');
  631. // Try to set CAPTCHA point through admin/user/captcha/captcha/captcha_point/$form_id
  632. $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . 'form_' . strtolower($this->randomName(32)));
  633. $this->assertText(t('You are not authorized to access this page.'),
  634. 'Non admin should not be able to set a CAPTCHA point');
  635. // Try to disable the CAPTCHA point
  636. $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/disable');
  637. $this->assertText(t('You are not authorized to access this page.'),
  638. 'Non admin should not be able to disable a CAPTCHA point');
  639. // Try to delete the CAPTCHA point
  640. $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/delete');
  641. $this->assertText(t('You are not authorized to access this page.'),
  642. 'Non admin should not be able to delete a CAPTCHA point');
  643. // Switch from nonadmin to admin again
  644. $this->drupalGet(url('logout', array('absolute' => TRUE)));
  645. $this->drupalLogin($this->admin_user);
  646. // Check if original CAPTCHA point still exists in database
  647. $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
  648. $this->assertEqual($result->module, $captcha_point_module,
  649. 'Enabled CAPTCHA point should still have module set');
  650. $this->assertEqual($result->captcha_type, $captcha_point_type,
  651. 'Enabled CAPTCHA point should still have type set');
  652. // Delete CAPTCHA point
  653. $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/delete', array(), t('Delete'));
  654. $this->assertRaw(t('Deleted CAPTCHA for form %form_id.', array('%form_id' => $captcha_point_form_id)),
  655. 'Deleting of CAPTCHA point');
  656. }
  657. }
  658. class CaptchaPersistenceTestCase extends CaptchaBaseWebTestCase {
  659. public static function getInfo() {
  660. return array(
  661. 'name' => t('CAPTCHA persistence functionality'),
  662. 'description' => t('Testing of the CAPTCHA persistence functionality.'),
  663. 'group' => t('CAPTCHA'),
  664. );
  665. }
  666. /**
  667. * Set up the persistence and CAPTCHA settings.
  668. * @param int $persistence the persistence value.
  669. */
  670. private function setUpPersistence($persistence) {
  671. // Log in as admin
  672. $this->drupalLogin($this->admin_user);
  673. // Set persistence.
  674. $edit = array('captcha_persistence' => $persistence);
  675. $this->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
  676. // Log admin out.
  677. $this->drupalLogout();
  678. // Set the Test123 CAPTCHA on user register and comment form.
  679. // We have to do this with the function captcha_set_form_id_setting()
  680. // (because the CATCHA admin form does not show the Test123 option).
  681. // We also have to do this after all usage of the CAPTCHA admin form
  682. // (because posting the CAPTCHA admin form would set the CAPTCHA to 'none').
  683. captcha_set_form_id_setting('user_login', 'captcha/Test');
  684. $this->drupalGet('user');
  685. $this->assertCaptchaPresence(TRUE);
  686. captcha_set_form_id_setting('user_register_form', 'captcha/Test');
  687. $this->drupalGet('user/register');
  688. $this->assertCaptchaPresence(TRUE);
  689. }
  690. protected function assertPreservedCsid($captcha_sid_initial) {
  691. $captcha_sid = $this->getCaptchaSidFromForm();
  692. $this->assertEqual($captcha_sid_initial, $captcha_sid,
  693. "CAPTCHA session ID should be preserved (expected: $captcha_sid_initial, found: $captcha_sid).");
  694. }
  695. protected function assertDifferentCsid($captcha_sid_initial) {
  696. $captcha_sid = $this->getCaptchaSidFromForm();
  697. $this->assertNotEqual($captcha_sid_initial, $captcha_sid,
  698. "CAPTCHA session ID should be different.");
  699. }
  700. function testPersistenceAlways(){
  701. // Set up of persistence and CAPTCHAs.
  702. $this->setUpPersistence(CAPTCHA_PERSISTENCE_SHOW_ALWAYS);
  703. // Go to login form and check if there is a CAPTCHA on the login form (look for the title).
  704. $this->drupalGet('user');
  705. $this->assertCaptchaPresence(TRUE);
  706. $captcha_sid_initial = $this->getCaptchaSidFromForm();
  707. // Try to with wrong user name and password, but correct CAPTCHA.
  708. $edit = array(
  709. 'name' => 'foobar',
  710. 'pass' => 'bazlaz',
  711. 'captcha_response' => 'Test 123',
  712. );
  713. $this->drupalPost(NULL, $edit, t('Log in'));
  714. // Check that there was no error message for the CAPTCHA.
  715. $this->assertCaptchaResponseAccepted();
  716. // Name and password were wrong, we should get an updated form with a fresh CAPTCHA.
  717. $this->assertCaptchaPresence(TRUE);
  718. $this->assertPreservedCsid($captcha_sid_initial);
  719. // Post from again.
  720. $this->drupalPost(NULL, $edit, t('Log in'));
  721. // Check that there was no error message for the CAPTCHA.
  722. $this->assertCaptchaResponseAccepted();
  723. $this->assertPreservedCsid($captcha_sid_initial);
  724. }
  725. function testPersistencePerFormInstance(){
  726. // Set up of persistence and CAPTCHAs.
  727. $this->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
  728. // Go to login form and check if there is a CAPTCHA on the login form.
  729. $this->drupalGet('user');
  730. $this->assertCaptchaPresence(TRUE);
  731. $captcha_sid_initial = $this->getCaptchaSidFromForm();
  732. // Try to with wrong user name and password, but correct CAPTCHA.
  733. $edit = array(
  734. 'name' => 'foobar',
  735. 'pass' => 'bazlaz',
  736. 'captcha_response' => 'Test 123',
  737. );
  738. $this->drupalPost(NULL, $edit, t('Log in'));
  739. // Check that there was no error message for the CAPTCHA.
  740. $this->assertCaptchaResponseAccepted();
  741. // There shouldn't be a CAPTCHA on the new form.
  742. $this->assertCaptchaPresence(FALSE);
  743. $this->assertPreservedCsid($captcha_sid_initial);
  744. // Start a new form instance/session
  745. $this->drupalGet('node');
  746. $this->drupalGet('user');
  747. $this->assertCaptchaPresence(TRUE);
  748. $this->assertDifferentCsid($captcha_sid_initial);
  749. // Check another form
  750. $this->drupalGet('user/register');
  751. $this->assertCaptchaPresence(TRUE);
  752. $this->assertDifferentCsid($captcha_sid_initial);
  753. }
  754. function testPersistencePerFormType(){
  755. // Set up of persistence and CAPTCHAs.
  756. $this->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_TYPE);
  757. // Go to login form and check if there is a CAPTCHA on the login form.
  758. $this->drupalGet('user');
  759. $this->assertCaptchaPresence(TRUE);
  760. $captcha_sid_initial = $this->getCaptchaSidFromForm();
  761. // Try to with wrong user name and password, but correct CAPTCHA.
  762. $edit = array(
  763. 'name' => 'foobar',
  764. 'pass' => 'bazlaz',
  765. 'captcha_response' => 'Test 123',
  766. );
  767. $this->drupalPost(NULL, $edit, t('Log in'));
  768. // Check that there was no error message for the CAPTCHA.
  769. $this->assertCaptchaResponseAccepted();
  770. // There shouldn't be a CAPTCHA on the new form.
  771. $this->assertCaptchaPresence(FALSE);
  772. $this->assertPreservedCsid($captcha_sid_initial);
  773. // Start a new form instance/session
  774. $this->drupalGet('node');
  775. $this->drupalGet('user');
  776. $this->assertCaptchaPresence(FALSE);
  777. $this->assertDifferentCsid($captcha_sid_initial);
  778. // Check another form
  779. $this->drupalGet('user/register');
  780. $this->assertCaptchaPresence(TRUE);
  781. $this->assertDifferentCsid($captcha_sid_initial);
  782. }
  783. function testPersistenceOnlyOnce(){
  784. // Set up of persistence and CAPTCHAs.
  785. $this->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL);
  786. // Go to login form and check if there is a CAPTCHA on the login form.
  787. $this->drupalGet('user');
  788. $this->assertCaptchaPresence(TRUE);
  789. $captcha_sid_initial = $this->getCaptchaSidFromForm();
  790. // Try to with wrong user name and password, but correct CAPTCHA.
  791. $edit = array(
  792. 'name' => 'foobar',
  793. 'pass' => 'bazlaz',
  794. 'captcha_response' => 'Test 123',
  795. );
  796. $this->drupalPost(NULL, $edit, t('Log in'));
  797. // Check that there was no error message for the CAPTCHA.
  798. $this->assertCaptchaResponseAccepted();
  799. // There shouldn't be a CAPTCHA on the new form.
  800. $this->assertCaptchaPresence(FALSE);
  801. $this->assertPreservedCsid($captcha_sid_initial);
  802. // Start a new form instance/session
  803. $this->drupalGet('node');
  804. $this->drupalGet('user');
  805. $this->assertCaptchaPresence(FALSE);
  806. $this->assertDifferentCsid($captcha_sid_initial);
  807. // Check another form
  808. $this->drupalGet('user/register');
  809. $this->assertCaptchaPresence(FALSE);
  810. $this->assertDifferentCsid($captcha_sid_initial);
  811. }
  812. }
  813. class CaptchaSessionReuseAttackTestCase extends CaptchaBaseWebTestCase {
  814. public static function getInfo() {
  815. return array(
  816. 'name' => t('CAPTCHA session reuse attack tests'),
  817. 'description' => t('Testing of the protection against CAPTCHA session reuse attacks.'),
  818. 'group' => t('CAPTCHA'),
  819. );
  820. }
  821. /**
  822. * Assert that the CAPTCHA session ID reuse attack was detected.
  823. */
  824. protected function assertCaptchaSessionIdReuseAttackDetection() {
  825. $this->assertText(t(CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE),
  826. 'CAPTCHA session ID reuse attack should be detected.',
  827. 'CAPTCHA');
  828. // There should be an error message about wrong response.
  829. $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
  830. 'CAPTCHA response should flagged as wrong.',
  831. 'CAPTCHA');
  832. }
  833. function testCaptchaSessionReuseAttackDetectionOnCommentPreview() {
  834. // Create commentable node
  835. $node = $this->createNodeWithCommentsEnabled();
  836. // Set Test CAPTCHA on comment form.
  837. captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Math');
  838. variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
  839. // Log in as normal user.
  840. $this->drupalLogin($this->normal_user);
  841. // Go to comment form of commentable node.
  842. $this->drupalGet('comment/reply/' . $node->nid);
  843. $this->assertCaptchaPresence(TRUE);
  844. // Get CAPTCHA session ID and solution of the challenge.
  845. $captcha_sid = $this->getCaptchaSidFromForm();
  846. $captcha_token = $this->getCaptchaTokenFromForm();
  847. $solution = $this->getMathCaptchaSolutionFromForm();
  848. // Post the form with the solution.
  849. $edit = $this->getCommentFormValues();
  850. $edit['captcha_response'] = $solution;
  851. $this->drupalPost(NULL, $edit, t('Preview'));
  852. // Answer should be accepted and further CAPTCHA ommitted.
  853. $this->assertCaptchaResponseAccepted();
  854. $this->assertCaptchaPresence(FALSE);
  855. // Post a new comment, reusing the previous CAPTCHA session.
  856. $edit = $this->getCommentFormValues();
  857. $edit['captcha_sid'] = $captcha_sid;
  858. $edit['captcha_token'] = $captcha_token;
  859. $edit['captcha_response'] = $solution;
  860. $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
  861. // CAPTCHA session reuse attack should be detected.
  862. $this->assertCaptchaSessionIdReuseAttackDetection();
  863. // There should be a CAPTCHA.
  864. $this->assertCaptchaPresence(TRUE);
  865. }
  866. function testCaptchaSessionReuseAttackDetectionOnNodeForm() {
  867. // Set CAPTCHA on page form.
  868. captcha_set_form_id_setting('page_node_form', 'captcha/Math');
  869. variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
  870. // Log in as normal user.
  871. $this->drupalLogin($this->normal_user);
  872. // Go to node add form.
  873. $this->drupalGet('node/add/page');
  874. $this->assertCaptchaPresence(TRUE);
  875. // Get CAPTCHA session ID and solution of the challenge.
  876. $captcha_sid = $this->getCaptchaSidFromForm();
  877. $captcha_token = $this->getCaptchaTokenFromForm();
  878. $solution = $this->getMathCaptchaSolutionFromForm();
  879. // Page settings to post, with correct CAPTCHA answer.
  880. $edit = $this->getNodeFormValues();
  881. $edit['captcha_response'] = $solution;
  882. // Preview the node
  883. $this->drupalPost(NULL, $edit, t('Preview'));
  884. // Answer should be accepted.
  885. $this->assertCaptchaResponseAccepted();
  886. // Check that there is no CAPTCHA after preview.
  887. $this->assertCaptchaPresence(FALSE);
  888. // Post a new comment, reusing the previous CAPTCHA session.
  889. $edit = $this->getNodeFormValues();
  890. $edit['captcha_sid'] = $captcha_sid;
  891. $edit['captcha_token'] = $captcha_token;
  892. $edit['captcha_response'] = $solution;
  893. $this->drupalPost('node/add/page', $edit, t('Preview'));
  894. // CAPTCHA session reuse attack should be detected.
  895. $this->assertCaptchaSessionIdReuseAttackDetection();
  896. // There should be a CAPTCHA.
  897. $this->assertCaptchaPresence(TRUE);
  898. }
  899. function testCaptchaSessionReuseAttackDetectionOnLoginForm() {
  900. // Set CAPTCHA on login form.
  901. captcha_set_form_id_setting('user_login', 'captcha/Math');
  902. variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);
  903. // Go to log in form.
  904. $this->drupalGet('user');
  905. $this->assertCaptchaPresence(TRUE);
  906. // Get CAPTCHA session ID and solution of the challenge.
  907. $captcha_sid = $this->getCaptchaSidFromForm();
  908. $captcha_token = $this->getCaptchaTokenFromForm();
  909. $solution = $this->getMathCaptchaSolutionFromForm();
  910. // Log in through form.
  911. $edit = array(
  912. 'name' => $this->normal_user->name,
  913. 'pass' => $this->normal_user->pass_raw,
  914. 'captcha_response' => $solution,
  915. );
  916. $this->drupalPost(NULL, $edit, t('Log in'));
  917. $this->assertCaptchaResponseAccepted();
  918. $this->assertCaptchaPresence(FALSE);
  919. // If a "log out" link appears on the page, it is almost certainly because
  920. // the login was successful.
  921. $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $this->normal_user->name)), t('User login'));
  922. // Log out again.
  923. $this->drupalLogout();
  924. // Try to log in again, reusing the previous CAPTCHA session.
  925. $edit += array(
  926. 'captcha_sid' => $captcha_sid,
  927. 'captcha_token' => $captcha_token,
  928. );
  929. $this->drupalPost('user', $edit, t('Log in'));
  930. // CAPTCHA session reuse attack should be detected.
  931. $this->assertCaptchaSessionIdReuseAttackDetection();
  932. // There should be a CAPTCHA.
  933. $this->assertCaptchaPresence(TRUE);
  934. }
  935. public function testMultipleCaptchaProtectedFormsOnOnePage()
  936. {
  937. // Set Test CAPTCHA on comment form and login block
  938. captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');
  939. captcha_set_form_id_setting('user_login_block', 'captcha/Math');
  940. $this->allowCommentPostingForAnonymousVisitors();
  941. // Create a node with comments enabled.
  942. $node = $this->createNodeWithCommentsEnabled();
  943. // Preview comment with correct CAPTCHA answer.
  944. $edit = $this->getCommentFormValues();
  945. $comment_subject = $edit['subject'];
  946. $edit['captcha_response'] = 'Test 123';
  947. $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
  948. // Post should be accepted: no warnings,
  949. // no CAPTCHA reuse detection (which could be used by user log in block).
  950. $this->assertCaptchaResponseAccepted();
  951. $this->assertText($comment_subject);
  952. }
  953. }
  954. // Some tricks to debug:
  955. // drupal_debug($data) // from devel module
  956. // file_put_contents('tmp.simpletest.html', $this->drupalGetContent());