CaptchaAdminTest.php 16.5 KB
Newer Older
1
2
<?php

3
namespace Drupal\Tests\captcha\Functional;
4

5
use Drupal\captcha\Entity\CaptchaPoint;
6
use Drupal\Core\Url;
7

8
9
10
11
12
/**
 * Tests CAPTCHA admin settings.
 *
 * @group captcha
 */
13
class CaptchaAdminTest extends CaptchaWebTestBase {
14

15
16
17
  /**
   * Test access to the admin pages.
   */
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  public function testAdminAccess() {
    $this->drupalLogin($this->normalUser);
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
    // @TODO do we need this ?
    // file_put_contents('tmp.simpletest.html', $this->drupalGetContent());
    $this->assertText(t('Access denied'), 'Normal users should not be able to access the CAPTCHA admin pages', 'CAPTCHA');

    $this->drupalLogin($this->adminUser);
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
    $this->assertNoText(t('Access denied'), 'Admin users should be able to access the CAPTCHA admin pages', 'CAPTCHA');
  }

  /**
   * Test the CAPTCHA point setting getter/setter.
   */
  public function testCaptchaPointSettingGetterAndSetter() {
    $comment_form_id = self::COMMENT_FORM_ID;
35
    captcha_set_form_id_setting($comment_form_id, 'test');
36
37
38
    /* @var CaptchaPoint $result */
    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
39
    $this->assertEqual($result->getCaptchaType(), 'test', 'CAPTCHA type: default', 'CAPTCHA');
40
41
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
42
    $this->assertEqual($result, 'test', 'Setting and symbolic getting CAPTCHA point: "test"', 'CAPTCHA');
43

44
    // Set to 'default'.
45
    captcha_set_form_id_setting($comment_form_id, 'default');
46
47
48
    $this->config('captcha.settings')
      ->set('default_challenge', 'foo/bar')
      ->save();
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
    $this->assertEqual($result->getCaptchaType(), 'foo/bar', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertNotNull($result, 'Setting and symbolic getting CAPTCHA point: "default"', 'CAPTCHA');
    $this->assertEqual($result, 'foo/bar', 'Setting and symbolic getting CAPTCHA point: default', 'CAPTCHA');

    // Set to 'baz/boo'.
    captcha_set_form_id_setting($comment_form_id, 'baz/boo');
    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
    $this->assertEqual($result->getCaptchaType(), 'baz/boo', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertEqual($result, 'baz/boo', 'Setting and symbolic getting CAPTCHA point: "baz/boo"', 'CAPTCHA');

    // Set to NULL (which should delete the CAPTCHA point setting entry).
    captcha_set_form_id_setting($comment_form_id, NULL);
    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');
    $this->assertEqual($result->getCaptchaType(), 'foo/bar', 'Setting and getting CAPTCHA point: NULL', 'CAPTCHA');
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertNotNull($result, 'CAPTCHA exists', 'CAPTCHA');

    // Set with object.
    $captcha_type = 'baba/fofo';
    captcha_set_form_id_setting($comment_form_id, $captcha_type);

    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNotNull($result, 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
    // $this->assertEqual($result->module, 'baba', 'Setting and getting
79
    // CAPTCHA point: baba/fofo', 'CAPTCHA');.
80
81
82
83
    $this->assertEqual($result->getCaptchaType(), 'baba/fofo', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertEqual($result, 'baba/fofo', 'Setting and symbolic getting CAPTCHA point: "baba/fofo"', 'CAPTCHA');
  }
84

85
86
87
88
89
90
91
  /**
   * Helper function for checking CAPTCHA setting of a form.
   *
   * @param string $form_id
   *   The form_id of the form to investigate.
   * @param string $challenge_type
   *   What the challenge type should be:
92
   *   NULL, 'default' or something like 'captcha/Math'.
93
94
95
96
97
   */
  protected function assertCaptchaSetting($form_id, $challenge_type) {
    $result = captcha_get_form_id_setting(self::COMMENT_FORM_ID, TRUE);
    $this->assertEqual($result, $challenge_type,
      t('Check CAPTCHA setting for form: expected: @expected, received: @received.',
98
        [
99
100
          '@expected' => var_export($challenge_type, TRUE),
          '@received' => var_export($result, TRUE),
101
102
        ]),
      'CAPTCHA');
103
104
105
106
107
108
109
110
111
  }

  /**
   * Testing of the CAPTCHA administration links.
   */
  public function testCaptchaAdminLinks() {
    $this->drupalLogin($this->adminUser);

    // Enable CAPTCHA administration links.
112
    $edit = [
113
      'administration_mode' => TRUE,
114
    ];
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH, $edit, t('Save configuration'));

    // Create a node with comments enabled.
    $node = $this->drupalCreateNode();

    // Go to node page.
    $this->drupalGet('node/' . $node->id());

    // Click the add new comment link.
    $this->clickLink(t('Add new comment'));
    $add_comment_url = $this->getUrl();

    // Remove fragment part from comment URL to avoid
    // problems with later asserts.
    $add_comment_url = strtok($add_comment_url, "#");

    // Click the CAPTCHA admin link to enable a challenge.
    $this->clickLink(t('Place a CAPTCHA here for untrusted users.'));

    // Enable Math CAPTCHA.
136
    $edit = ['captchaType' => 'captcha/Math'];
137
138
    $this->drupalPostForm($this->getUrl(), $edit, t('Save'));
    // Check if returned to original comment form.
139
    $this->assertUrl($add_comment_url, [],
140
141
142
143
      'After setting CAPTCHA with CAPTCHA admin links: should return to original form.', 'CAPTCHA');

    // Check if CAPTCHA was successfully enabled
    // (on CAPTCHA admin links fieldset).
144
    $this->assertText(t('CAPTCHA: challenge "@type" enabled', ['@type' => $edit['captchaType']]),
145
146
147
148
149
150
151
152
153
      'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');

    // Check if CAPTCHA was successfully enabled (through API).
    $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'captcha/Math');

    // Edit challenge type through CAPTCHA admin links.
    $this->clickLink(t('change'));

    // Enable Math CAPTCHA.
154
    $edit = ['captchaType' => 'default'];
155
156
157
158
159
160
161
162
163
164
165
    $this->drupalPostForm($this->getUrl(), $edit, t('Save'));

    // Check if returned to original comment form.
    $this->assertEqual($add_comment_url, $this->getUrl(),
      'After editing challenge type CAPTCHA admin links: should return to original form.', 'CAPTCHA');

    // Check if CAPTCHA was successfully changed
    // (on CAPTCHA admin links fieldset).
    // This is actually the same as the previous setting because
    // the captcha/Math is the default for the default challenge.
    // TODO Make sure the edit is a real change.
166
    $this->assertText(t('CAPTCHA: challenge "@type" enabled', ['@type' => $edit['captchaType']]),
167
168
169
170
171
      'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
    // Check if CAPTCHA was successfully edited (through API).
    $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'default');

    // Disable challenge through CAPTCHA admin links.
172
173
    $this->drupalGet(Url::fromRoute('entity.captcha_point.disable', ['captcha_point' => self::COMMENT_FORM_ID]));
    $this->drupalPostForm(NULL, [], t('Disable'));
174

175
176
177
178
    // Check if returned to captcha point list.
    global $base_url;
    $this->assertEqual($base_url . '/admin/config/people/captcha/captcha-points', $this->getUrl(),
      'After disabling challenge in CAPTCHA admin: should return to captcha point list.', 'CAPTCHA');
179
180
181

    // Check if CAPTCHA was successfully disabled
    // (on CAPTCHA admin links fieldset).
182
    $this->assertRaw(t('Captcha point %form_id has been disabled.', ['%form_id' => self::COMMENT_FORM_ID]),
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
      'Disable challenge through the CAPTCHA admin links', 'CAPTCHA');
  }

  /**
   * Test untrusted user posting.
   */
  public function testUntrustedUserPosting() {
    // Set CAPTCHA on comment form.
    captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Math');

    // Create a node with comments enabled.
    $node = $this->drupalCreateNode();

    // Log in as normal (untrusted) user.
    $this->drupalLogin($this->normalUser);

    // Go to node page and click the "add comment" link.
    $this->drupalGet('node/' . $node->id());
    $this->clickLink(t('Add new comment'));
    $add_comment_url = $this->getUrl();

    // Check if CAPTCHA is visible on form.
    $this->assertCaptchaPresence(TRUE);
    // Try to post a comment with wrong answer.
    $edit = $this->getCommentFormValues();
    $edit['captcha_response'] = 'xx';
    $this->drupalPostForm($add_comment_url, $edit, t('Preview'));
    $this->assertText(self::CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE,
      'wrong CAPTCHA should block form submission.', 'CAPTCHA');
  }

  /**
   * Test XSS vulnerability on CAPTCHA description.
   */
  public function testXssOnCaptchaDescription() {
    // Set CAPTCHA on user register form.
    captcha_set_form_id_setting('user_register', 'captcha/Math');

221
    // Put JavaScript snippet in CAPTCHA description.
222
223
    $this->drupalLogin($this->adminUser);
    $xss = '<script type="text/javascript">alert("xss")</script>';
224
    $edit = ['description' => $xss];
225
226
    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');

227
    // Visit user register form and check if JavaScript snippet is there.
228
229
    $this->drupalLogout();
    $this->drupalGet('user/register');
230
    $this->assertNoRaw($xss, 'JavaScript should not be allowed in CAPTCHA description.', 'CAPTCHA');
231
232
233
234
235
236
237
238
239
240
241
  }

  /**
   * Test the CAPTCHA placement clearing.
   */
  public function testCaptchaPlacementCacheClearing() {
    // Set CAPTCHA on user register form.
    captcha_set_form_id_setting('user_register_form', 'captcha/Math');
    // Visit user register form to fill the CAPTCHA placement cache.
    $this->drupalGet('user/register');
    // Check if there is CAPTCHA placement cache.
242
243
    $placement_map = $this->container->get('cache.default')
      ->get('captcha_placement_map_cache');
244
245
246
    $this->assertNotNull($placement_map, 'CAPTCHA placement cache should be set.');
    // Clear the cache.
    $this->drupalLogin($this->adminUser);
247
    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH, [], t('Clear the CAPTCHA placement cache'));
248
    // Check that the placement cache is unset.
249
250
    $placement_map = $this->container->get('cache.default')
      ->get('captcha_placement_map_cache');
251
252
253
254
255
256
257
258
259
260
    $this->assertFalse($placement_map, 'CAPTCHA placement cache should be unset after cache clear.');
  }

  /**
   * Helper function to get CAPTCHA point setting straight from the database.
   *
   * @param string $form_id
   *   Form machine ID.
   *
   * @return \Drupal\captcha\Entity\CaptchaPoint
261
   *   CaptchaPoint with mysql query result.
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
   */
  protected function getCaptchaPointSettingFromDatabase($form_id) {
    $ids = \Drupal::entityQuery('captcha_point')
      ->condition('formId', $form_id)
      ->execute();
    return $ids ? CaptchaPoint::load(reset($ids)) : NULL;
  }

  /**
   * Method for testing the CAPTCHA point administration.
   */
  public function testCaptchaPointAdministration() {
    // Generate CAPTCHA point data:
    // Drupal form ID should consist of lowercase alphanumerics and underscore).
    $captcha_point_form_id = 'form_' . strtolower($this->randomMachineName(32));
    // The Math CAPTCHA by the CAPTCHA module is always available,
    // so let's use it.
    $captcha_point_module = 'captcha';
    $captcha_point_type = 'Math';

    // Log in as admin.
    $this->drupalLogin($this->adminUser);
284
    $label = 'TEST';
285
286

    // Try and set CAPTCHA point without the #required label. Should fail.
287
    $form_values = [
288
289
      'formId' => $captcha_point_form_id,
      'captchaType' => $captcha_point_module . '/' . $captcha_point_type,
290
    ];
291
    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add', $form_values, t('Save'));
292
293
294
295
296
    $this->assertText(t('Form ID field is required.'));

    // Set CAPTCHA point through admin/user/captcha/captcha/captcha_point.
    $form_values['label'] = $label;
    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add', $form_values, t('Save'));
297
    $this->assertRaw(t('Captcha Point for %label form was created.', ['%label' => $captcha_point_form_id]));
298
299
300
301
302
303
304
305

    // Check in database.
    /* @var CaptchaPoint result */
    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
    $this->assertEqual($result->captchaType, $captcha_point_module . '/' . $captcha_point_type,
      'Enabled CAPTCHA point should have module and type set');

    // Disable CAPTCHA point again.
306
307
    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/disable', [], t('Disable'));
    $this->assertRaw(t('Captcha point %label has been disabled.', ['%label' => $label]), 'Disabling of CAPTCHA point');
308
309
310
311
312

    // Check in database.
    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);

    // Set CAPTCHA point via admin/user/captcha/captcha/captcha_point/$form_id.
313
    $form_values = [
314
      'captchaType' => $captcha_point_module . '/' . $captcha_point_type,
315
    ];
316
    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id, $form_values, t('Save'));
317
    $this->assertRaw(t('Captcha Point for %form_id form was updated.', ['%form_id' => $captcha_point_form_id]), 'Saving of CAPTCHA point settings');
318
319
320
321
322
323
324

    // Check in database.
    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
    $this->assertEqual($result->captchaType, $captcha_point_module . '/' . $captcha_point_type,
      'Enabled CAPTCHA point should have module and type set');

    // Delete CAPTCHA point.
325
326
    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/delete', [], t('Delete'));
    $this->assertRaw(t('Captcha point %label has been deleted.', ['%label' => $label]),
327
      'Deleting of CAPTCHA point');
328
329
330
331
332
333
334
335
336
337
338
339
340

    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
    $this->assertFalse($result, 'Deleted CAPTCHA point should be in database');
  }

  /**
   * Method for testing the CAPTCHA point administration.
   */
  public function testCaptchaPointAdministrationByNonAdmin() {
    // First add a CAPTCHA point (as admin).
    $captcha_point_form_id = 'form_' . strtolower($this->randomMachineName(32));
    $captcha_point_module = 'captcha';
    $captcha_point_type = 'Math';
341
    $label = 'TEST_2';
342
343
344

    $this->drupalLogin($this->adminUser);

345
    $form_values = [
346
      'label' => $label,
347
348
      'formId' => $captcha_point_form_id,
      'captchaType' => $captcha_point_module . '/' . $captcha_point_type,
349
    ];
350
    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/add', $form_values, 'Save');
351
    $this->assertRaw(t('Captcha Point for %form_id form was created.', ['%form_id' => $captcha_point_form_id]));
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378

    // Switch from admin to non-admin.
    $this->drupalLogin($this->normalUser);

    // Try to set CAPTCHA point
    // through admin/user/captcha/captcha/captcha_point.
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points');
    $this->assertText(t('You are not authorized to access this page.'),
      'Non admin should not be able to set a CAPTCHA point');

    // Try to disable the CAPTCHA point.
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/disable');
    $this->assertText(t('You are not authorized to access this page.'),
      'Non admin should not be able to disable a CAPTCHA point');

    // Try to delete the CAPTCHA point.
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/delete');
    $this->assertText(t('You are not authorized to access this page.'),
      'Non admin should not be able to delete a CAPTCHA point');

    // Switch from nonadmin to admin again.
    $this->drupalLogin($this->adminUser);

    // Check if original CAPTCHA point still exists in database.
    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
    $this->assertEqual($result->captchaType, $captcha_point_module . '/' . $captcha_point_type, 'Enabled CAPTCHA point should have module and type set');

379
    // Delete captcha point.
380
381
    $this->drupalPostForm(self::CAPTCHA_ADMIN_PATH . '/captcha-points/' . $captcha_point_form_id . '/delete', [], 'Delete');
    $this->assertRaw(t('Captcha point %label has been deleted.', ['%label' => $label]), 'Disabling of CAPTCHA point');
382
  }
383

384
}