tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

protected-audience-helper-module.js (8295B)


      1 // This file is adapted from /fledge/tentative/resources/fledge-util.js,
      2 // removing unnecessary logic and modifying to allow it to be run in the
      3 // private-aggregation directory.
      4 
      5 "use strict;"
      6 
      7 const FULL_URL = window.location.href;
      8 let BASE_URL = FULL_URL.substring(0, FULL_URL.lastIndexOf('/') + 1)
      9 const BASE_PATH = (new URL(BASE_URL)).pathname;
     10 const DEFAULT_INTEREST_GROUP_NAME = 'default name';
     11 
     12 // Use python script files under fledge directory
     13 const FLEDGE_DIR = '/fledge/tentative/';
     14 const FLEDGE_BASE_URL = BASE_URL.replace(BASE_PATH, FLEDGE_DIR);
     15 
     16 // Sleep method that waits for prescribed number of milliseconds.
     17 const sleep = ms => new Promise(resolve => step_timeout(resolve, ms));
     18 
     19 // Generates a UUID by token.
     20 function generateUuid() {
     21  let uuid = token();
     22  return uuid;
     23 }
     24 
     25 // Creates a URL that will be sent to the handler.
     26 // `uuid` is used to identify the stash shard to use.
     27 // `operate` is used to set action as write or read.
     28 // `report` is used to carry the message for write requests.
     29 function createReportingURL(uuid, operation, report = 'default-report') {
     30  let url = new URL(`${window.location.origin}${BASE_PATH}resources/protected_audience_event_level_report_handler.py`);
     31  url.searchParams.append('uuid', uuid);
     32  url.searchParams.append('operation', operation);
     33 
     34  if (report)
     35    url.searchParams.append('report', report);
     36 
     37  return url.toString();
     38 }
     39 
     40 function createWritingURL(uuid, report) {
     41  return createReportingURL(uuid, 'write');
     42 }
     43 
     44 function createReadingURL(uuid) {
     45  return createReportingURL(uuid, 'read');
     46 }
     47 
     48 async function waitForObservedReports(uuid, expectedNumReports, timeout = 5000 /*ms*/) {
     49  expectedReports = Array(expectedNumReports).fill('default-report');
     50  const reportURL = createReadingURL(uuid);
     51  let startTime = performance.now();
     52 
     53  while (performance.now() - startTime < timeout) {
     54    let response = await fetch(reportURL, { credentials: 'omit', mode: 'cors' });
     55    let actualReports = await response.json();
     56 
     57    // If expected number of reports have been observed, compare with list of
     58    // all expected reports and exit.
     59    if (actualReports.length == expectedReports.length) {
     60      assert_array_equals(actualReports.sort(), expectedReports);
     61      return;
     62    }
     63 
     64    await sleep(/*ms=*/ 100);
     65  }
     66  assert_unreached("Report fetching timed out: " + uuid);
     67 }
     68 
     69 // Creates a bidding script with the provided code in the method bodies. The
     70 // bidding script's generateBid() method will return a bid of 9 for the first
     71 // ad, after the passed in code in the "generateBid" input argument has been
     72 // run, unless it returns something or throws.
     73 //
     74 // The default reportWin() method is empty.
     75 function createBiddingScriptURL(params = {}) {
     76  let url = new URL(`${FLEDGE_BASE_URL}resources/bidding-logic.sub.py`);
     77  if (params.generateBid)
     78    url.searchParams.append('generateBid', params.generateBid);
     79  if (params.reportWin)
     80    url.searchParams.append('reportWin', params.reportWin);
     81  if (params.error)
     82    url.searchParams.append('error', params.error);
     83  if (params.bid)
     84    url.searchParams.append('bid', params.bid);
     85  return url.toString();
     86 }
     87 
     88 // Creates a decision script with the provided code in the method bodies. The
     89 // decision script's scoreAd() method will reject ads with renderURLs that
     90 // don't ends with "uuid", and will return a score equal to the bid, after the
     91 // passed in code in the "scoreAd" input argument has been run, unless it
     92 // returns something or throws.
     93 //
     94 // The default reportResult() method is empty.
     95 function createDecisionScriptURL(uuid, params = {}) {
     96  let url = new URL(`${FLEDGE_BASE_URL}resources/decision-logic.sub.py`);
     97  url.searchParams.append('uuid', uuid);
     98  if (params.scoreAd)
     99    url.searchParams.append('scoreAd', params.scoreAd);
    100  if (params.reportResult)
    101    url.searchParams.append('reportResult', params.reportResult);
    102  if (params.error)
    103    url.searchParams.append('error', params.error);
    104  return url.toString();
    105 }
    106 
    107 // Creates a renderURL for an ad that runs the passed in "script". "uuid" has
    108 // no effect, beyond making the URL distinct between tests, and being verified
    109 // by the decision logic script before accepting a bid. "uuid" is expected to
    110 // be last.
    111 function createRenderURL(uuid, script) {
    112  let url = new URL(`${FLEDGE_BASE_URL}resources/fenced-frame.sub.py`);
    113  if (script)
    114    url.searchParams.append('script', script);
    115  url.searchParams.append('uuid', uuid);
    116  return url.toString();
    117 }
    118 
    119 // Joins an interest group that, by default, is owned by the current frame's
    120 // origin, is named DEFAULT_INTEREST_GROUP_NAME, has a bidding script that
    121 // issues a bid of 9 with a renderURL of "https://not.checked.test/${uuid}".
    122 // `interestGroupOverrides` is required to override fields in the joined
    123 // interest group.
    124 async function joinInterestGroup(test, uuid, interestGroupOverrides) {
    125  const INTEREST_GROUP_LIFETIME_SECS = 60;
    126 
    127  let interestGroup = {
    128    owner: window.location.origin,
    129    name: DEFAULT_INTEREST_GROUP_NAME,
    130    ads: [{renderURL: createRenderURL(uuid)}],
    131    ...interestGroupOverrides
    132  };
    133 
    134  await navigator.joinAdInterestGroup(interestGroup,
    135                                      INTEREST_GROUP_LIFETIME_SECS);
    136  test.add_cleanup(
    137      async () => {await navigator.leaveAdInterestGroup(interestGroup)});
    138 }
    139 
    140 // Runs a FLEDGE auction and returns the result. `auctionConfigOverrides` is
    141 // required to override fields in the auction configuration.
    142 async function runBasicFledgeAuction(test, uuid, auctionConfigOverrides) {
    143  let auctionConfig = {
    144    seller: window.location.origin,
    145    interestGroupBuyers: [window.location.origin],
    146    resolveToConfig: true,
    147    ...auctionConfigOverrides
    148  };
    149  return await navigator.runAdAuction(auctionConfig);
    150 }
    151 
    152 // Calls runBasicFledgeAuction(), expecting the auction to have a winner.
    153 // Creates a fenced frame that will be destroyed on completion of "test", and
    154 // navigates it to the URN URL returned by the auction. Does not wait for the
    155 // fenced frame to finish loading, since there's no API that can do that.
    156 async function runBasicFledgeAuctionAndNavigate(test, uuid,
    157  auctionConfigOverrides) {
    158  let config = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides);
    159  assert_true(config instanceof FencedFrameConfig,
    160      `Wrong value type returned from auction: ${config.constructor.type}`);
    161 
    162  let fencedFrame = document.createElement('fencedframe');
    163  fencedFrame.mode = 'opaque-ads';
    164  fencedFrame.config = config;
    165  document.body.appendChild(fencedFrame);
    166  test.add_cleanup(() => { document.body.removeChild(fencedFrame); });
    167 }
    168 
    169 // Joins an interest group and runs an auction, expecting no winner to be
    170 // returned. "testConfig" can optionally modify the interest group or
    171 // auctionConfig.
    172 async function runBasicFledgeTestExpectingNoWinner(test, testConfig) {
    173  const uuid = generateUuid(test);
    174  await joinInterestGroup(test, uuid, testConfig.interestGroupOverrides);
    175  let result = await runBasicFledgeAuction(
    176      test, uuid, testConfig.auctionConfigOverrides);
    177  assert_true(result === null, 'Auction unexpectedly had a winner');
    178 }
    179 
    180 // Test helper for report phase of auctions that lets the caller specify the
    181 // body of scoreAd(), reportResult(), generateBid() and reportWin(), as well as
    182 // additional arguments to be passed to joinAdInterestGroup() and runAdAuction()
    183 async function runReportTest(test, uuid, codeToInsert,
    184                             expectedNumReports = 0, overrides = {}) {
    185  let generateBid = codeToInsert.generateBid;
    186  let scoreAd = codeToInsert.scoreAd;
    187  let reportWin = codeToInsert.reportWin;
    188  let reportResult = codeToInsert.reportResult;
    189 
    190  let extraInterestGroupOverrides = overrides.joinAdInterestGroup || {}
    191  let extraAuctionConfigOverrides = overrides.runAdAuction || {}
    192 
    193  let interestGroupOverrides = {
    194    biddingLogicURL: createBiddingScriptURL({ generateBid, reportWin }),
    195    ...extraInterestGroupOverrides
    196  };
    197  let auctionConfigOverrides = {
    198    decisionLogicURL: createDecisionScriptURL(
    199      uuid, { scoreAd, reportResult }),
    200    ...extraAuctionConfigOverrides
    201  }
    202 
    203  await joinInterestGroup(test, uuid, interestGroupOverrides);
    204  await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfigOverrides);
    205 
    206  if (expectedNumReports) {
    207    await waitForObservedReports(uuid, expectedNumReports);
    208  }
    209 }