Provide setting to capture credentials if site uses HTTP authentication like Shield

Created on 18 January 2023, almost 2 years ago
Updated 28 August 2024, 3 months ago

Problem/Motivation

When running internal link checks on a site that uses HTTP authentication to prevent public access, all internal link checks fail with a 401 response.

Steps to reproduce

1. Install and configure the Shield module
2. Set up LinkChecker to crawl and test internal links
3. Inspect the Broken Links report

Proposed resolution

Provide username/password fields in the LinkChecker configuration form to capture credentials used by the HTTP Authentication service blocking the requests.

LinkChecker can then use these when submitting requests to internal URLs (although this would slow the requests down noticeably).

User interface changes

Add fields to capture credentials to LinkChecker UI.

✨ Feature request
Status

Needs review

Version

2.0

Component

Code

Created by

πŸ‡¦πŸ‡ΊAustralia timfletcher

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • I have this issue, but rather than using Shield I have a site with basic authentication configured via Nginx.

  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    This would be very helpful for decoupled sites with a static frontend and a locked-down backend.

  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    Seems like it might be easy to add the settings and then use them below in the headers something like:$headers['Authorization'] = 'Basic ' . base64_encode($user . ":" . $pass); for internal links

      public function check(LinkCheckerLinkInterface $link) {
        $userAgent = $this->linkcheckerSetting->get('check.useragent');
    
        $headers = [];
        $headers['User-Agent'] = $userAgent;
    
        $uri = @parse_url($link->getUrl());
    
        // URL contains a fragment.
        if (in_array($link->getRequestMethod(), ['HEAD', 'GET']) && !empty($uri['fragment'])) {
          // We need the full content and not only the HEAD.
          $link->setRequestMethod('GET');
          // Request text content only (like Firefox/Chrome).
          $headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
        }
        elseif ($link->getRequestMethod() == 'GET') {
          // Range: Only request the first 1024 bytes from remote server. This is
          // required to prevent timeouts on URLs that are large downloads.
          $headers['Range'] = 'bytes=0-1024';
        }
    
        // Add in the headers.
        $options = [
          'headers' => $headers,
          'max_redirects' => 0,
          'http_errors' => FALSE,
          'allow_redirects' => FALSE,
          'synchronous' => FALSE,
        ];
    
        return $this->httpClient
          ->requestAsync($link->getRequestMethod(), $link->getUrl(), $options)
          ->then(function (ResponseInterface $response) use ($link, $uri) {
            if (!empty($uri['fragment'])) {
              $response = $response->withHeader('Fragment', $uri['fragment']);
            }
            $this->statusHandling($response, $link);
          },
          function (RequestException $e) use ($link) {
            $this->exceptionHandling($e, $link);
          }
        );
      }
    

    Thanks to @stooit for the internal discussion on this.

  • Status changed to Needs review about 1 year ago
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 8.0 & MySQL 5.7
    last update about 1 year ago
    86 pass
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    Here's a start. I didn't add any tests.

  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    Here are some screenshots for before and after:

    Before, there are lots of 401 errors

    After, there the 401 errors are gone

    New fields

    Form error checking

    Logging

  • Assigned to Kristen Pol
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    I'll reroll this for the 2.0.x branch so it can be used for Drupal 10 as well.

  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    Actually, it is still applying (with offets/fuzz) but I'll reroll so it's a clean apply.

    Kristens-MacBook-Pro:linkchecker kristenpol$ patch -p1 < 3334357-linkchecker-basic-auth.patch 
    patching file config/install/linkchecker.settings.yml
    patching file config/schema/linkchecker.schema.yml
    patching file linkchecker.install
    patching file src/Form/LinkCheckerAdminSettingsForm.php
    Hunk #1 succeeded at 183 (offset -22 lines).
    Hunk #2 succeeded at 459 with fuzz 2 (offset -141 lines).
    Hunk #3 succeeded at 486 (offset -141 lines).
    patching file src/LinkCheckerService.php
    Hunk #1 succeeded at 153 (offset 11 lines).
    Hunk #2 succeeded at 269 (offset 20 lines).
    
  • Status changed to Needs work about 1 year ago
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    I rerolled it but there's an issue when the basic auth is in the settings because then the Clear link data... button isn't working so I'm debugging.

  • Status changed to Needs review about 1 year ago
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    CI aborted
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    The behavior is different between D9 and D10.

    D9: If you click the Clear link data... button, you don't need to fill in the basic auth password.

    D10: If you click the Clear link data... button, you do need to fill in the basic auth password. It does show an error (which I didn't notice the first time since the behavior is different).

    Since it does provide an error and it works when you put the basic auth password in, then it seems fine as is.

    Note that we could change this so that the password is clear text given that the Shield module uses clear text for its password. If this was clear text, then we wouldn't have this problem because the password would show up in the form after you provide it.

  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 10.0.7 + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    86 pass
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    86 pass
  • Issue was unassigned.
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    Ran tests against 9.5 and 10.0 which have passed.

  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    A nice enhancement to this would be to pull the settings from the Shield module if it's installed and configured.

  • Assigned to Kristen Pol
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    After an internal discussion with @stooit, I'll update the password field to clear text to match the Shield module behavior.

  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    64 pass, 32 fail
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    Here's a 2.0.x patch that has the password in clear text and adds an option to use the Shield settings.

  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 10.0.5 + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    64 pass, 32 fail
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    64 pass, 32 fail
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    Whoops. Removed debugging in this patch.

    If Shield is enabled, you'll see the new option.

  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 10.0.5 + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    64 pass, 32 fail
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    64 pass, 32 fail
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    Added a minor tweak to handle if the shield module is installed but disabled in the settings: && \Drupal::config('shield.settings')->get('shield_enable')

  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 10.0.7 + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    64 pass, 32 fail
  • Issue was unassigned.
  • Open in Jenkins β†’ Open on Drupal.org β†’
    Core: 9.5.x + Environment: PHP 8.1 & MySQL 8
    last update about 1 year ago
    Patch Failed to Apply
  • πŸ‡ΊπŸ‡ΈUnited States Kristen Pol Santa Cruz, CA, USA

    Here's a patch for the 8.x-1.1 branch.

  • The last submitted patch, 15: 3334357-linkchecker-basic-auth-2-0-x-15.patch, failed testing. View results β†’
    - codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

  • The last submitted patch, 16: 3334357-linkchecker-basic-auth-2-0-x-16.patch, failed testing. View results β†’
    - codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

  • The last submitted patch, 17: 3334357-linkchecker-basic-auth-2-0-x-17.patch, failed testing. View results β†’
    - codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

  • πŸ‡§πŸ‡·Brazil jkamizato

    Hi everyone,

    I've created simplest approach, skipping new fields, and configuration. It's passing basic authorization inline, like "login:pass@host.com" in Base Path field.

  • Status changed to Needs work 3 months ago
  • @jkamizato It might be simple, but it's not secure. Credentials should not be stored in plaintext, and that method is going to do so.

  • Back to Needs Work because patch didn't apply and tests failing. Also, the changes need to be converted to a merge request so that it uses GitLab CI.

  • πŸ‡§πŸ‡·Brazil jkamizato

    Hi @solideogloria, thanks for your advice.

    I've recreated this patch using key module dependecy ( https://www.drupal.org/project/key β†’ )

    Now, admin user can create a new key entry (user/pass) and reference it in linkchecker configuration.

  • πŸ‡§πŸ‡·Brazil jkamizato

    Sorry, wrong patch file. I'm updating the correct file.

  • Status changed to Needs review 3 months ago
  • πŸ‡§πŸ‡·Brazil jkamizato

    Changed status to "Needs Review"

  • πŸ‡³πŸ‡΄Norway eiriksm Norway

    That sounds very nice!

    Could you create a merge request instead of a patch too? This is the only way to trigger the tests πŸ€“βœŒοΈ

  • πŸ‡§πŸ‡·Brazil jkamizato

    Update:
    Fix "Undefined variable $authUsername" error

  • πŸ‡§πŸ‡·Brazil jkamizato

    @eiriksm Merge Request created

  • Pipeline finished with Failed
    3 months ago
    Total: 290s
    #261609
  • PHPStan failing

     ------ ---------------------------------------------------------------------- 
      Line   src/Form/LinkCheckerAdminSettingsForm.php                             
     ------ ---------------------------------------------------------------------- 
      209    \Drupal calls should be avoided in classes, use dependency injection  
             instead                                                               
     ------ ---------------------------------------------------------------------- 
     ------ ---------------------------------------------------------------------- 
      Line   src/LinkCheckerService.php                                            
     ------ ---------------------------------------------------------------------- 
      161    \Drupal calls should be avoided in classes, use dependency injection  
             instead                                                               
      169    \Drupal calls should be avoided in classes, use dependency injection  
             instead                                                               
      170    \Drupal calls should be avoided in classes, use dependency injection  
             instead                                                               
      181    \Drupal calls should be avoided in classes, use dependency injection  
             instead                                                               
     ------ ---------------------------------------------------------------------- 
    
  • Pipeline finished with Failed
    3 months ago
    Total: 167s
    #262991
  • Pipeline finished with Failed
    3 months ago
    Total: 205s
    #262998
  • πŸ‡§πŸ‡·Brazil jkamizato

    I know it needs to fix phpunit issue, but here the latest patch version with phpstan and phpcs fixes

  • Pipeline finished with Failed
    3 months ago
    Total: 251s
    #266536
  • Pipeline finished with Failed
    3 months ago
    #266548
  • Pipeline finished with Success
    3 months ago
    Total: 253s
    #266553
  • πŸ‡§πŸ‡·Brazil jkamizato

    The phpunit test was fixed and I'm attaching the patch.

Production build 0.71.5 2024