tor-browser

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

fedcm-helper.sub.js (11887B)


      1 export const manifest_origin = "https://{{host}}:{{ports[https][0]}}";
      2 export const alt_manifest_origin = 'https://{{hosts[alt][]}}:{{ports[https][0]}}';
      3 export const same_site_manifest_origin = 'https://{{hosts[][www1]}}:{{ports[https][0]}}';
      4 export const default_manifest_path = '/fedcm/support/manifest.py';
      5 
      6 export function open_and_wait_for_popup(origin, path) {
      7  return new Promise(resolve => {
      8    let popup_window = window.open(origin + path);
      9 
     10    // We rely on the popup page to send us a message when done.
     11    const popup_message_handler = (event) => {
     12      // We use new URL() to ensure the two origins are normalized the same
     13      // way (especially so that default ports are handled identically).
     14      if (new URL(event.origin).toString() == new URL(origin).toString()) {
     15        popup_window.close();
     16        window.removeEventListener('message', popup_message_handler);
     17        resolve();
     18      }
     19    };
     20 
     21    window.addEventListener('message', popup_message_handler);
     22  });
     23 }
     24 
     25 // Set the identity provider cookie.
     26 export function set_fedcm_cookie(host) {
     27  if (host == undefined) {
     28    document.cookie = 'cookie=1; SameSite=None; Path=/fedcm/support; Secure';
     29    return Promise.resolve();
     30  } else {
     31    return open_and_wait_for_popup(host, '/fedcm/support/set_cookie');
     32  }
     33 }
     34 
     35 // Set the alternate identity provider cookie.
     36 export function set_alt_fedcm_cookie() {
     37  return set_fedcm_cookie(alt_manifest_origin);
     38 }
     39 
     40 export function setup_accounts_push(origin = manifest_origin) {
     41  return open_and_wait_for_popup(origin, '/fedcm/support/push_accounts');
     42 }
     43 
     44 export function mark_signed_in(origin = manifest_origin) {
     45  return open_and_wait_for_popup(origin, '/fedcm/support/mark_signedin');
     46 }
     47 
     48 export function mark_signed_out(origin = manifest_origin) {
     49  return open_and_wait_for_popup(origin, '/fedcm/support/mark_signedout');
     50 }
     51 
     52 // Returns FedCM CredentialRequestOptions for which navigator.credentials.get()
     53 // succeeds.
     54 export function request_options_with_mediation_required(manifest_filename, origin = manifest_origin) {
     55  if (manifest_filename === undefined) {
     56    manifest_filename = "manifest.py";
     57  }
     58  const manifest_path = `${origin}/\
     59 fedcm/support/${manifest_filename}`;
     60  return {
     61    identity: {
     62      providers: [{
     63        configURL: manifest_path,
     64        clientId: '1',
     65        // TODO(crbug.com/441895082): Move nonce to params when FedCmNonceInParams is enabled by default
     66        nonce: '2'
     67      }]
     68    },
     69    mediation: 'required'
     70  };
     71 }
     72 
     73 // Returns alternate FedCM CredentialRequestOptions for which navigator.credentials.get()
     74 // succeeds.
     75 export function alt_request_options_with_mediation_required(manifest_filename) {
     76  return request_options_with_mediation_required(manifest_filename, alt_manifest_origin);
     77 }
     78 
     79 // Returns FedCM CredentialRequestOptions with auto re-authentication.
     80 // succeeds.
     81 export function request_options_with_mediation_optional(manifest_filename) {
     82  let options = alt_request_options_with_mediation_required(manifest_filename);
     83  // Approved client
     84  options.identity.providers[0].clientId = '123';
     85  options.mediation = 'optional';
     86 
     87  return options;
     88 }
     89 
     90 export function request_options_with_context(manifest_filename, context) {
     91  if (manifest_filename === undefined) {
     92    manifest_filename = "manifest.py";
     93  }
     94  const manifest_path = `${manifest_origin}/\
     95 fedcm/support/${manifest_filename}`;
     96  return {
     97    identity: {
     98      providers: [{
     99        configURL: manifest_path,
    100        clientId: '1',
    101        nonce: '2'
    102      }],
    103      context: context
    104    },
    105    mediation: 'required'
    106  };
    107 }
    108 
    109 export function request_options_with_two_idps(mediation = 'required') {
    110  const first_config = `${manifest_origin}${default_manifest_path}`;
    111  const second_config = `${alt_manifest_origin}${default_manifest_path}`;
    112  return {
    113    identity: {
    114      providers: [{
    115        configURL: first_config,
    116        clientId: '123',
    117        nonce: 'N1'
    118      },
    119      {
    120        configURL: second_config,
    121        clientId: '456',
    122        nonce: 'N2'
    123      }],
    124    },
    125    mediation: mediation
    126  };
    127 }
    128 
    129 // Test wrapper which does FedCM-specific setup.
    130 export function fedcm_test(test_func, test_name) {
    131  promise_test(async t => {
    132    assert_implements(window.IdentityCredential, "FedCM is not supported");
    133 
    134    try {
    135      await navigator.credentials.preventSilentAccess();
    136    } catch (ex) {
    137      // In Chrome's content_shell, the promise will be rejected
    138      // even though the part we care about succeeds.
    139    }
    140 
    141    // Turn off delays that are not useful in tests.
    142    try {
    143      await test_driver.set_fedcm_delay_enabled(false);
    144    } catch (e) {
    145      // Failure is not critical; it just might slow down tests.
    146    }
    147 
    148    // Reset cooldown in case a previous test triggered it.
    149    try {
    150      await test_driver.reset_fedcm_cooldown();
    151    } catch (e) {
    152      // Failure is not critical.
    153    }
    154 
    155    t.add_cleanup(async () => {
    156      // A fedcm_test may affect the connected account set from the IDPs, so invoke
    157      // disconnect as a cleanup.
    158      try {
    159        await IdentityCredential.disconnect(disconnect_options(""));
    160      } catch (ex) {
    161        // Failure is not critical, test state is reset.
    162      }
    163      try {
    164        await IdentityCredential.disconnect(alt_disconnect_options(""));
    165      } catch (ex) {
    166        // Failure is not critical, test state is reset.
    167      }
    168    });
    169 
    170    await mark_signed_in();
    171    await mark_signed_in(alt_manifest_origin);
    172    await set_fedcm_cookie();
    173    await set_alt_fedcm_cookie();
    174    await test_func(t);
    175  }, test_name);
    176 }
    177 
    178 function select_manifest_impl(manifest_url) {
    179  const url_query = (manifest_url === undefined)
    180      ? '' : `?manifest_url=${manifest_url}`;
    181 
    182  return new Promise(resolve => {
    183    const img = document.createElement('img');
    184    img.src = `/fedcm/support/select_manifest_in_root_manifest.py${url_query}`;
    185    img.addEventListener('error', resolve);
    186    document.body.appendChild(img);
    187  });
    188 }
    189 
    190 // Sets the manifest returned by the next fetch of /.well-known/web_identity
    191 // select_manifest() only affects the next fetch and not any subsequent fetches
    192 // (ex second next fetch).
    193 export function select_manifest(test, test_options) {
    194  // Add cleanup in case that /.well-known/web_identity is not fetched at all.
    195  test.add_cleanup(async () => {
    196    await select_manifest_impl();
    197  });
    198  const manifest_url = test_options.identity.providers[0].configURL;
    199  return select_manifest_impl(manifest_url);
    200 }
    201 
    202 export function request_options_with_login_hint(manifest_filename, login_hint) {
    203  let options = request_options_with_mediation_required(manifest_filename);
    204  options.identity.providers[0].loginHint = login_hint;
    205 
    206  return options;
    207 }
    208 
    209 export function request_options_with_domain_hint(manifest_filename, domain_hint) {
    210  let options = request_options_with_mediation_required(manifest_filename);
    211  options.identity.providers[0].domainHint = domain_hint;
    212 
    213  return options;
    214 }
    215 
    216 export function fedcm_get_dialog_type_promise(t) {
    217  return new Promise((resolve, reject) => {
    218    async function helper() {
    219      // Try to get the dialog type. If the UI is not up yet, we'll catch a 'no such alert'
    220      // exception and try again in 10ms. Other exceptions will be rejected.
    221      try {
    222        const type = await window.test_driver.get_fedcm_dialog_type();
    223        resolve(type);
    224      } catch (ex) {
    225        if (String(ex).includes("no such alert")) {
    226          if (t) {
    227            t.step_timeout(helper, 10);
    228          } else{
    229            window.setTimeout(helper, 10);
    230          }
    231        } else {
    232          reject(ex);
    233        }
    234      }
    235    }
    236 
    237    helper();
    238  });
    239 }
    240 
    241 
    242 export async function fedcm_settles_without_dialog(t, cred_promise) {
    243  let dialog_promise = fedcm_get_dialog_type_promise(t);
    244  let result = await Promise.race([cred_promise, dialog_promise]);
    245  // Intentionally pass through any exceptions.
    246  if (result instanceof IdentityCredential) {
    247    return result;
    248  }
    249  throw "expected request to finish, got dialog: " + result;
    250 }
    251 
    252 export async function fedcm_expect_dialog(cred_promise, other_promise) {
    253  let result = await Promise.race([cred_promise, other_promise]);
    254  // If we got an exception, just pass it through, the caller will ensure it is
    255  // the right type.
    256  if (result instanceof IdentityCredential) {
    257    throw "did not expect FedCM request to finish, got token: " + result.token;
    258  }
    259  return result;
    260 }
    261 
    262 export function fedcm_get_title_promise(t) {
    263  return new Promise(resolve => {
    264    async function helper() {
    265      // Try to get the title. If the UI is not up yet, we'll catch an exception
    266      // and try again in 10ms.
    267      try {
    268        const title = await window.test_driver.get_fedcm_dialog_title();
    269        resolve(title);
    270      } catch (ex) {
    271        t.step_timeout(helper, 10);
    272      }
    273    }
    274    helper();
    275  });
    276 }
    277 
    278 export async function fedcm_select_account_promise(t, account_index) {
    279  let type = await fedcm_get_dialog_type_promise(t);
    280  if (type != "AccountChooser")
    281    throw "Incorrect dialog type: " + type;
    282  await window.test_driver.select_fedcm_account(account_index);
    283 }
    284 
    285 export async function fedcm_get_and_select_first_account(t, options) {
    286  const credentialPromise = navigator.credentials.get(options);
    287  let type = await fedcm_expect_dialog(
    288    credentialPromise,
    289    fedcm_get_dialog_type_promise(t)
    290  );
    291  if (type != "AccountChooser")
    292    throw "Incorrect dialog type: " + type;
    293  await window.test_driver.select_fedcm_account(0);
    294  return credentialPromise;
    295 }
    296 
    297 export function fedcm_error_dialog_dismiss(t) {
    298  return new Promise(resolve => {
    299    async function helper() {
    300      // Try to select the account. If the UI is not up yet, we'll catch an exception
    301      // and try again in 10ms.
    302      try {
    303        let type = await fedcm_get_dialog_type_promise(t);
    304        assert_equals(type, "Error");
    305        await window.test_driver.cancel_fedcm_dialog();
    306        resolve();
    307      } catch (ex) {
    308        t.step_timeout(helper, 10);
    309      }
    310    }
    311    helper();
    312  });
    313 }
    314 
    315 export function fedcm_error_dialog_click_button(t, button) {
    316  return new Promise(resolve => {
    317    async function helper() {
    318      // Try to select the account. If the UI is not up yet, we'll catch an exception
    319      // and try again in 10ms.
    320      try {
    321        let type = await fedcm_get_dialog_type_promise(t);
    322        assert_equals(type, "Error");
    323        await window.test_driver.click_fedcm_dialog_button(button);
    324        resolve();
    325      } catch (ex) {
    326        t.step_timeout(helper, 10);
    327      }
    328    }
    329    helper();
    330  });
    331 }
    332 
    333 export function disconnect_options(accountHint, manifest_filename) {
    334  if (manifest_filename === undefined) {
    335    manifest_filename = "manifest.py";
    336  }
    337  const manifest_path = `${manifest_origin}/\
    338 fedcm/support/${manifest_filename}`;
    339  return {
    340      configURL: manifest_path,
    341      clientId: '1',
    342      accountHint: accountHint
    343      };
    344 }
    345 
    346 export function alt_disconnect_options(accountHint, manifest_filename) {
    347  if (manifest_filename === undefined) {
    348    manifest_filename = "manifest.py";
    349  }
    350  const manifest_path = `${alt_manifest_origin}/\
    351 fedcm/support/${manifest_filename}`;
    352  return {
    353      configURL: manifest_path,
    354      clientId: '1',
    355      accountHint: accountHint
    356  };
    357 }
    358 
    359 export async function fedcm_get_flexible_tokens_credential(t, type) {
    360  const options = request_options_with_mediation_required(`manifest_flexible_tokens.json`);
    361  options.identity.providers[0].params = {
    362      "token_type": type
    363  };
    364  await select_manifest(t, options);
    365  return await fedcm_get_and_select_first_account(t, options);
    366 }
    367 
    368 export function set_well_known_format(format_type) {
    369  const url_query = `?format=${encodeURIComponent(format_type)}`;
    370 
    371  return new Promise(resolve => {
    372    const img = document.createElement('img');
    373    img.addEventListener('error', resolve);
    374    img.src = `/fedcm/support/set-well-known-format.py${url_query}`;
    375    document.body.appendChild(img);
    376  });
    377 }