chromium/chrome/test/data/extensions/api_test/webrequest/test_auth_required_parallel/test_auth_required_parallel.js

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Generates a unique authentication URL so each test can run
// without hitting the HTTP authentication cache. Each test
// must use a unique realm, however.
function getURLAuthRequired(realm, subpath = 'subpath') {
  return getServerURL(
      'auth-basic/' + realm + '/' + subpath + '?realm=' + realm);
}

const scriptUrl = '_test_resources/api_test/webrequest/framework.js';
let loadScript = chrome.test.loadScript(scriptUrl);

loadScript.then(async function() {
  runTests([
  // Test that two parallel onAuthRequired signals do not interfere with each
  // other. This is a regression test for https://crbug.com/931479.
  function authRequiredParallel() {
    const realm = 'parallelasync';

    const imgUrl1 = getURLAuthRequired(realm, '1');
    const imgUrl2 = getURLAuthRequired(realm, '2');
    const initiator = getServerDomain(initiators.WEB_INITIATED);
    const parallelAuthRequestsUrl = getServerURL(
        'extensions/api_test/webrequest/auth_parallel?img1=' +
        encodeURIComponent(imgUrl1) + '&img2=' + encodeURIComponent(imgUrl2));

    function createExternallyResolvablePromise() {
      let _resolve;
      const promise = new Promise((resolve, reject) => _resolve = resolve);
      promise.resolve = _resolve;
      return promise;
    }

    // Create some promises that will resolved to callbacks when onAuthRequired
    // and onComplete fire.
    const authRequired1 = createExternallyResolvablePromise();
    const completed1 = createExternallyResolvablePromise();
    const authRequired2 = createExternallyResolvablePromise();

    // responseStarted2 is whether the request for |imgUrl2| has signalled
    // |onResponseStarted| yet.
    let responseStarted2 = false;

    expect(
      [  // events
        { label: 'onBeforeRequest-1',
          event: 'onBeforeRequest',
          details: {
            url: imgUrl1,
            type: 'image',
            initiator: initiator,
            // The testing framework cannot recover the frame URL because the
            // URL filter below does not capture the top-level request.
            frameUrl: 'unknown frame URL',
          },
        },
        { label: 'onBeforeSendHeaders-1',
          event: 'onBeforeSendHeaders',
          details: {
            url: imgUrl1,
            type: 'image',
            initiator: initiator,
            // Note: no requestHeaders because we don't ask for them.
          },
          retval: {}
        },
        { label: 'onSendHeaders-1',
          event: 'onSendHeaders',
          details: {
            url: imgUrl1,
            type: 'image',
            initiator: initiator,
          }
        },
        { label: 'onHeadersReceived-1',
          event: 'onHeadersReceived',
          details: {
            url: imgUrl1,
            type: 'image',
            initiator: initiator,
            responseHeadersExist: true,
            statusLine: 'HTTP/1.1 401 Unauthorized',
            statusCode: 401,
          }
        },
        { label: 'onAuthRequired-1',
          event: 'onAuthRequired',
          details: {
            url: imgUrl1,
            type: 'image',
            initiator: initiator,
            isProxy: false,
            scheme: 'basic',
            realm: realm,
            challenger: {host: testServer, port: testServerPort},
            responseHeadersExist: true,
            statusLine: 'HTTP/1.1 401 Unauthorized',
            statusCode: 401,
          },
          retval_function:
              (name, details, callback) => authRequired1.resolve(callback),
        },
        { label: 'onResponseStarted-1',
          event: 'onResponseStarted',
          details: {
            url: imgUrl1,
            type: 'image',
            initiator: initiator,
            fromCache: false,
            statusCode: 200,
            ip: '127.0.0.1',
            responseHeadersExist: true,
            statusLine: 'HTTP/1.1 200 OK',
          }
        },
        { label: 'onCompleted-1',
          event: 'onCompleted',
          details: {
            url: imgUrl1,
            type: 'image',
            initiator: initiator,
            fromCache: false,
            statusCode: 200,
            ip: '127.0.0.1',
            responseHeadersExist: true,
            statusLine: 'HTTP/1.1 200 OK',
          },
          retval_function: (name, details) => completed1.resolve(),
        },
        { label: 'onBeforeRequest-2',
          event: 'onBeforeRequest',
          details: {
            url: imgUrl2,
            type: 'image',
            initiator: initiator,
            // The testing framework cannot recover the frame URL because the
            // URL filter below does not capture the top-level request.
            frameUrl: 'unknown frame URL',
          },
        },
        { label: 'onBeforeSendHeaders-2',
          event: 'onBeforeSendHeaders',
          details: {
            url: imgUrl2,
            type: 'image',
            initiator: initiator,
            // Note: no requestHeaders because we don't ask for them.
          },
          retval: {}
        },
        { label: 'onSendHeaders-2',
          event: 'onSendHeaders',
          details: {
            url: imgUrl2,
            type: 'image',
            initiator: initiator,
          }
        },
        { label: 'onHeadersReceived-2',
          event: 'onHeadersReceived',
          details: {
            url: imgUrl2,
            type: 'image',
            initiator: initiator,
            responseHeadersExist: true,
            statusLine: 'HTTP/1.1 401 Unauthorized',
            statusCode: 401,
          }
        },
        { label: 'onAuthRequired-2',
          event: 'onAuthRequired',
          details: {
            url: imgUrl2,
            type: 'image',
            initiator: initiator,
            isProxy: false,
            scheme: 'basic',
            realm: realm,
            challenger: {host: testServer, port: testServerPort},
            responseHeadersExist: true,
            statusLine: 'HTTP/1.1 401 Unauthorized',
            statusCode: 401,
          },
          retval_function:
              (name, details, callback) => authRequired2.resolve(callback),
        },
        { label: 'onResponseStarted-2',
          event: 'onResponseStarted',
          details: {
            url: imgUrl2,
            type: 'image',
            initiator: initiator,
            fromCache: false,
            statusCode: 401,
            ip: '127.0.0.1',
            responseHeadersExist: true,
            statusLine: 'HTTP/1.1 401 Unauthorized',
          },
          retval_function: (name, details) => responseStarted2 = true,
        },
        { label: 'onCompleted-2',
          event: 'onCompleted',
          details: {
            url: imgUrl2,
            type: 'image',
            initiator: initiator,
            fromCache: false,
            statusCode: 401,
            ip: '127.0.0.1',
            responseHeadersExist: true,
            statusLine: 'HTTP/1.1 401 Unauthorized',
          }
        },
      ],
      [  // event order
        ['onBeforeRequest-1', 'onBeforeSendHeaders-1', 'onSendHeaders-1',
         'onHeadersReceived-1', 'onAuthRequired-1', 'onResponseStarted-1',
         'onCompleted-1'],
        ['onBeforeRequest-2', 'onBeforeSendHeaders-2', 'onSendHeaders-2',
         'onHeadersReceived-2', 'onAuthRequired-2', 'onResponseStarted-2',
         'onCompleted-2']
      ],
      // Only pay attention to |imgUrl1| and |imgUrl2|, not
      // |parallelAuthRequestsUrl|.
      {urls: ['<all_urls>'], types: ['image']},
      ['responseHeaders', 'asyncBlocking']);

    navigateAndWait(parallelAuthRequestsUrl);
    (async function() {
      // Wait for onAuthRequired to be signaled for both requests before doing
      // anything.
      const callback1 = await authRequired1;
      const callback2 = await authRequired2;

      // Resolve the first request and let it complete.
      callback1({authCredentials: {username: 'foo', password: 'secret'}});
      await completed1;

      // We wish to test that this did not resolve |callback2| with the same
      // credentials internally. This would cause |imgUrl2| to signal
      // |onResponseStarted| and |onCompleted| though it should be blocked.
      chrome.test.assertFalse(responseStarted2);

      // However it's possible that the second request may be much slower than
      // the first, causing the above check to pass even if we don't correctly
      // isolate the two authentication requests.
      //
      // Per the webRequest documentation, |onBeforeSendHeaders| should be
      // signaled after a resolved |onAuthRequired|. This happens without a
      // network delay, so it would make a fairly reliable test. However,
      // webRequest does not match the documentation and does not signal it. See
      // https://crbug.com/809761.
      //
      // Instead, resolve the second request, this time canceling the
      // authentication request. This should result in |imgUrl2| completing with
      // a 401 response. If the credentials were incorrectly propagated from
      // |imgUrl1|, this will be ignored and |imgUrl2| will complete with a 200
      // response.
      callback2({cancel: true});
    })();
  },
])});