diff --git a/config/install/redirect.settings.yml b/config/install/redirect.settings.yml index 11cb0de..93517e3 100644 --- a/config/install/redirect.settings.yml +++ b/config/install/redirect.settings.yml @@ -7,3 +7,4 @@ global_clean: true global_admin_paths: false global_home: true global_deslash: false +loop_threshold: 5 diff --git a/config/schema/redirect.schema.yml b/config/schema/redirect.schema.yml index f47038e..209e63a 100644 --- a/config/schema/redirect.schema.yml +++ b/config/schema/redirect.schema.yml @@ -31,3 +31,6 @@ redirect.settings: global_deslash: type: boolean label: 'Remove trailing slashes from paths.' + loop_threshold: + type: integer + label: 'Number of redirects in a 15-second period that will determine a redirect loop.' diff --git a/src/Form/RedirectSettingsForm.php b/src/Form/RedirectSettingsForm.php index 847402e..39a1707 100644 --- a/src/Form/RedirectSettingsForm.php +++ b/src/Form/RedirectSettingsForm.php @@ -55,6 +55,14 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#options' => redirect_status_code_options(), '#default_value' => $config->get('default_status_code'), ); + $form['redirect_loop_threshold'] = [ + '#type' => 'select', + '#title' => $this->t('Loop detection threshold'), + '#description' => $this->t('Number of repeated redirects from a single user in a 15-second time period that will cause a redirect loop to be detected.'), + '#options' => [0 => $this->t('Disable loop detection'), 5 => 5, 10 => 10, 15 => 15], + '#default_value' => $config->get('loop_threshold'), + ]; + $form['globals'] = array( '#type' => 'fieldset', '#title' => $this->t('Global redirects'), @@ -102,7 +110,6 @@ public function submitForm(array &$form, FormStateInterface $form_state) { } } $config->save(); - redirect_page_cache_clear(); drupal_set_message(t('Configuration was saved.')); } diff --git a/src/RedirectChecker.php b/src/RedirectChecker.php index b567f92..cecab03 100644 --- a/src/RedirectChecker.php +++ b/src/RedirectChecker.php @@ -86,13 +86,17 @@ public function canRedirect(Request $request) { * TRUE if a redirect loop has been identified. */ public function isLoop(Request $request) { - $hash = Crypt::hashBase64('redirection' . $request->getRequestUri()); - if ($this->flood->isAllowed($hash, 5, 15)) { - $this->flood->register($hash, 60); + if (!$this->config->get('loop_threshold')) { + // Loop detection is disabled. + return FALSE; + } + if ($this->flood->isAllowed('redirection', $this->config->get('loop_threshold'), 15)) { + $this->flood->register('redirection', 60); return FALSE; } else { return TRUE; } } + } diff --git a/src/Tests/RedirectUITest.php b/src/Tests/RedirectUITest.php index 871543f..ad993f5 100644 --- a/src/Tests/RedirectUITest.php +++ b/src/Tests/RedirectUITest.php @@ -306,39 +306,34 @@ function testRedirectLoop() { $log = reset($log); $this->assertEqual($log->severity, RfcLogLevel::WARNING); $this->assertEqual(SafeMarkup::format($log->message, unserialize($log->variables)), - SafeMarkup::format('Redirect loop identified at %path for redirect %id', array('%path' => '/node', '%id' => $redirect1->id()))); - } - - // Test flood protection. - // Create 20 redirects. - $redirects = []; - for ($i = 0; $i < 20; $i++) { - $redirect = $this->storage->create(); - $redirect->setSource('test-redirect' . $i); - $redirect->setRedirect('node'); - $redirect->setStatusCode(301); - $redirect->save(); - $redirects[$i] = $redirect; + SafeMarkup::format('Redirect loop identified at %path for redirect %id', array('%path' => '/admin', '%id' => $redirect2->id()))); } + } - // Hit the first redirect 10 times, should result in a loop. - for ($i = 0; $i < 10; $i++) { - // Should detect a redirect after 4. - if ($i > 4) { - $this->drupalGet('test-redirect0'); + /** + * Test loop detection threshold. + */ + public function testLoopThreshold() { + $redirect = $this->storage->create(); + $redirect->setSource('test-redirect'); + $redirect->setRedirect('node'); + $redirect->setStatusCode(301); + $redirect->save(); + + // Set loop threshold to 3. + \Drupal::configFactory()->getEditable('redirect.settings')->set('loop_threshold', 3)->save(); + // Hit the first redirect 5 times, should result in a loop. + for ($i = 0; $i < 5; $i++) { + // Should detect a redirect loop after 3. + if ($i > 2) { + $this->drupalGet('test-redirect'); $this->assertText('Service unavailable'); $this->assertResponse(503); } else { - $this->assertRedirect('test-redirect0', 'node'); + $this->assertRedirect('test-redirect', 'node'); } } - unset($redirects[0]); - - // Hitting each different redirect should work. - foreach ($redirects as $i => $redirect) { - $this->assertRedirect('test-redirect' . $i, 'node'); - } } /**