export const manifest_origin = "https://{{host}}:{{ports[https][0]}}"; export const alt_manifest_origin = 'https://{{hosts[alt][]}}:{{ports[https][0]}}'; export const same_site_manifest_origin = 'https://{{hosts[][www1]}}:{{ports[https][0]}}'; export const default_manifest_path = '/fedcm/support/manifest.py'; export function open_and_wait_for_popup(origin, path) { return new Promise(resolve => { let popup_window = window.open(origin + path); // We rely on the popup page to send us a message when done. const popup_message_handler = (event) => { // We use new URL() to ensure the two origins are normalized the same // way (especially so that default ports are handled identically). if (new URL(event.origin).toString() == new URL(origin).toString()) { popup_window.close(); window.removeEventListener('message', popup_message_handler); resolve(); } }; window.addEventListener('message', popup_message_handler); }); } // Set the identity provider cookie. export function set_fedcm_cookie(host) { if (host == undefined) { document.cookie = 'cookie=1; SameSite=None; Path=/fedcm/support; Secure'; return Promise.resolve(); } else { return open_and_wait_for_popup(host, '/fedcm/support/set_cookie'); } } // Set the alternate identity provider cookie. export function set_alt_fedcm_cookie() { return set_fedcm_cookie(alt_manifest_origin); } export function setup_accounts_push(origin = manifest_origin) { return open_and_wait_for_popup(origin, '/fedcm/support/push_accounts'); } export function mark_signed_in(origin = manifest_origin) { return open_and_wait_for_popup(origin, '/fedcm/support/mark_signedin'); } export function mark_signed_out(origin = manifest_origin) { return open_and_wait_for_popup(origin, '/fedcm/support/mark_signedout'); } // Returns FedCM CredentialRequestOptions for which navigator.credentials.get() // succeeds. export function request_options_with_mediation_required(manifest_filename, origin = manifest_origin) { if (manifest_filename === undefined) { manifest_filename = "manifest.py"; } const manifest_path = `${origin}/\ fedcm/support/${manifest_filename}`; return { identity: { providers: [{ configURL: manifest_path, clientId: '1', // TODO(crbug.com/441895082): Move nonce to params when FedCmNonceInParams is enabled by default nonce: '2' }] }, mediation: 'required' }; } // Returns alternate FedCM CredentialRequestOptions for which navigator.credentials.get() // succeeds. export function alt_request_options_with_mediation_required(manifest_filename) { return request_options_with_mediation_required(manifest_filename, alt_manifest_origin); } // Returns FedCM CredentialRequestOptions with auto re-authentication. // succeeds. export function request_options_with_mediation_optional(manifest_filename) { let options = alt_request_options_with_mediation_required(manifest_filename); // Approved client options.identity.providers[0].clientId = '123'; options.mediation = 'optional'; return options; } export function request_options_with_context(manifest_filename, context) { if (manifest_filename === undefined) { manifest_filename = "manifest.py"; } const manifest_path = `${manifest_origin}/\ fedcm/support/${manifest_filename}`; return { identity: { providers: [{ configURL: manifest_path, clientId: '1', nonce: '2' }], context: context }, mediation: 'required' }; } export function request_options_with_two_idps(mediation = 'required') { const first_config = `${manifest_origin}${default_manifest_path}`; const second_config = `${alt_manifest_origin}${default_manifest_path}`; return { identity: { providers: [{ configURL: first_config, clientId: '123', nonce: 'N1' }, { configURL: second_config, clientId: '456', nonce: 'N2' }], }, mediation: mediation }; } // Test wrapper which does FedCM-specific setup. export function fedcm_test(test_func, test_name) { promise_test(async t => { assert_implements(window.IdentityCredential, "FedCM is not supported"); try { await navigator.credentials.preventSilentAccess(); } catch (ex) { // In Chrome's content_shell, the promise will be rejected // even though the part we care about succeeds. } // Turn off delays that are not useful in tests. try { await test_driver.set_fedcm_delay_enabled(false); } catch (e) { // Failure is not critical; it just might slow down tests. } // Reset cooldown in case a previous test triggered it. try { await test_driver.reset_fedcm_cooldown(); } catch (e) { // Failure is not critical. } t.add_cleanup(async () => { // A fedcm_test may affect the connected account set from the IDPs, so invoke // disconnect as a cleanup. try { await IdentityCredential.disconnect(disconnect_options("")); } catch (ex) { // Failure is not critical, test state is reset. } try { await IdentityCredential.disconnect(alt_disconnect_options("")); } catch (ex) { // Failure is not critical, test state is reset. } }); await mark_signed_in(); await mark_signed_in(alt_manifest_origin); await set_fedcm_cookie(); await set_alt_fedcm_cookie(); await test_func(t); }, test_name); } function select_manifest_impl(manifest_url) { const url_query = (manifest_url === undefined) ? '' : `?manifest_url=${manifest_url}`; return new Promise(resolve => { const img = document.createElement('img'); img.src = `/fedcm/support/select_manifest_in_root_manifest.py${url_query}`; img.addEventListener('error', resolve); document.body.appendChild(img); }); } // Sets the manifest returned by the next fetch of /.well-known/web_identity // select_manifest() only affects the next fetch and not any subsequent fetches // (ex second next fetch). export function select_manifest(test, test_options) { // Add cleanup in case that /.well-known/web_identity is not fetched at all. test.add_cleanup(async () => { await select_manifest_impl(); }); const manifest_url = test_options.identity.providers[0].configURL; return select_manifest_impl(manifest_url); } export function request_options_with_login_hint(manifest_filename, login_hint) { let options = request_options_with_mediation_required(manifest_filename); options.identity.providers[0].loginHint = login_hint; return options; } export function request_options_with_domain_hint(manifest_filename, domain_hint) { let options = request_options_with_mediation_required(manifest_filename); options.identity.providers[0].domainHint = domain_hint; return options; } export function fedcm_get_dialog_type_promise(t) { return new Promise((resolve, reject) => { async function helper() { // Try to get the dialog type. If the UI is not up yet, we'll catch a 'no such alert' // exception and try again in 10ms. Other exceptions will be rejected. try { const type = await window.test_driver.get_fedcm_dialog_type(); resolve(type); } catch (ex) { if (String(ex).includes("no such alert")) { if (t) { t.step_timeout(helper, 10); } else{ window.setTimeout(helper, 10); } } else { reject(ex); } } } helper(); }); } export async function fedcm_settles_without_dialog(t, cred_promise) { let dialog_promise = fedcm_get_dialog_type_promise(t); let result = await Promise.race([cred_promise, dialog_promise]); // Intentionally pass through any exceptions. if (result instanceof IdentityCredential) { return result; } throw "expected request to finish, got dialog: " + result; } export async function fedcm_expect_dialog(cred_promise, other_promise) { let result = await Promise.race([cred_promise, other_promise]); // If we got an exception, just pass it through, the caller will ensure it is // the right type. if (result instanceof IdentityCredential) { throw "did not expect FedCM request to finish, got token: " + result.token; } return result; } export function fedcm_get_title_promise(t) { return new Promise(resolve => { async function helper() { // Try to get the title. If the UI is not up yet, we'll catch an exception // and try again in 10ms. try { const title = await window.test_driver.get_fedcm_dialog_title(); resolve(title); } catch (ex) { t.step_timeout(helper, 10); } } helper(); }); } export async function fedcm_select_account_promise(t, account_index) { let type = await fedcm_get_dialog_type_promise(t); if (type != "AccountChooser") throw "Incorrect dialog type: " + type; await window.test_driver.select_fedcm_account(account_index); } export async function fedcm_get_and_select_first_account(t, options) { const credentialPromise = navigator.credentials.get(options); let type = await fedcm_expect_dialog( credentialPromise, fedcm_get_dialog_type_promise(t) ); if (type != "AccountChooser") throw "Incorrect dialog type: " + type; await window.test_driver.select_fedcm_account(0); return credentialPromise; } export function fedcm_error_dialog_dismiss(t) { return new Promise(resolve => { async function helper() { // Try to select the account. If the UI is not up yet, we'll catch an exception // and try again in 10ms. try { let type = await fedcm_get_dialog_type_promise(t); assert_equals(type, "Error"); await window.test_driver.cancel_fedcm_dialog(); resolve(); } catch (ex) { t.step_timeout(helper, 10); } } helper(); }); } export function fedcm_error_dialog_click_button(t, button) { return new Promise(resolve => { async function helper() { // Try to select the account. If the UI is not up yet, we'll catch an exception // and try again in 10ms. try { let type = await fedcm_get_dialog_type_promise(t); assert_equals(type, "Error"); await window.test_driver.click_fedcm_dialog_button(button); resolve(); } catch (ex) { t.step_timeout(helper, 10); } } helper(); }); } export function disconnect_options(accountHint, manifest_filename) { if (manifest_filename === undefined) { manifest_filename = "manifest.py"; } const manifest_path = `${manifest_origin}/\ fedcm/support/${manifest_filename}`; return { configURL: manifest_path, clientId: '1', accountHint: accountHint }; } export function alt_disconnect_options(accountHint, manifest_filename) { if (manifest_filename === undefined) { manifest_filename = "manifest.py"; } const manifest_path = `${alt_manifest_origin}/\ fedcm/support/${manifest_filename}`; return { configURL: manifest_path, clientId: '1', accountHint: accountHint }; } export async function fedcm_get_flexible_tokens_credential(t, type) { const options = request_options_with_mediation_required(`manifest_flexible_tokens.json`); options.identity.providers[0].params = { "token_type": type }; await select_manifest(t, options); return await fedcm_get_and_select_first_account(t, options); } export function set_well_known_format(format_type) { const url_query = `?format=${encodeURIComponent(format_type)}`; return new Promise(resolve => { const img = document.createElement('img'); img.addEventListener('error', resolve); img.src = `/fedcm/support/set-well-known-format.py${url_query}`; document.body.appendChild(img); }); }