CaptchaWebTestBase.php 7.78 KB
Newer Older
1
2
<?php

3
namespace Drupal\Tests\captcha\Functional;
4

5
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
6
use Drupal\comment\Tests\CommentTestTrait;
7
use Drupal\Core\Session\AccountInterface;
8
9
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\BrowserTestBase;
10

11
/**
12
13
14
15
16
17
18
19
20
21
 * The TODO list.
 *
 * @todo write test for CAPTCHAs on admin pages.
 * @todo test for default challenge type.
 * @todo test about placement (comment form, node forms, log in form, etc).
 * @todo test if captcha_cron does it work right.
 * @todo test custom CAPTCHA validation stuff.
 * @todo test if entry on status report (Already X blocked form submissions).
 * @todo test space ignoring validation of image CAPTCHA.
 * @todo refactor the 'comment_body[0][value]' stuff.
22
23
 */

24
25
26
27
28
/**
 * Base class for CAPTCHA tests.
 *
 * Provides common setup stuff and various helper functions.
 */
29
abstract class CaptchaWebTestBase extends BrowserTestBase {
30

31
32
  use CommentTestTrait;

33
34
35
  /**
   * Wrong response error message.
   */
36
37
  const CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE = 'The answer you entered for the CAPTCHA was not correct.';

38
  /**
39
   * Unknown CSID error message.
40
   */
41
42
  const CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE = 'CAPTCHA validation error: unknown CAPTCHA session ID. Contact the site administrator if this problem persists.';

43
44
45
46
47
48
49
50
51
52
53
54
  /**
   * Form ID of comment form on standard (page) node.
   */
  const COMMENT_FORM_ID = 'comment_comment_form';

  const LOGIN_HTML_FORM_ID = 'user-login-form';

  /**
   * Drupal path of the (general) CAPTCHA admin page.
   */
  const CAPTCHA_ADMIN_PATH = 'admin/config/people/captcha';

55
  /**
56
57
58
   * Modules to install for this Test class.
   *
   * @var array
59
   */
60
  public static $modules = ['captcha', 'comment'];
61
62
63
64

  /**
   * User with various administrative permissions.
   *
65
   * @var \Drupal\user\Entity\User
66
67
68
69
70
71
   */
  protected $adminUser;

  /**
   * Normal visitor with limited permissions.
   *
72
   * @var \Drupal\user\Entity\User
73
74
75
76
77
78
79
80
81
82
83
84
   */
  protected $normalUser;

  /**
   * {@inheritdoc}
   */
  public function setUp() {
    // Load two modules: the captcha module itself and the comment
    // module for testing anonymous comments.
    parent::setUp();
    module_load_include('inc', 'captcha');

85
    $this->drupalCreateContentType(['type' => 'page']);
86

87
    // Create a normal user.
88
89
90
91
92
93
94
95
    $permissions = [
      'access comments',
      'post comments',
      'skip comment approval',
      'access content',
      'create page content',
      'edit own page content',
    ];
96
97
98
99
100
101
102
103
104
105
    $this->normalUser = $this->drupalCreateUser($permissions);

    // Create an admin user.
    $permissions[] = 'administer CAPTCHA settings';
    $permissions[] = 'skip CAPTCHA';
    $permissions[] = 'administer permissions';
    $permissions[] = 'administer content types';
    $this->adminUser = $this->drupalCreateUser($permissions);

    // Open comment for page content type.
106
    $this->addDefaultCommentField('node', 'page');
107
108

    // Put comments on page nodes on a separate page.
109
    $comment_field = FieldConfig::loadByName('node', 'page', 'comment');
110
    $comment_field->setSetting('form_location', CommentItemInterface::FORM_SEPARATE_PAGE);
111
    $comment_field->save();
112
113

    /* @var \Drupal\captcha\Entity\CaptchaPoint $captcha_point */
114
115
116
    $captcha_point = \Drupal::entityTypeManager()
      ->getStorage('captcha_point')
      ->load('user_login_form');
117
    $captcha_point->enable()->save();
118
119
120
    $this->config('captcha.settings')
      ->set('default_challenge', 'captcha/test')
      ->save();
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
  }

  /**
   * Assert that the response is accepted.
   *
   * No "unknown CSID" message, no "CSID reuse attack detection" message,
   * No "wrong answer" message.
   */
  protected function assertCaptchaResponseAccepted() {
    // There should be no error message about unknown CAPTCHA session ID.
    $this->assertNoText(self::CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE,
      'CAPTCHA response should be accepted (known CSID).',
      'CAPTCHA'
    );
    // There should be no error message about wrong response.
    $this->assertNoText(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE,
      'CAPTCHA response should be accepted (correct response).',
      'CAPTCHA'
    );
  }

  /**
   * Assert that there is a CAPTCHA on the form or not.
   *
   * @param bool $presence
   *   Whether there should be a CAPTCHA or not.
   */
  protected function assertCaptchaPresence($presence) {
    if ($presence) {
      $this->assertText(_captcha_get_description(),
        'There should be a CAPTCHA on the form.', 'CAPTCHA'
      );
    }
    else {
      $this->assertNoText(_captcha_get_description(),
        'There should be no CAPTCHA on the form.', 'CAPTCHA'
      );
    }
  }

  /**
   * Helper function to generate a form values array for comment forms.
   */
  protected function getCommentFormValues() {
165
    $edit = [
166
167
      'subject[0][value]' => 'comment_subject ' . $this->randomMachineName(32),
      'comment_body[0][value]' => 'comment_body ' . $this->randomMachineName(256),
168
    ];
169
170
171
172
173
174
175
176

    return $edit;
  }

  /**
   * Helper function to generate a form values array for node forms.
   */
  protected function getNodeFormValues() {
177
    $edit = [
178
179
      'title[0][value]' => 'node_title ' . $this->randomMachineName(32),
      'body[0][value]' => 'node_body ' . $this->randomMachineName(256),
180
    ];
181
182
183
184
185
186
187
188
189
190
191
192
193

    return $edit;
  }

  /**
   * Get the CAPTCHA session id from the current form in the browser.
   *
   * @param null|string $form_html_id
   *   HTML form id attribute.
   *
   * @return int
   *   Captcha SID integer.
   */
194
  protected function  getCaptchaSidFromForm($form_html_id = NULL) {
195
196
197
198
199
200
    if (!$form_html_id) {
      $elements = $this->xpath('//input[@name="captcha_sid"]');
    }
    else {
      $elements = $this->xpath('//form[@id="' . $form_html_id . '"]//input[@name="captcha_sid"]');
    }
201
202
203

    $element = current($elements);
    $captcha_sid = (int) $element->getValue();
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223

    return $captcha_sid;
  }

  /**
   * Get the CAPTCHA token from the current form in the browser.
   *
   * @param null|string $form_html_id
   *   HTML form id attribute.
   *
   * @return int
   *   Captcha token integer.
   */
  protected function getCaptchaTokenFromForm($form_html_id = NULL) {
    if (!$form_html_id) {
      $elements = $this->xpath('//input[@name="captcha_token"]');
    }
    else {
      $elements = $this->xpath('//form[@id="' . $form_html_id . '"]//input[@name="captcha_token"]');
    }
224
225
    $element = current($elements);
    $captcha_token = (int) $element->getValue();
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241

    return $captcha_token;
  }

  /**
   * Get the solution of the math CAPTCHA from the current form in the browser.
   *
   * @param null|string $form_html_id
   *   HTML form id attribute.
   *
   * @return int
   *   Calculated Math solution.
   */
  protected function getMathCaptchaSolutionFromForm($form_html_id = NULL) {
    // Get the math challenge.
    if (!$form_html_id) {
242
      $elements = $this->xpath('//div[contains(@class, "form-item-captcha-response")]/span[@class="field-prefix"]');
243
244
    }
    else {
245
      $elements = $this->xpath('//form[@id="' . $form_html_id . '"]//div[contains(@class, "form-item-captcha-response")]/span[@class="field-prefix"]');
246
247
248
249
250
    }
    $this->assert('pass', json_encode($elements));
    $challenge = (string) $elements[0];
    $this->assert('pass', $challenge);
    // Extract terms and operator from challenge.
251
    $matches = [];
252
253
254
255
256
257
258
259
260
261
262
263
264
265
    preg_match('/\\s*(\\d+)\\s*(-|\\+)\\s*(\\d+)\\s*=\\s*/', $challenge, $matches);
    // Solve the challenge.
    $a = (int) $matches[1];
    $b = (int) $matches[3];
    $solution = $matches[2] == '-' ? $a - $b : $a + $b;

    return $solution;
  }

  /**
   * Helper function to allow comment posting for anonymous users.
   */
  protected function allowCommentPostingForAnonymousVisitors() {
    // Enable anonymous comments.
266
    user_role_grant_permissions(AccountInterface::ANONYMOUS_ROLE, [
267
268
269
270
      'access comments',
      'post comments',
      'skip comment approval',
    ]);
271
272
273
  }

}