diff options
Diffstat (limited to 'testing/web-platform/tests/credential-management')
119 files changed, 3857 insertions, 0 deletions
diff --git a/testing/web-platform/tests/credential-management/META.yml b/testing/web-platform/tests/credential-management/META.yml new file mode 100644 index 0000000000..7b18609213 --- /dev/null +++ b/testing/web-platform/tests/credential-management/META.yml @@ -0,0 +1,3 @@ +spec: https://w3c.github.io/webappsec-credential-management/ +suggested_reviewers: + - mikewest diff --git a/testing/web-platform/tests/credential-management/credentialscontainer-create-basics.https.html b/testing/web-platform/tests/credential-management/credentialscontainer-create-basics.https.html new file mode 100644 index 0000000000..ea2326c4ae --- /dev/null +++ b/testing/web-platform/tests/credential-management/credentialscontainer-create-basics.https.html @@ -0,0 +1,135 @@ +<!DOCTYPE html> +<title>Credential Management API: create() basics.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(function(t) { + return promise_rejects_dom(t, "NotSupportedError", + navigator.credentials.create()); +}, "navigator.credentials.create() with no argument."); + +promise_test(function(t) { + return promise_rejects_dom(t, "NotSupportedError", + navigator.credentials.create({})); +}, "navigator.credentials.create() with empty argument."); + +promise_test(function(t) { + var credential_data = { + id: 'id', + password: 'pencil', + }; + + return navigator.credentials.create({password: credential_data}) + .then(function(credential) { + assert_equals(credential.id, 'id'); + assert_equals(credential.name, ''); + assert_equals(credential.iconURL, ''); + assert_equals(credential.type, 'password'); + assert_equals(credential.password, 'pencil'); + }); +}, "navigator.credentials.create() with valid PasswordCredentialData"); + +promise_test(function(t) { + var f = document.createElement('form'); + f.innerHTML = "<input type='text' name='theId' value='musterman' autocomplete='username'>" + + "<input type='text' name='thePassword' value='sekrit' autocomplete='current-password'>" + + "<input type='text' name='theIcon' value='https://example.com/photo' autocomplete='photo'>" + + "<input type='text' name='theExtraField' value='extra'>" + + "<input type='text' name='theName' value='friendly name' autocomplete='name'>"; + + return navigator.credentials.create({password: f}) + .then(function(credential) { + assert_equals(credential.id, 'musterman'); + assert_equals(credential.name, 'friendly name'); + assert_equals(credential.iconURL, 'https://example.com/photo'); + assert_equals(credential.type, 'password'); + assert_equals(credential.password, 'sekrit'); + }); +}, "navigator.credentials.create() with valid HTMLFormElement"); + +promise_test(function(t) { + return promise_rejects_js(t, TypeError, + navigator.credentials.create({password: "bogus password data"})); +}, "navigator.credentials.create() with bogus password data"); + +promise_test(function(t) { + var federated_data = { + id: 'id', + provider: 'https://example.com/', + }; + + return navigator.credentials.create({federated: federated_data}) + .then(function(credential) { + assert_equals(credential.id, 'id'); + assert_equals(credential.name, ''); + assert_equals(credential.iconURL, ''); + assert_equals(credential.type, 'federated'); + }); +}, "navigator.credentials.create() with valid FederatedCredentialData"); + +promise_test(function(t) { + return promise_rejects_js(t, TypeError, + navigator.credentials.create({federated: "bogus federated data"})); +}, "navigator.credentials.create() with bogus federated data"); + +promise_test(function(t) { + return promise_rejects_js(t, TypeError, + navigator.credentials.create({publicKey: "bogus publicKey data"})); +}, "navigator.credentials.create() with bogus publicKey data"); + +promise_test(function(t) { + var credential_data = { + id: 'id', + password: 'pencil', + }; + + var federated_data = { + id: 'id', + provider: 'https://example.com/', + }; + + return promise_rejects_dom(t, "NotSupportedError", + navigator.credentials.create({ + password: credential_data, + federated: federated_data, + })); +}, "navigator.credentials.create() with both PasswordCredentialData and FederatedCredentialData"); + +promise_test(function(t) { + return promise_rejects_js(t, TypeError, + navigator.credentials.create({ + password: "bogus password data", + federated: "bogus federated data", + })); +}, "navigator.credentials.create() with bogus password and federated data"); + +promise_test(function(t) { + return promise_rejects_js(t, TypeError, + navigator.credentials.create({ + federated: "bogus federated data", + publicKey: "bogus publicKey data", + })); +}, "navigator.credentials.create() with bogus federated and publicKey data"); + +promise_test(function(t) { + return promise_rejects_js(t, TypeError, + navigator.credentials.create({ + password: "bogus password data", + publicKey: "bogus publicKey data", + })); +}, "navigator.credentials.create() with bogus password and publicKey data"); + +promise_test(function(t) { + return promise_rejects_js(t, TypeError, + navigator.credentials.create({ + password: "bogus password data", + federated: "bogus federated data", + publicKey: "bogus publicKey data", + })); +}, "navigator.credentials.create() with bogus password, federated, and publicKey data"); + +promise_test(function(t) { + return promise_rejects_dom(t, "NotSupportedError", + navigator.credentials.create({bogus_key: "bogus data"})); +}, "navigator.credentials.create() with bogus data"); +</script> diff --git a/testing/web-platform/tests/credential-management/credentialscontainer-prevent-silent-access.https.html b/testing/web-platform/tests/credential-management/credentialscontainer-prevent-silent-access.https.html new file mode 100644 index 0000000000..5a83e556a7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/credentialscontainer-prevent-silent-access.https.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<title>Credential Management API: preventSilentAccess().</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +promise_test(function() { + return navigator.credentials.preventSilentAccess() + .then((result) => { + assert_equals(result, undefined); + }); +}, "navigator.credentials.preventSilentAccess() resolves with undefined."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-abort.https.html b/testing/web-platform/tests/credential-management/fedcm-abort.https.html new file mode 100644 index 0000000000..0f03bff832 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-abort.https.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + let controller = new AbortController(); + let test_options = request_options_with_mediation_required(); + test_options.signal = controller.signal; + const cred = fedcm_get_and_select_first_account(t, test_options); + controller.abort(); + return promise_rejects_dom(t, 'AbortError', cred); +}, "Test the abort signal"); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-after-abort.https.html b/testing/web-platform/tests/credential-management/fedcm-after-abort.https.html new file mode 100644 index 0000000000..3c2f981e82 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-after-abort.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + let controller = new AbortController(); + let test_options = request_options_with_mediation_required(); + test_options.signal = controller.signal; + const first_cred = fedcm_get_and_select_first_account(t, test_options); + controller.abort(); + await promise_rejects_dom(t, 'AbortError', first_cred); + + const second_cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + assert_equals(second_cred.token, "token"); +}, "Get after abort should work"); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html b/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html new file mode 100644 index 0000000000..3ce1f51e37 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {fedcm_test, + request_options_with_mediation_required, + select_manifest, + fedcm_get_and_select_first_account} from '../support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + const options = request_options_with_mediation_required('manifest_with_continue_on.json'); + await select_manifest(t, options); + const cred = await fedcm_get_and_select_first_account(t, options); + assert_equals(cred.token, "resolved token"); +}, "continue_on and IdentityProvider.resolve work correctly."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html b/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html new file mode 100644 index 0000000000..ef53ed4ffc --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<title>Federated Credential Management API authz getUserInfo() tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {alt_manifest_origin, + alt_request_options_with_mediation_required, + select_manifest, + fedcm_test, + fedcm_get_and_select_first_account} from '../support/fedcm-helper.sub.js'; + +async function createIframeWithPermissionPolicyAndWaitForMessage(test, iframeUrl) { + const messageWatcher = new EventWatcher(test, window, "message"); + let iframe = document.createElement("iframe"); + iframe.src = iframeUrl; + iframe.allow = "identity-credentials-get"; + document.body.appendChild(iframe); + const message = await messageWatcher.wait_for("message"); + return message.data; +} + +fedcm_test(async t => { + const options = alt_request_options_with_mediation_required('manifest_with_continue_on.json'); + await select_manifest(t, options); + const cred = await fedcm_get_and_select_first_account(t, options); + assert_equals(cred.token, "resolved token"); + + const iframe_in_idp_scope = `${alt_manifest_origin}/\ +credential-management/support/fedcm/userinfo-iframe.html`; + const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope); + assert_equals(message.result, "Pass"); + assert_equals(message.numAccounts, 1); + assert_equals(message.firstAccountEmail, "john_doe@idp.example"); + assert_equals(message.firstAccountName, "John Doe"); + assert_equals(message.firstAccountGivenName, "John"); + assert_equals(message.firstAccountPicture, "https://idp.example/profile/123"); +}, 'Test getUserInfo() after resolve() to verify that resolve stores the RP/IDP connection'); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html b/testing/web-platform/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html new file mode 100644 index 0000000000..fb93cb632d --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Federated Credential Management API auto reauthentication without approved client.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_optional, + fedcm_test, + mark_signed_in, + select_manifest, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + let test_options = request_options_with_mediation_optional( + "manifest_with_single_account.json"); + test_options.identity.providers[0].clientId = 'nomatch'; + await select_manifest(t, test_options); + + // Signs in john_doe so that they will be a returning user + let cred = await fedcm_get_and_select_first_account(t, test_options); + assert_equals(cred.token, "account_id=john_doe"); + + test_options.mediation = 'silent'; + cred = navigator.credentials.get(test_options); + return promise_rejects_dom(t, 'NetworkError', cred); +}, "Test that if the clientId is missing from approved_clients then auto\ +reauthentication cannot occur."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-auto-selected-flag.https.html b/testing/web-platform/tests/credential-management/fedcm-auto-selected-flag.https.html new file mode 100644 index 0000000000..d06aba73bc --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-auto-selected-flag.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Federated Credential Management API auto selected flag tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_optional, + fedcm_test, + select_manifest, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + let test_options = request_options_with_mediation_optional("manifest_with_auto_selected_flag.json"); + await select_manifest(t, test_options); + + let cred = await fedcm_get_and_select_first_account(t, test_options); + assert_equals(cred.token, "is_auto_selected=false"); + + test_options = request_options_with_mediation_optional("manifest_with_auto_selected_flag.json"); + await select_manifest(t, test_options); + + cred = await navigator.credentials.get(test_options); + assert_equals(cred.token, "is_auto_selected=true"); + assert_equals(cred.isAutoSelected, true); +}, "Test that the is_auto_selected bit is properly sent."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-basic.https.html b/testing/web-platform/tests/credential-management/fedcm-basic.https.html new file mode 100644 index 0000000000..3d20f4cfb7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-basic.https.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {fedcm_test, + request_options_with_mediation_required, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + assert_equals(cred.token, "token"); + assert_equals(cred.isAutoSelected, false); +}, "Successfully obtaining token should resolve the promise."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-client-metadata-not-cached.https.html b/testing/web-platform/tests/credential-management/fedcm-client-metadata-not-cached.https.html new file mode 100644 index 0000000000..b802369256 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-client-metadata-not-cached.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + // Reset the client_metadata fetch count. + const clear_metadata_count_path = `support/fedcm/client_metadata_clear_count.py`; + await fetch(clear_metadata_count_path); + + // FedCM flow causes the counter of client metadata to increase by 1. + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + assert_equals(cred.token, "token"); + + await new Promise(resolve => { + // Fetch the client metadata from a popup window. + let popup_window = window.open('support/fedcm/client_metadata.py?skip_checks=1'); + const popup_window_load_handler = (event) => { + popup_window.removeEventListener('load', popup_window_load_handler); + popup_window.close(); + resolve(); + }; + popup_window.addEventListener('load', popup_window_load_handler); + }); + + const client_metadata_counter = await fetch(clear_metadata_count_path); + const client_metadata_counter_text = await client_metadata_counter.text(); + // Check that the client metadata response is not cached. If the client metadata response was + // cached, when the user visits the IDP as a first party, the IDP would be able to determine the + // last RP the user visited regardless of whether the user granted consent via the FedCM prompt. + assert_equals(client_metadata_counter_text, "2"); +}, 'Test client_metadata request is not cached'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-context.https.html b/testing/web-platform/tests/credential-management/fedcm-context.https.html new file mode 100644 index 0000000000..bc1f96eafa --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-context.https.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<title>Federated Credential Management API context tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {request_options_with_mediation_required, + request_options_with_context, + fedcm_get_title_promise, + fedcm_test} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + let p = navigator.credentials.get(request_options_with_mediation_required()); + const title = await fedcm_get_title_promise(t); + assert_true(title.toLowerCase().includes('sign in')); + window.test_driver.select_fedcm_account(0); + return p; +}, "FedCM call defaults to 'signin' context."); + +fedcm_test(async t => { + let p = navigator.credentials.get(request_options_with_context("manifest.py", "signup")); + const title = await fedcm_get_title_promise(t); + assert_true(title.toLowerCase().includes('sign up')); + window.test_driver.select_fedcm_account(0); + return p; +}, "FedCM with 'signup' context."); + +fedcm_test(async t => { + let p = navigator.credentials.get(request_options_with_context("manifest.py", "use")); + const title = await fedcm_get_title_promise(t); + assert_true(title.toLowerCase().includes('use')); + window.test_driver.select_fedcm_account(0); + return p; +}, "FedCM with 'use' context."); + +fedcm_test(async t => { + let p = navigator.credentials.get(request_options_with_context("manifest.py", "continue")); + const title = await fedcm_get_title_promise(t); + assert_true(title.toLowerCase().includes('continue')); + window.test_driver.select_fedcm_account(0); + return p; +}, "FedCM with 'continue' context."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html b/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html new file mode 100644 index 0000000000..1e3b4c71a8 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Federated Credential Management API Cross-Origin-Embedder-Policy tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + assert_equals(cred.token, 'token'); +}, 'Test that COEP policy do not apply to FedCM requests'); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers b/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers new file mode 100644 index 0000000000..32523a6978 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers @@ -0,0 +1 @@ +Cross-Origin-Embedder-Policy: credentialless diff --git a/testing/web-platform/tests/credential-management/fedcm-csp.https.html b/testing/web-platform/tests/credential-management/fedcm-csp.https.html new file mode 100644 index 0000000000..5925741438 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-csp.https.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + set_fedcm_cookie} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + const cred = navigator.credentials.get(request_options_with_mediation_required()); + return promise_rejects_dom(t, "NetworkError", cred); +}, "Provider configURL should honor Content-Security-Policy."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-csp.https.html.sub.headers b/testing/web-platform/tests/credential-management/fedcm-csp.https.html.sub.headers new file mode 100644 index 0000000000..c1e6fd6c4c --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-csp.https.html.sub.headers @@ -0,0 +1,2 @@ +Cross-Origin-Embedder-Policy: credentialless +Content-Security-Policy: default-src 'none'; script-src 'self' 'unsafe-inline'; img-src 'self' diff --git a/testing/web-platform/tests/credential-management/fedcm-disconnect-errors.https.html b/testing/web-platform/tests/credential-management/fedcm-disconnect-errors.https.html new file mode 100644 index 0000000000..dbf42c4083 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-disconnect-errors.https.html @@ -0,0 +1,69 @@ +<!DOCTYPE html> +<title>Federated Credential Management API disconnect() basic errors.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {fedcm_test, + mark_signed_in, + set_fedcm_cookie, + fedcm_get_and_select_first_account, + manifest_origin, + request_options_with_mediation_required, + select_manifest, + disconnect_options} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + await set_fedcm_cookie(); + // Get at least one connected account that can be disconnected. + const cred = await fedcm_get_and_select_first_account(t, + request_options_with_mediation_required()); + const manifest = `${manifest_origin}/\ +credential-management/support/fedcm/manifest.py`; + await promise_rejects_js(t, TypeError, IdentityCredential.disconnect({ + configURL: manifest, + clientId: '1' + })); + await promise_rejects_js(t, TypeError, IdentityCredential.disconnect({ + configURL: manifest, + accountHint: 'hint' + })); + return promise_rejects_js(t, TypeError, IdentityCredential.disconnect({ + clientId: '1', + accountHint: 'hint' + })); +}, "disconnect requires 3 parameters: configURL, clientId, and accountHint"); + +fedcm_test(async t => { + await mark_signed_in(); + await set_fedcm_cookie(); + // Get at least one connected account that can be disconnected. + const cred = await fedcm_get_and_select_first_account(t, + request_options_with_mediation_required()); + + const manifest = `manifest_with_cross_origin_disconnect.sub.json`; + await select_manifest(t, request_options_with_mediation_required(manifest)); + return promise_rejects_dom(t, 'NetworkError', + IdentityCredential.disconnect(disconnect_options('1234', manifest))); +}, "disconnect fails if the disconnect endpoint is cross-origin with respect\ +to the config file"); + +fedcm_test(async t => { + await mark_signed_in(); + await set_fedcm_cookie(); + // Get at least one connected account that can be disconnected. + const cred = await fedcm_get_and_select_first_account(t, + request_options_with_mediation_required()); + + const manifest = `manifest_with_disconnect_failure.json`; + await select_manifest(t, request_options_with_mediation_required(manifest)); + return promise_rejects_dom(t, 'NetworkError', + IdentityCredential.disconnect(disconnect_options('1234', manifest))); +}, "disconnect fails if the server sends failure"); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-disconnect-iframe.sub.https.html b/testing/web-platform/tests/credential-management/fedcm-disconnect-iframe.sub.https.html new file mode 100644 index 0000000000..3d31be60b1 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-disconnect-iframe.sub.https.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title>Federated Credential Management API disconnect() tests in iframes.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {alt_manifest_origin, + alt_request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +async function createIframeAndWaitForMessage(test, iframeUrl, allow = false) { + const messageWatcher = new EventWatcher(test, window, "message"); + let iframe = document.createElement("iframe"); + iframe.src = iframeUrl; + if (allow) { + iframe.allow = "identity-credentials-get"; + } + document.body.appendChild(iframe); + const message = await messageWatcher.wait_for("message"); + return message.data; +} + +fedcm_test(async t => { + const message = await createIframeAndWaitForMessage(t, 'support/fedcm/disconnect-iframe.html'); + assert_equals(message.result, "Pass"); +}, 'Same-origin iframe does not need explicit identity-credentials-get'); + +fedcm_test(async t => { + const message = await createIframeAndWaitForMessage(t, + 'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect-iframe.html?skip_get'); + assert_equals(message.result, "Failed disconnect"); + assert_equals(message.errorType, "NotAllowedError"); +}, 'Cross-origin iframe fails disconnect() without explicit identity-credentials-get'); + +fedcm_test(async t => { + const message = await createIframeAndWaitForMessage(t, + 'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect-iframe.html', + /*allow=*/true); + assert_equals(message.result, "Pass"); +}, 'Cross-origin iframe can disconnect with explicit identity-credentials-get'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-disconnect.sub.https.html b/testing/web-platform/tests/credential-management/fedcm-disconnect.sub.https.html new file mode 100644 index 0000000000..300144fa72 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-disconnect.sub.https.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<title>Federated Credential Management API disconnect() tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {fedcm_test, + mark_signed_in, + set_fedcm_cookie, + disconnect_options, + fedcm_get_and_select_first_account, + request_options_with_mediation_required, + alt_manifest_origin, + alt_request_options_with_mediation_required, + alt_disconnect_options, + set_alt_fedcm_cookie} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + await set_fedcm_cookie(); + // Get at least one connected account that can be disconnected. + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + // The IDP implementation will accept any account hint, so this is really testing that the user + // agent eventually stops sending the requests to the IDP. + // This test clears the connection just created above, but it also clears any previously existing + // connected accounts, which helps the logic of the other tests. + return new Promise(async resolve => { + while (true) { + try { + await IdentityCredential.disconnect(disconnect_options("1234")); + } catch(e) { + resolve(); + break; + } + } + }); +}, "Repeatedly calling disconnect should eventually fail"); + +fedcm_test(async t => { + const disconnect = IdentityCredential.disconnect( + disconnect_options("nonExistent")); + return promise_rejects_dom(t, 'NetworkError', disconnect); +}, 'Test that disconnect fails when there is no account to disconnect'); + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + + return IdentityCredential.disconnect(disconnect_options("1234")); +}, 'Test that disconnect succeeds when there is an account to disconnect'); + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + + await IdentityCredential.disconnect(disconnect_options("1234")); + + const disconnect = IdentityCredential.disconnect(disconnect_options("1234")); + return promise_rejects_dom(t, 'NetworkError', disconnect); +}, 'Test that disconnecting the same account twice results in failure.'); + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + // A connected account is guaranteed by the above, and IDP accepts any account hint, so this tests + // that the user agent allows the request to go through to the IDP. + return IdentityCredential.disconnect(disconnect_options("noMatch")); +}, 'Disconnect passing an incorrect ID can still succeed'); + +fedcm_test(async t => { + await set_alt_fedcm_cookie(); + await mark_signed_in(alt_manifest_origin); + await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required()); + await fedcm_get_and_select_first_account(t, + request_options_with_mediation_required()); + + // Await the first disconnect since they cannot happen in parallel. Both + // should succeed. + await IdentityCredential.disconnect(disconnect_options("1")); + return IdentityCredential.disconnect(alt_disconnect_options("2")); +}, 'Disconnect is bound to each IDP'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-domainhint.https.html b/testing/web-platform/tests/credential-management/fedcm-domainhint.https.html new file mode 100644 index 0000000000..3e07491d48 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-domainhint.https.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<title>Federated Credential Management API domain hint tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<body> + +<script type="module"> +import { + fedcm_test, + request_options_with_domain_hint, + select_manifest, + mark_signed_in, + fedcm_get_dialog_type_promise, + fedcm_get_and_select_first_account +} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + + let options = request_options_with_domain_hint('manifest.py', + 'nomatch'); + const cred = fedcm_get_and_select_first_account(t, options); + // We expect a mismatch dialog. + const type = await fedcm_get_dialog_type_promise(t); + assert_equals(type, 'ConfirmIdpLogin'); + window.test_driver.cancel_fedcm_dialog(); + + return promise_rejects_dom(t, "NetworkError", cred); +}, "No domain hint matches an account."); + +fedcm_test(async t => { + const options = request_options_with_domain_hint('manifest.py', + 'idp.example'); + const cred = await fedcm_get_and_select_first_account(t, options); + assert_equals(cred.token, 'token'); +}, "Domain hint matches an account."); + +fedcm_test(async t => { + let options = request_options_with_domain_hint( + 'manifest_with_two_accounts.json', 'example'); + await select_manifest(t, options); + + const cred = await fedcm_get_and_select_first_account(t, options); + assert_equals(cred.token, 'account_id=john_doe'); +}, "Domain hint matches an account from two accounts."); + +fedcm_test(async t => { + let options = request_options_with_domain_hint( + 'manifest_with_two_accounts.json', 'any'); + await select_manifest(t, options); + + const cred = await fedcm_get_and_select_first_account(t, options); + assert_equals(cred.token, 'account_id=john_doe'); +}, "Domain hint 'any' matches an account with any domain hint."); + </script> diff --git a/testing/web-platform/tests/credential-management/fedcm-endpoint-redirects.https.html b/testing/web-platform/tests/credential-management/fedcm-endpoint-redirects.https.html new file mode 100644 index 0000000000..cff5036f39 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-endpoint-redirects.https.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + select_manifest, + mark_signed_in, + fedcm_get_dialog_type_promise, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + + let test_options = request_options_with_mediation_required("manifest_redirect_accounts.json"); + await select_manifest(t, test_options); + + const cred = fedcm_get_and_select_first_account(t, test_options); + // We expect a mismatch dialog. + const type = await fedcm_get_dialog_type_promise(t); + assert_equals(type, 'ConfirmIdpLogin'); + window.test_driver.cancel_fedcm_dialog(); + return promise_rejects_dom(t, 'NetworkError', cred); +}, 'Test that promise is rejected if accounts endpoint redirects'); + +fedcm_test(async t => { + await mark_signed_in(); + + let test_options = request_options_with_mediation_required("manifest_redirect_token.json"); + await select_manifest(t, test_options); + + try { + const cred = await fedcm_get_and_select_first_account(t, test_options); + assert_unreached("An IdentityCredentialError exception should be thrown."); + } catch (e) { + assert_true(e instanceof DOMException); + assert_equals(e.name, "IdentityCredentialError"); + // 308 should not produce a valid error code + assert_equals(e.code, ""); + } +}, 'Test that token endpoint does not follow redirects'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-error-basic.https.html b/testing/web-platform/tests/credential-management/fedcm-error-basic.https.html new file mode 100644 index 0000000000..49d6ea50df --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-error-basic.https.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<title>Federated Credential Management API Error API tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + manifest_origin, + select_manifest, + fedcm_get_and_select_first_account, + fedcm_error_dialog_dismiss, + fedcm_error_dialog_click_button} from './support/fedcm-helper.sub.js'; + +const url_prefix = manifest_origin + '/credential-management/support/fedcm/'; + +fedcm_test(async t => { + let test_options = + request_options_with_mediation_required("manifest_id_assertion_endpoint_returns_error.json"); + await select_manifest(t, test_options); + + try { + const cred = await fedcm_get_and_select_first_account(t, test_options); + fedcm_error_dialog_dismiss(t); + await cred; + assert_unreached("An IdentityCredentialError exception should be thrown."); + } catch (e) { + assert_true(e instanceof DOMException); + assert_equals(e.name, "IdentityCredentialError"); + assert_equals(e.code, "unauthorized_client"); + assert_equals(e.url, url_prefix + "error.html"); + } +}, 'Test that the promise is rejected with proper error details when dialog is dismissed'); + +fedcm_test(async t => { + let test_options = + request_options_with_mediation_required("manifest_id_assertion_endpoint_returns_error.json"); + await select_manifest(t, test_options); + + try { + const cred = await fedcm_get_and_select_first_account(t, test_options); + fedcm_error_dialog_click_button(t, "ErrorGotIt"); + await cred; + assert_unreached("An IdentityCredentialError exception should be thrown."); + } catch (e) { + assert_true(e instanceof DOMException); + assert_equals(e.name, "IdentityCredentialError"); + assert_equals(e.code, "unauthorized_client"); + assert_equals(e.url, url_prefix + "error.html"); + } +}, 'Test that the promise is rejected with proper error details when got it is clicked'); + +fedcm_test(async t => { + let test_options = + request_options_with_mediation_required("manifest_id_assertion_endpoint_returns_error.json"); + await select_manifest(t, test_options); + + try { + const cred = await fedcm_get_and_select_first_account(t, test_options); + fedcm_error_dialog_click_button(t, "ErrorMoreDetails"); + await cred; + assert_unreached("An IdentityCredentialError exception should be thrown."); + } catch (e) { + assert_true(e instanceof DOMException); + assert_equals(e.name, "IdentityCredentialError"); + assert_equals(e.code, "unauthorized_client"); + assert_equals(e.url, url_prefix + "error.html"); + } +}, 'Test that the promise is rejected with proper error details when more details is clicked'); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-iframe.https.html b/testing/web-platform/tests/credential-management/fedcm-iframe.https.html new file mode 100644 index 0000000000..dc0c17dea6 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-iframe.https.html @@ -0,0 +1,84 @@ +<!doctype html> +<link rel="help" href="https://wicg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<div id=log> +<script type="module"> +'use strict'; + +import {fedcm_test, set_fedcm_cookie} from './support/fedcm-helper.sub.js'; + +const host = get_host_info(); +// This regex removes the filename from the path so that we just get +// the directory. +const basePath = window.location.pathname.replace(/\/[^\/]*$/, '/'); +const remoteBaseURL = host.HTTPS_REMOTE_ORIGIN + basePath; +const localhostBaseURL = "http://localhost:" + host.HTTP_PORT + basePath; + +async function createIframeAndWaitForMessage(test, iframeUrl, setPermissionPolicy, style = "") { + const messageWatcher = new EventWatcher(test, window, "message"); + var iframe = document.createElement("iframe"); + iframe.src = iframeUrl; + if (setPermissionPolicy) { + iframe.allow = "identity-credentials-get"; + } + if (style !== "") { + iframe.style = style; + } + document.body.appendChild(iframe); + const message = await messageWatcher.wait_for("message"); + return message.data; +} + +fedcm_test(async t => { + const message = await createIframeAndWaitForMessage( + t, remoteBaseURL + "support/fedcm-iframe.html", + /*setPermissionPolicy=*/false); + assert_equals(message.result, "Fail"); + assert_equals(message.errorType, "NotAllowedError"); +}, "FedCM disabled in cross origin iframe without permissions policy"); + +fedcm_test(async t => { + const message = await createIframeAndWaitForMessage( + t, remoteBaseURL + "support/fedcm-iframe-level2.html", + /*setPermissionPolicy=*/true); + assert_equals(message.result, "Pass"); + assert_equals(message.token, "token"); +}, "FedCM enabled in 2 level deep nested iframe. FedCM should be enabled regardless of iframe nesting depth"); + +fedcm_test(async t => { + const message = await createIframeAndWaitForMessage( + t, remoteBaseURL + "support/fedcm-iframe.html", + /*setPermissionPolicy=*/true, /*style=*/"display:none;"); + assert_equals(message.result, "Pass"); + assert_equals(message.token, "token"); +}, "FedCM enabled in invisible iframe. FedCM should be enabled as long as the top frame is visible"); + +fedcm_test(async t => { + const message = await createIframeAndWaitForMessage( + t, remoteBaseURL + "support/fedcm-iframe-level2.html", + /*setPermissionPolicy=*/false); + assert_equals(message.result, "Fail"); + assert_equals(message.errorType, "NotAllowedError"); +}, "FedCM disabled in 2 level deep nested iframe where middle iframe does not have permission policy"); + +fedcm_test(async t => { + const message = await createIframeAndWaitForMessage( + t, remoteBaseURL + "support/fedcm-iframe-level2.html?permission=0", + /*setPermissionPolicy=*/true); + assert_equals(message.result, "Fail"); + assert_equals(message.errorType, "NotAllowedError"); +}, "FedCM disabled in 2 level deep nested iframe where innermost iframe does not have permission policy"); + +fedcm_test(async t => { + // This is only an iframe because there's no other way to have this URL + // loaded from localhost. + const message = await createIframeAndWaitForMessage( + t, localhostBaseURL + "support/fedcm-iframe.html", + /*setPermissionPolicy=*/true); + assert_equals(message.result, "Pass"); + assert_equals(message.token, "token"); +}, "FedCM should work in non-HTTPS URLs on localhost"); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html b/testing/web-platform/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html new file mode 100644 index 0000000000..0f8df72b61 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>FedCM IDP log-in status API tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + fedcm_get_dialog_type_promise, + select_manifest, + mark_signed_in} from '../support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + + let test_options = request_options_with_mediation_required("manifest_with_variable_accounts.json"); + await select_manifest(t, test_options); + + let cred_promise = navigator.credentials.get(test_options); + let type = await fedcm_get_dialog_type_promise(t); + assert_equals(type, "ConfirmIdpLogin"); + + // Manifest selection only persists for a single fetch, so we need to set it + // again because Chrome's implementation re-fetches the manifest as well, not + // just the accounts endpoint. + // (This is not technically spec-compliant) + await select_manifest(t, test_options); + await window.test_driver.click_fedcm_dialog_button("ConfirmIdpLoginContinue"); + + // Now wait for the account chooser. + type = await fedcm_get_dialog_type_promise(t); + assert_equals(type, "AccountChooser"); + window.test_driver.select_fedcm_account(0); + + let cred = await cred_promise; + assert_equals(cred.token, "account_id=1234"); +}, 'Tests the IDP login dialog and subsequent account chooser.'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-login-status/cross-origin-status.https.html b/testing/web-platform/tests/credential-management/fedcm-login-status/cross-origin-status.https.html new file mode 100644 index 0000000000..f32e18d40e --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-login-status/cross-origin-status.https.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>FedCM IDP login status API tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {fedcm_test, + alt_manifest_origin, + same_site_manifest_origin, + set_fedcm_cookie, + select_manifest, + request_options_with_mediation_required, + alt_request_options_with_mediation_required, + fedcm_get_and_select_first_account, + open_and_wait_for_popup, + mark_signed_out} from '../support/fedcm-helper.sub.js'; + +const path = '/credential-management/support/' +const url_prefix = alt_manifest_origin + path; +const same_site_url_prefix = same_site_manifest_origin + path; + +fedcm_test(async t => { + await set_fedcm_cookie(same_site_manifest_origin); + await mark_signed_out(same_site_manifest_origin); + // The header should be processed successfully because it is same-site. + const fetch_result = await fetch(same_site_url_prefix + "mark_signedin"); + assert_true(fetch_result.ok); + + const config = request_options_with_mediation_required(undefined, same_site_manifest_origin); + await select_manifest(t, config); + const cred = await fedcm_get_and_select_first_account(t, config); + assert_equals(cred.token, "token"); +}, 'Cross-origin same-site status header should work from fetch()'); + +fedcm_test(async t => { + await mark_signed_out(alt_manifest_origin); + // The header should be ignored because it's a cross-site fetch. + const fetch_result = await fetch(url_prefix + "mark_signedin"); + assert_true(fetch_result.ok); + + const config = alt_request_options_with_mediation_required(); + const result = navigator.credentials.get(config); + return promise_rejects_dom(t, 'NetworkError', result); +}, 'Cross-origin status header should be ignored from fetch()'); + +fedcm_test(async t => { + await mark_signed_out(alt_manifest_origin); + // The header should be ignored because it's a cross-site iframe. + let iframe = document.createElement("iframe"); + let iframe_load = new Promise(resolve => {iframe.onload = resolve;}); + iframe.src = url_prefix + "mark_signedin"; + document.body.appendChild(iframe); + await iframe_load; + + const config = alt_request_options_with_mediation_required(); + const result = navigator.credentials.get(config); + return promise_rejects_dom(t, 'NetworkError', result); +}, 'Status header should be ignored from cross-site iframe'); + +fedcm_test(async t => { + await mark_signed_out(alt_manifest_origin); + // The header in the subresource should be ignored because the iframe is cross-site. + let iframe = document.createElement("iframe"); + let iframe_load = new Promise(resolve => {iframe.onload = resolve;}); + iframe.src = url_prefix + "iframe-mark-signedin.html"; + document.body.appendChild(iframe); + await iframe_load; + + const config = alt_request_options_with_mediation_required(); + const result = navigator.credentials.get(config); + return promise_rejects_dom(t, 'NetworkError', result); +}, 'Status header should be ignored from cross-site iframe that contains a subresource with the header'); + +fedcm_test(async t => { + await mark_signed_out(alt_manifest_origin); + await open_and_wait_for_popup(alt_manifest_origin, "/credential-management/support/fencedframe-mark-signedin.html"); + + const config = alt_request_options_with_mediation_required(); + const result = navigator.credentials.get(config); + return promise_rejects_dom(t, 'NetworkError', result); +}, 'Status header should be ignored from a fenced frame, even if it is same-origin'); + +</script> + diff --git a/testing/web-platform/tests/credential-management/fedcm-login-status/logged-out.https.html b/testing/web-platform/tests/credential-management/fedcm-login-status/logged-out.https.html new file mode 100644 index 0000000000..09750ff096 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-login-status/logged-out.https.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>FedCM IDP sign-in status API tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + alt_manifest_origin, + alt_request_options_with_mediation_required, + fedcm_get_and_select_first_account, + mark_signed_out} from '../support/fedcm-helper.sub.js'; + +async function createIframeWithPermissionPolicyAndWaitForMessage(test, iframeUrl) { + const messageWatcher = new EventWatcher(test, window, "message"); + let iframe = document.createElement("iframe"); + iframe.src = iframeUrl; + iframe.allow = "identity-credentials-get"; + document.body.appendChild(iframe); + const message = await messageWatcher.wait_for("message"); + return message.data; +} + +fedcm_test(async t => { + await mark_signed_out(); + const config = request_options_with_mediation_required(); + const result = navigator.credentials.get(config); + return promise_rejects_dom(t, 'NetworkError', result); +}, 'FedCM request should fail because we are marked as not logged in'); + +fedcm_test(async t => { + // Log in so that the browser allows the later user info request. + const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required()); + assert_equals(cred.token, "token"); + + await mark_signed_out(alt_manifest_origin); + + const iframe_in_idp_scope = `${alt_manifest_origin}/\ +credential-management/support/fedcm/userinfo-iframe.html`; + const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope); + assert_equals(message.result, "Fail"); + +}, 'User info request should fail because we are marked as not logged in'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-loginhint.https.html b/testing/web-platform/tests/credential-management/fedcm-loginhint.https.html new file mode 100644 index 0000000000..edae955a76 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-loginhint.https.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title>Federated Credential Management API login hint tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<body> + +<script type="module"> +import {fedcm_test, + request_options_with_login_hint, + select_manifest, + mark_signed_in, + fedcm_get_dialog_type_promise, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + + let options = request_options_with_login_hint('manifest.py', 'nomatch'); + const cred = fedcm_get_and_select_first_account(t, options); + // We expect a mismatch dialog. + const type = await fedcm_get_dialog_type_promise(t); + assert_equals(type, 'ConfirmIdpLogin'); + window.test_driver.cancel_fedcm_dialog(); + return promise_rejects_dom(t, "NetworkError", cred); +}, "No login hint matches an account."); + +fedcm_test(async t => { + const options = request_options_with_login_hint('manifest.py', 'john_doe'); + const cred = await fedcm_get_and_select_first_account(t, options); + assert_equals(cred.token, 'token'); +}, "Login hint matches an account."); + +fedcm_test(async t => { + let options = request_options_with_login_hint('manifest_with_two_accounts.json', 'john_doe'); + await select_manifest(t, options); + + const cred = await fedcm_get_and_select_first_account(t, options); + assert_equals(cred.token, 'account_id=john_doe'); +}, "Login hint matches an account from two accounts."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-manifest-not-in-list.https.html b/testing/web-platform/tests/credential-management/fedcm-manifest-not-in-list.https.html new file mode 100644 index 0000000000..087af384b1 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-manifest-not-in-list.https.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {alt_request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + let test_options = alt_request_options_with_mediation_required('manifest-not-in-list.json'); + const cred = fedcm_get_and_select_first_account(t, test_options); + return promise_rejects_dom(t, 'NetworkError', cred); +}, 'Test that the promise is rejected if the manifest is not in the manifest list'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-first-idp.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-first-idp.https.html new file mode 100644 index 0000000000..ed7c1300bd --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-first-idp.https.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP abort first IDP test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script type="module"> + import { + set_fedcm_cookie, + set_alt_fedcm_cookie, + request_options_with_mediation_required, + alt_request_options_with_mediation_required + } from '../support/fedcm-helper.sub.js'; + + let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]); + + promise_test(async t => { + let first_controller = new AbortController(); + let first_test_options = request_options_with_mediation_required(); + first_test_options.signal = first_controller.signal; + const first_cred = navigator.credentials.get(first_test_options); + + let second_controller = new AbortController(); + let second_test_options = alt_request_options_with_mediation_required(); + second_test_options.signal = second_controller.signal; + const second_cred = navigator.credentials.get(second_test_options); + + await cookies_promise; + first_controller.abort(); + return Promise.all([ + promise_rejects_dom(t, 'AbortError', first_cred), + promise_rejects_dom(t, 'AbortError', second_cred) + ]); + }, "Test abort signal for a multi IDP request by aborting the first IDP"); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-second-idp.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-second-idp.https.html new file mode 100644 index 0000000000..dfe8969932 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-second-idp.https.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP abort second IDP test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script type="module"> + import { + set_fedcm_cookie, + set_alt_fedcm_cookie, + request_options_with_mediation_required, + alt_request_options_with_mediation_required + } from '../support/fedcm-helper.sub.js'; + + let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]); + + promise_test(async t => { + let first_controller = new AbortController(); + let first_test_options = request_options_with_mediation_required(); + first_test_options.signal = first_controller.signal; + const first_cred = navigator.credentials.get(first_test_options); + + let second_controller = new AbortController(); + let second_test_options = alt_request_options_with_mediation_required(); + second_test_options.signal = second_controller.signal; + const second_cred = navigator.credentials.get(second_test_options); + + await cookies_promise; + second_controller.abort(); + return Promise.all([ + promise_rejects_dom(t, 'AbortError', first_cred), + promise_rejects_dom(t, 'AbortError', second_cred) + ]); + }, "Test abort signal for a multi IDP request by aborting the second IDP"); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-after-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-after-onload.https.html new file mode 100644 index 0000000000..12e0eb4d81 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-after-onload.https.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP get before and after onload test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {set_fedcm_cookie, set_alt_fedcm_cookie, + request_options_with_mediation_required, + alt_request_options_with_mediation_required, + fedcm_select_account_promise} from '../support/fedcm-helper.sub.js'; + +let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]); +let has_window_loaded = false; +const window_loaded = new Promise(resolve => { + window.addEventListener('load', () => { + has_window_loaded = true; + resolve(); + }); +}); + +promise_test(async t => { + let first_cred_resolved = false; + assert_false(has_window_loaded); + // First navigator.credentials.get() is called prior to window.onload + const first_cred = navigator.credentials.get(request_options_with_mediation_required()).finally(() => { first_cred_resolved = true; }); + await Promise.all([cookies_promise, window_loaded]); + assert_true(has_window_loaded); + assert_false(first_cred_resolved); + + // Second navigator.credentials.get() is called after window.onload but before first navigator.credentials.get() + // resolves. Should be rejected because it occurs after onload, and the first get() call is pending. + const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required()); + const rejection = promise_rejects_dom(t, 'NotAllowedError', second_cred); + + // Select first account from the first get() call. + await fedcm_select_account_promise(t, 0); + const first = await first_cred; + assert_equals(first.token, "token"); + return rejection; +}, "When there's a `get` call before onload, a `get` call which occurs after onload but before the first `get` call resolves, should be rejected."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-during-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-during-onload.https.html new file mode 100644 index 0000000000..3e2f134f20 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-during-onload.https.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP get before and during onload test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {set_fedcm_cookie, set_alt_fedcm_cookie, + request_options_with_mediation_required, + alt_request_options_with_mediation_required, + fedcm_select_account_promise} from '../support/fedcm-helper.sub.js'; + +let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]); + +promise_test(async t => { + let has_window_loaded = false; + let rejection; + const window_loaded = new Promise(resolve => { + window.addEventListener('load', async () => { + const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required()); + rejection = promise_rejects_dom(t, 'NetworkError', second_cred); + has_window_loaded = true; + resolve(); + }); + }); + assert_false(has_window_loaded); + const first_cred = navigator.credentials.get(request_options_with_mediation_required()); + await Promise.all([cookies_promise, window_loaded]); + + // Select first account from the first get() call. + await fedcm_select_account_promise(t, 0); + assert_true(has_window_loaded); + const first = await first_cred; + assert_equals(first.token, "token"); + return rejection; +}, "A `get` call before onload and a `get` call during onload should be combined."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-onload-and-during-dom-content-loaded.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-onload-and-during-dom-content-loaded.https.html new file mode 100644 index 0000000000..95495948b7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-onload-and-during-dom-content-loaded.https.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP get before onload and during DOMContentLoaded test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {set_fedcm_cookie, + set_alt_fedcm_cookie, + request_options_with_mediation_required, + alt_request_options_with_mediation_required, + fedcm_select_account_promise} from '../support/fedcm-helper.sub.js'; + +let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]); + +promise_test(async t => { + let has_dom_content_loaded = false; + let rejection; + const dom_content_loaded = new Promise(resolve => { + document.addEventListener('DOMContentLoaded', async () => { + const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required()); + rejection = promise_rejects_dom(t, 'NetworkError', second_cred); + has_dom_content_loaded = true; + resolve(); + }); + }); + assert_false(has_dom_content_loaded); + const first_cred = navigator.credentials.get(request_options_with_mediation_required()); + await Promise.all([cookies_promise, dom_content_loaded]); + assert_true(has_dom_content_loaded); + + await fedcm_select_account_promise(t, 0); + const first = await first_cred; + assert_equals(first.token, "token"); + return rejection; +}, "A `get` call before onload and a `get` call during DOMContentLoaded event should combine despite being called from different tasks."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-abort.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-abort.https.html new file mode 100644 index 0000000000..899302fb22 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-abort.https.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP get after abort test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> + import { + set_fedcm_cookie, + set_alt_fedcm_cookie, + request_options_with_mediation_required, + alt_request_options_with_mediation_required, + fedcm_select_account_promise + } from '../support/fedcm-helper.sub.js'; + + let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]); + + promise_test(async t => { + let first_controller = new AbortController(); + let first_test_options = request_options_with_mediation_required(); + first_test_options.signal = first_controller.signal; + const first_cred = navigator.credentials.get(first_test_options); + + let second_controller = new AbortController(); + let second_test_options = alt_request_options_with_mediation_required(); + second_test_options.signal = second_controller.signal; + const second_cred = navigator.credentials.get(second_test_options); + + await cookies_promise; + second_controller.abort(); + await Promise.all([ + promise_rejects_dom(t, 'AbortError', first_cred), + promise_rejects_dom(t, 'AbortError', second_cred) + ]); + + const third_cred = navigator.credentials.get(request_options_with_mediation_required()); + const fourth_cred = navigator.credentials.get(alt_request_options_with_mediation_required()); + + // Select first account, i.e. from the `third_cred`. + await fedcm_select_account_promise(t, 0); + + // NetworkError is returned when another IDP is selected. + await promise_rejects_dom(t, 'NetworkError', fourth_cred); + const cred = await third_cred; + assert_equals(cred.token, "token"); + }, "Multiple gets after aborting a multi IDP request should work"); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-onload.https.html new file mode 100644 index 0000000000..1b5d744e8f --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-onload.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP multiple gets after onload test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {set_fedcm_cookie, + set_alt_fedcm_cookie, + request_options_with_mediation_required, + alt_request_options_with_mediation_required, + fedcm_select_account_promise} from '../support/fedcm-helper.sub.js'; + +let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]); +const window_loaded = new Promise(resolve => { + window.addEventListener('load', () => { + resolve(); + }); +}); + +promise_test(async t => { + await Promise.all([cookies_promise, window_loaded]); + const first_cred = navigator.credentials.get(request_options_with_mediation_required()); + const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required()); + + // Select first account from the first get() call. + await fedcm_select_account_promise(t, 0); + // NetworkError is returned when another IDP is selected. + await promise_rejects_dom(t, 'NetworkError', second_cred); + const first = await first_cred; + assert_equals(first.token, "token"); +}, "No `get` calls before or during onload, multiple `get` calls after onload in the same task are allowed."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-before-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-before-onload.https.html new file mode 100644 index 0000000000..8c98bf53b0 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-before-onload.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP multiple gets before onload test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {set_fedcm_cookie, + set_alt_fedcm_cookie, + request_options_with_mediation_required, + alt_request_options_with_mediation_required, + fedcm_select_account_promise} from '../support/fedcm-helper.sub.js'; + +let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]); +let has_window_loaded = false; +window.addEventListener('load', () => { + has_window_loaded = true; +}); + +promise_test(async t => { + assert_false(has_window_loaded); + const first_cred = navigator.credentials.get(alt_request_options_with_mediation_required()); + const second_cred = navigator.credentials.get(request_options_with_mediation_required()); + await cookies_promise; + + // Select second account, i.e. from the second get() call. + await fedcm_select_account_promise(t, 1); + await promise_rejects_dom(t, 'NetworkError', first_cred); + const cred = await second_cred; + assert_equals(cred.token, "token"); +}, "Multiple get calls before window onload are allowed."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-during-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-during-onload.https.html new file mode 100644 index 0000000000..bcf70a31c7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-during-onload.https.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP multiple gets during onload test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {set_fedcm_cookie, + set_alt_fedcm_cookie, + request_options_with_mediation_required, + alt_request_options_with_mediation_required, + fedcm_select_account_promise} from '../support/fedcm-helper.sub.js'; + +let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]); + +promise_test(async t => { + const window_loaded = new Promise(resolve => { + window.addEventListener('load', async () => { + const first_cred = navigator.credentials.get(request_options_with_mediation_required()); + const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required()); + await cookies_promise; + await fedcm_select_account_promise(t, 0); + await promise_rejects_dom(t, 'NetworkError', second_cred); + const first = await first_cred; + assert_equals(first.token, "token"); + resolve(); + }); + }); + await window_loaded; +}, "No `get` calls before onload, multiple `get` calls during onload are allowed."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-after-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-after-onload.https.html new file mode 100644 index 0000000000..de6a7c5371 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-after-onload.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP single get after onload test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {set_fedcm_cookie, + request_options_with_mediation_required, + fedcm_get_and_select_first_account} from '../support/fedcm-helper.sub.js'; + +const window_loaded = new Promise(resolve => { + window.addEventListener('load', () => { + resolve(); + }); +}); + +promise_test(async t => { + await set_fedcm_cookie(); + await window_loaded; + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + assert_equals(cred.token, "token"); +}, "Single `get` call after onload is allowed."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-before-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-before-onload.https.html new file mode 100644 index 0000000000..0ac9b0e920 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-before-onload.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP single get before onload test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {set_fedcm_cookie, + request_options_with_mediation_required, + fedcm_select_account_promise} from '../support/fedcm-helper.sub.js'; + +let has_window_loaded = false; +const window_loaded = new Promise(resolve => { + window.addEventListener('load', () => { + has_window_loaded = true; + resolve(); + }); +}); + +promise_test(async t => { + const first_cred = navigator.credentials.get(request_options_with_mediation_required()); + assert_false(has_window_loaded); + await set_fedcm_cookie(); + await window_loaded; + assert_true(has_window_loaded); + + // Select first account after onload. + await fedcm_select_account_promise(t, 0); + const first = await first_cred; + assert_equals(first.token, "token"); +}, "Single `get` call before onload is allowed even if account is selected after."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-during-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-during-onload.https.html new file mode 100644 index 0000000000..832565744d --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-during-onload.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>Federated Credential Management API multi IDP single get during onload test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {request_options_with_mediation_required, + set_fedcm_cookie, + fedcm_get_and_select_first_account} from '../support/fedcm-helper.sub.js'; + +promise_test(async t => { + const window_loaded = new Promise(resolve => { + window.addEventListener('load', async () => { + await set_fedcm_cookie(); + const first_cred = fedcm_get_and_select_first_account(t, + request_options_with_mediation_required()); + const cred = await first_cred; + assert_equals(cred.token, "token"); + resolve(); + }); + }); + await window_loaded; +}, "Single `get` call during onload is allowed."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-no-login-url.https.html b/testing/web-platform/tests/credential-management/fedcm-no-login-url.https.html new file mode 100644 index 0000000000..94592d2dbf --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-no-login-url.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Federated Credential Management API: Missing login URL should fail.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + select_manifest, + mark_signed_in, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + + let test_options = request_options_with_mediation_required("manifest_no_login_url.json"); + await select_manifest(t, test_options); + + const cred = fedcm_get_and_select_first_account(t, test_options); + return promise_rejects_dom(t, 'NetworkError', cred); +}, 'Test that promise is rejected if the manifest has no login URL'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-nonce-is-optional.https.html b/testing/web-platform/tests/credential-management/fedcm-nonce-is-optional.https.html new file mode 100644 index 0000000000..dafd6c9e98 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-nonce-is-optional.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + let test_options = request_options_with_mediation_required(); + assert_true("nonce" in test_options.identity.providers[0]); + delete test_options.identity.providers[0].nonce; + const cred = await fedcm_get_and_select_first_account(t, test_options); + assert_equals(cred.token, "token"); +}, "nonce is not required in FederatedIdentityProvider."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-not-observed-by-service-worker.https.html b/testing/web-platform/tests/credential-management/fedcm-not-observed-by-service-worker.https.html new file mode 100644 index 0000000000..072d669665 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-not-observed-by-service-worker.https.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> + +<body> + +<script type="module"> +import {fedcm_test, + request_options_with_mediation_required, + set_fedcm_cookie, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +function loadUrlInIframe(url) { + let iframe = document.createElement("iframe"); + return new Promise(resolve => { + iframe.src = url; + iframe.onload = function() { resolve(iframe); }; + document.body.appendChild(iframe); + }); +} + +fedcm_test(async t => { + const service_worker_url = 'support/fedcm/intercept_service_worker.js'; + const sw_scope_url = '/credential-management/support/fedcm/'; + // URL for querying number of page loads observed by service worker. + const query_sw_intercepts_url = 'support/fedcm/query_service_worker_intercepts.html'; + const page_in_sw_scope_url = 'support/fedcm/simple.html'; + + const sw_registration = await service_worker_unregister_and_register( + t, service_worker_url, sw_scope_url); + t.add_cleanup(() => service_worker_unregister(t, sw_scope_url)); + await wait_for_state(t, sw_registration.installing, 'activated'); + + // Verify that service worker works. + await loadUrlInIframe(page_in_sw_scope_url); + let query_sw_iframe = await loadUrlInIframe(query_sw_intercepts_url); + assert_equals(query_sw_iframe.contentDocument.body.textContent, "1"); + + await set_fedcm_cookie(); + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + assert_equals(cred.token, "token"); + + // Use cache buster query parameter to avoid cached response. + let query_sw_iframe2 = await loadUrlInIframe(query_sw_intercepts_url + "?2"); + assert_equals(query_sw_iframe2.contentDocument.body.textContent, "1"); +}, 'Test that service worker cannot observe fetches performed by FedCM API'); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-pending-call-rejected.https.html b/testing/web-platform/tests/credential-management/fedcm-pending-call-rejected.https.html new file mode 100644 index 0000000000..feb3f903d8 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-pending-call-rejected.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + alt_request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + const first = fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + const second = navigator.credentials.get(alt_request_options_with_mediation_required()); + + // We have to call promise_rejects_dom here, because if we call it after + // the promise gets rejected, the unhandled rejection event handler is called + // and fails the test even if we handle the rejection later. + const rej = promise_rejects_dom(t, 'AbortError', second); + + const first_cred = await first; + assert_equals(first_cred.token, "token"); + + return rej; +}, "When there's a pending request, a second `get` call should be rejected."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-pending-disconnect.https.html b/testing/web-platform/tests/credential-management/fedcm-pending-disconnect.https.html new file mode 100644 index 0000000000..1b60acc108 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-pending-disconnect.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Federated Credential Management API pending disconnect() test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {fedcm_test, + mark_signed_in, + set_fedcm_cookie, + disconnect_options, + fedcm_get_and_select_first_account, + request_options_with_mediation_required} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + await set_fedcm_cookie(); + // Go through the FedCM flow so that the disconnect() call is not trivial. + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + + // Invoke disconnect without awaiting it to test that the browser can handle + // the page being destroyed while there is a pending disconnect call. + IdentityCredential.disconnect(disconnect_options("1234")); +}, 'Test that disconnect can be pending when the test finishes.'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-pending-userinfo.https.html b/testing/web-platform/tests/credential-management/fedcm-pending-userinfo.https.html new file mode 100644 index 0000000000..0ecae3e80a --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-pending-userinfo.https.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Federated Credential Management API pending getUserInfo() test.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {alt_manifest_origin, + alt_request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +async function createIframeWithPermissionPolicyAndWaitForMessage(test, iframeUrl) { + const messageWatcher = new EventWatcher(test, window, "message"); + let iframe = document.createElement("iframe"); + iframe.src = iframeUrl; + iframe.allow = "identity-credentials-get"; + document.body.appendChild(iframe); + const message = await messageWatcher.wait_for("message"); + return message.data; +} + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required()); + assert_equals(cred.token, "token"); + + const iframe_in_idp_scope = `${alt_manifest_origin}/\ +credential-management/support/fedcm/pending-userinfo-iframe.html`; + const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope); + assert_equals(message, "Pass"); +}, 'Test basic User InFo API flow'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-reject-invalid-responses.https.html b/testing/web-platform/tests/credential-management/fedcm-reject-invalid-responses.https.html new file mode 100644 index 0000000000..f450d56824 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-reject-invalid-responses.https.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {fedcm_test, + request_options_with_mediation_required, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + let test_options = request_options_with_mediation_required(); + test_options.identity.providers = []; + const cred = fedcm_get_and_select_first_account(t, test_options); + return promise_rejects_js(t, TypeError, cred); +}, "Reject when provider list is empty"); + +fedcm_test(async t => { + let test_options = request_options_with_mediation_required(); + delete test_options.identity.providers[0].configURL; + const cred = fedcm_get_and_select_first_account(t, test_options); + return promise_rejects_js(t, TypeError, cred); +}, "Reject when configURL is missing" ); + +fedcm_test(async t => { + let test_options = request_options_with_mediation_required(); + test_options.identity.providers[0].configURL = 'test'; + const cred = fedcm_get_and_select_first_account(t, test_options); + return promise_rejects_dom(t, "InvalidStateError", cred); +}, "Reject when configURL is invalid"); + +fedcm_test(async t => { + let test_options = request_options_with_mediation_required(); + test_options.identity.providers[0].clientId = ''; + const cred = fedcm_get_and_select_first_account(t, test_options); + return promise_rejects_dom(t, "InvalidStateError", cred); +}, "Reject when clientId is empty"); + +fedcm_test(async t => { + let test_options = request_options_with_mediation_required(); + delete test_options.identity.providers[0].clientId; + const cred = fedcm_get_and_select_first_account(t, test_options); + return promise_rejects_js(t, TypeError, cred); +}, "Reject when clientId is missing" ); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html b/testing/web-platform/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html new file mode 100644 index 0000000000..c9fe10a485 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>Federated Credential Management API network request tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_optional, + fedcm_test, + select_manifest, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + let test_options = request_options_with_mediation_optional("manifest_with_single_account.json"); + await select_manifest(t, test_options); + + // Signs in john_doe so that they will be a returning user + let cred = await fedcm_get_and_select_first_account(t, test_options); + assert_equals(cred.token, "account_id=john_doe"); + + test_options = request_options_with_mediation_optional("manifest_with_two_accounts.json"); + await select_manifest(t, test_options); + + // There are two accounts "Jane" and "John" returned in that order. Without + // auto re-authn, the first account "Jane" would be selected and an token + // would be issued to that account. However, since "John" is returning and + // "Jane" is a new user, the second account "John" will be selected. + cred = await navigator.credentials.get(test_options); + assert_equals(cred.token, "account_id=john_doe"); + assert_equals(cred.isAutoSelected, true); +}, "Test that the returning account from the two accounts will be auto re-authenticated."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-store.https.html b/testing/web-platform/tests/credential-management/fedcm-store.https.html new file mode 100644 index 0000000000..d1e6ef464c --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-store.https.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<title>Federated Credential Management API store() tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {fedcm_test, + request_options_with_mediation_required, + fedcm_get_and_select_first_account} from "./support/fedcm-helper.sub.js"; + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + return promise_rejects_dom(t, "NotSupportedError", navigator.credentials.store(cred)); +}, "navigator.credentials.store() with an identity credential returns NotSupportedError"); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-token-returned-with-http-error.https.html b/testing/web-platform/tests/credential-management/fedcm-token-returned-with-http-error.https.html new file mode 100644 index 0000000000..2337829add --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-token-returned-with-http-error.https.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<title>Federated Credential Management API token response tests</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<script type="module"> +import {request_options_with_mediation_required, + fedcm_test, + select_manifest, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + const test_options = + request_options_with_mediation_required("manifest_token_with_http_error.json"); + await select_manifest(t, test_options); + + const cred = fedcm_get_and_select_first_account(t, test_options); + return promise_rejects_dom(t, 'NetworkError', cred); +}, 'Test that the promise will be rejected if the response has http error'); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-too-many-disconnect-calls.https.html b/testing/web-platform/tests/credential-management/fedcm-too-many-disconnect-calls.https.html new file mode 100644 index 0000000000..cb5dfa615f --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-too-many-disconnect-calls.https.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Federated Credential Management API two disconnect() at the same time.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {fedcm_test, + mark_signed_in, + set_fedcm_cookie, + fedcm_get_and_select_first_account, + manifest_origin, + request_options_with_mediation_required, + disconnect_options} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + await mark_signed_in(); + await set_fedcm_cookie(); + // Get at least one connected account that can be disconnected. + const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required()); + const manifest = `${manifest_origin}/\ +credential-management/support/fedcm/manifest.py`; + const options = disconnect_options("1234"); + IdentityCredential.disconnect(options); + await promise_rejects_dom(t, 'NetworkError', IdentityCredential.disconnect(options)); +}, "When disconnect is called while there is a pending one, it is rejected."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-userinfo.https.html b/testing/web-platform/tests/credential-management/fedcm-userinfo.https.html new file mode 100644 index 0000000000..d460d82845 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-userinfo.https.html @@ -0,0 +1,70 @@ +<!DOCTYPE html> +<title>Federated Credential Management API getUserInfo() tests.</title> +<link rel="help" href="https://fedidcg.github.io/FedCM"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<body> + +<script type="module"> +import {alt_manifest_origin, + alt_request_options_with_mediation_required, + fedcm_test, + fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js'; + +async function createIframeWithPermissionPolicyAndWaitForMessage(test, iframeUrl) { + const messageWatcher = new EventWatcher(test, window, "message"); + let iframe = document.createElement("iframe"); + iframe.src = iframeUrl; + iframe.allow = "identity-credentials-get"; + document.body.appendChild(iframe); + const message = await messageWatcher.wait_for("message"); + return message.data; +} + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required()); + assert_equals(cred.token, "token"); + + const iframe_in_idp_scope = `${alt_manifest_origin}/\ +credential-management/support/fedcm/userinfo-iframe.html`; + const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope); + assert_equals(message.result, "Pass"); + assert_equals(message.numAccounts, 1); + assert_equals(message.firstAccountEmail, "john_doe@idp.example"); + assert_equals(message.firstAccountName, "John Doe"); + assert_equals(message.firstAccountGivenName, "John"); + assert_equals(message.firstAccountPicture, "https://idp.example/profile/123"); +}, 'Test basic User InFo API flow'); + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required()); + assert_equals(cred.token, "token"); + + const iframe_in_idp_scope = `support/fedcm/userinfo-iframe.html`; + const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope); + assert_equals(message.result, "Fail"); +}, 'Test that User Info API only works when invoked from iframe that is same origin as the IDP'); + +fedcm_test(async t => { + const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required()); + assert_equals(cred.token, "token"); + + try { + const manifest_path = `${alt_manifest_origin}/\ +credential-management/support/fedcm/manifest.py`; + const user_info = await IdentityProvider.getUserInfo({ + configURL: manifest_path, + // Approved client + clientId: '123', + }); + assert_unreached("Failure message"); + } catch (error) { + assert_equals(error.message, "UserInfo request must be initiated from a frame that is the same origin with the provider."); + // Expect failure + } +}, 'Test that User Info API does not work in the top frame'); + +</script> diff --git a/testing/web-platform/tests/credential-management/federatedcredential-framed-get.sub.https.html b/testing/web-platform/tests/credential-management/federatedcredential-framed-get.sub.https.html new file mode 100644 index 0000000000..7883c8ebf2 --- /dev/null +++ b/testing/web-platform/tests/credential-management/federatedcredential-framed-get.sub.https.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +assert_implements('FederatedCredential' in window, "`FederatedCredential` is supported."); + +// Ensure that the check is "same origin", not "same origin-domain". +document.domain = window.location.hostname; + +function create_iframe_test(origin, expectation) { + return function (t) { + window.addEventListener("load", _ => { + var iframe = document.createElement("iframe"); + iframe.src = origin + "/credential-management/support/federatedcredential-get.html"; + window.addEventListener("message", t.step_func(e => { + if (e.source == iframe.contentWindow) { + if (expectation == "blocked") { + assert_equals(e.data.exception, "NotAllowedError"); + } else { + if (e.data.exception) + assert_not_equals(e.data.exception, "NotAllowedError"); + } + t.done(); + } + })); + document.body.appendChild(iframe); + }); + }; +} + +function create_nested_iframe_test(outerOrigin, innerOrigin, expectation) { + return function (t) { + window.addEventListener("load", _ => { + var iframe = document.createElement("iframe"); + iframe.src = outerOrigin + "/credential-management/support/echoing-nester.html?origin=" + innerOrigin + "&file=federatedcredential-get.html"; + window.addEventListener("message", t.step_func(e => { + if (e.source == iframe.contentWindow) { + if (expectation == "blocked") { + assert_equals(e.data.exception, "NotAllowedError"); + } else { + assert_equals(e.data.exception, null); + } + t.done(); + } + })); + document.body.appendChild(iframe); + }); + }; +} + +const SAME_ORIGIN = window.origin; +const CROSS_ORIGIN = "https://{{domains[élève]}}:{{ports[https][0]}}"; + +async_test( + create_iframe_test(SAME_ORIGIN, "allowed"), + "Same-origin IFrame does not throw."); +async_test( + create_iframe_test(CROSS_ORIGIN, "blocked"), + "Cross-origin IFrame throws 'NotAllowedError'."); + +async_test( + create_nested_iframe_test(SAME_ORIGIN, SAME_ORIGIN, "allowed"), + "Same-origin IFrame in same-origin IFrame does not throw."); + +async_test( + create_nested_iframe_test(SAME_ORIGIN, CROSS_ORIGIN, "blocked"), + "Same-origin IFrame in same-origin IFrame throws 'NotAllowedError'."); + +async_test( + create_nested_iframe_test(CROSS_ORIGIN, SAME_ORIGIN, "blocked"), + "Cross-origin IFrame in same-origin IFrame throws 'NotAllowedError'."); + +async_test( + create_nested_iframe_test(CROSS_ORIGIN, CROSS_ORIGIN, "blocked"), + "Cross-origin IFrame in same-cross-origin throws 'NotAllowedError'."); +</script> diff --git a/testing/web-platform/tests/credential-management/idlharness.https.window.js b/testing/web-platform/tests/credential-management/idlharness.https.window.js new file mode 100644 index 0000000000..26d7c493b0 --- /dev/null +++ b/testing/web-platform/tests/credential-management/idlharness.https.window.js @@ -0,0 +1,37 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +// https://w3c.github.io/webappsec-credential-management/ + +'use strict'; + +idl_test( + ['credential-management'], + ['html', 'dom'], + idl_array => { + idl_array.add_objects({ + CredentialsContainer: ['navigator.credentials'], + PasswordCredential: ['passwordCredential'], + FederatedCredential: ['federatedCredential'], + }); + + try { + self.passwordCredential = new PasswordCredential({ + id: "id", + password: "pencil", + iconURL: "https://example.com/", + name: "name" + }); + } catch (e) {} + + try { + self.federatedCredential = new FederatedCredential({ + id: "id", + provider: "https://example.com", + iconURL: "https://example.com/", + name: "name" + }); + } catch (e) {} + } +) diff --git a/testing/web-platform/tests/credential-management/otpcredential-get-basics.https.html b/testing/web-platform/tests/credential-management/otpcredential-get-basics.https.html new file mode 100644 index 0000000000..3907f85450 --- /dev/null +++ b/testing/web-platform/tests/credential-management/otpcredential-get-basics.https.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<link rel="help" href="https://github.com/WICG/WebOTP"> +<title>Tests OTPCredential</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type="module"> +import {Status, expectOTPRequest} from './support/otpcredential-helper.js'; + +promise_test(async t => { + await expectOTPRequest().andReturn( + () => ({status: Status.SUCCESS, otp: "ABC"})); + + let cred = await navigator.credentials.get({otp: {transport: ["sms"]}}); + + assert_equals(cred.code, "ABC"); +}, 'Basic usage'); + +promise_test(async t => { + await expectOTPRequest().andReturn( + () => ({status: Status.SUCCESS, otp: "ABC"})); + await expectOTPRequest().andReturn( + () => ({status: Status.SUCCESS, otp: "ABC2"})); + + let sms1 = navigator.credentials.get({otp: {transport: ["sms"]}}); + let sms2 = navigator.credentials.get({otp: {transport: ["sms"]}}); + + let cred2 = await sms2; + let cred1 = await sms1; + + assert_equals(cred1.code, "ABC"); + assert_equals(cred2.code, "ABC2"); +}, 'Handle multiple requests in different order.'); + +promise_test(async t => { + await expectOTPRequest().andReturn(() => ({status: Status.CANCELLED})); + await expectOTPRequest().andReturn( + () => ({status: Status.SUCCESS, otp: "success"})); + + let cancelledRequest = navigator.credentials.get({otp: {transport: ["sms"]}}); + let successfulCred = + await navigator.credentials.get({otp: {transport: ["sms"]}}); + + assert_equals(successfulCred.code, "success"); + + try { + await cancelledRequest; + assert_unreached('Expected AbortError to be thrown.'); + } catch (error) { + assert_equals(error.name, "AbortError"); + } +}, 'Handle multiple requests with success and error.'); + +promise_test(async t => { + await expectOTPRequest().andReturn(() => ({status: Status.CANCELLED})); + + await promise_rejects_dom(t, 'AbortError', navigator.credentials.get( + {otp: {transport: ["sms"]}})); +}, 'Deal with cancelled requests'); + +promise_test(async t => { + const controller = new AbortController(); + const signal = controller.signal; + + controller.abort(); + await promise_rejects_dom(t, 'AbortError', navigator.credentials.get( + {otp: {transport: ["sms"]}, signal: signal})); +}, 'Should abort request'); + +promise_test(async t => { + const controller = new AbortController(); + const signal = controller.signal; + + controller.abort('CustomError'); + await promise_rejects_exactly(t, 'CustomError', navigator.credentials.get( + {otp: {transport: ["sms"]}, signal: signal})); +}, 'Should abort request with reason'); +</script> diff --git a/testing/web-platform/tests/credential-management/otpcredential-iframe.https.html b/testing/web-platform/tests/credential-management/otpcredential-iframe.https.html new file mode 100644 index 0000000000..da3e572b6b --- /dev/null +++ b/testing/web-platform/tests/credential-management/otpcredential-iframe.https.html @@ -0,0 +1,54 @@ +<!doctype html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<div id=log> +<script> +'use strict'; + +const host = get_host_info(); +const remoteBaseURL = + host.HTTPS_REMOTE_ORIGIN + + window.location.pathname.replace(/\/[^\/]*$/, '/'); +const localBaseURL = + host.HTTPS_ORIGIN + + window.location.pathname.replace(/\/[^\/]*$/, '/'); + +promise_test(async t => { + const messageWatcher = new EventWatcher(t, window, "message"); + var iframe = document.createElement("iframe"); + iframe.src = localBaseURL + "support/otpcredential-iframe.html"; + + document.body.appendChild(iframe); + + const message = await messageWatcher.wait_for("message"); + assert_equals(message.data.result, "Pass"); + assert_equals(message.data.code, "ABC123"); + +}, "Test OTPCredential enabled in same origin iframes"); + +promise_test(async t => { + const messageWatcher = new EventWatcher(t, window, "message"); + var iframe = document.createElement("iframe"); + iframe.src = remoteBaseURL + "support/otpcredential-iframe.html" + iframe.allow = "otp-credentials"; + document.body.appendChild(iframe); + + const message = await messageWatcher.wait_for("message"); + assert_equals(message.data.result, "Pass"); + assert_equals(message.data.code, "ABC123"); + +}, "OTPCredential enabled in cross origin iframes with permissions policy"); + +promise_test(async t => { + const messageWatcher = new EventWatcher(t, window, "message"); + var iframe = document.createElement("iframe"); + iframe.src = remoteBaseURL + "support/otpcredential-iframe.html" + document.body.appendChild(iframe); + + const message = await messageWatcher.wait_for("message"); + assert_equals(message.data.result, "Fail"); + assert_equals(message.data.errorType, "NotAllowedError"); + +}, "OTPCredential disabled in cross origin iframes without permissions policy"); +</script> diff --git a/testing/web-platform/tests/credential-management/otpcredential-store.https.html b/testing/web-platform/tests/credential-management/otpcredential-store.https.html new file mode 100644 index 0000000000..fa2ae8933d --- /dev/null +++ b/testing/web-platform/tests/credential-management/otpcredential-store.https.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<link rel="help" href="https://github.com/WICG/WebOTP"> +<title>Tests OTPCredential handing of store()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script type="module"> +import {Status, expectOTPRequest} from "./support/otpcredential-helper.js"; + +promise_test(async t => { + await expectOTPRequest().andReturn( + () => ({status: Status.SUCCESS, otp: "ABC"})); + + const cred = await navigator.credentials.get({otp: {transport: ["sms"]}}); + return promise_rejects_dom(t, "NotSupportedError", navigator.credentials.store(cred)); +}, "navigator.credentials.store() with an otp credential returns NotSupportedError"); + +</script> diff --git a/testing/web-platform/tests/credential-management/passwordcredential-framed-get.sub.https.html b/testing/web-platform/tests/credential-management/passwordcredential-framed-get.sub.https.html new file mode 100644 index 0000000000..b4afc1eb91 --- /dev/null +++ b/testing/web-platform/tests/credential-management/passwordcredential-framed-get.sub.https.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +assert_implements('PasswordCredential' in window, "`PasswordCredential` is supported."); + +// Ensure that the check is "same origin", not "same origin-domain". +document.domain = window.location.hostname; + +function create_iframe_test(origin, expectation) { + return function (t) { + window.addEventListener("load", _ => { + var iframe = document.createElement("iframe"); + iframe.src = origin + "/credential-management/support/passwordcredential-get.html"; + window.addEventListener("message", t.step_func(e => { + if (e.source == iframe.contentWindow) { + if (expectation == "blocked") { + assert_equals(e.data.exception, "NotAllowedError"); + } else { + if (e.data.exception) + assert_not_equals(e.data.exception, "NotAllowedError"); + } + t.done(); + } + })); + document.body.appendChild(iframe); + }); + }; +} + +function create_nested_iframe_test(outerOrigin, innerOrigin, expectation) { + return function (t) { + window.addEventListener("load", _ => { + var iframe = document.createElement("iframe"); + iframe.src = outerOrigin + "/credential-management/support/echoing-nester.html?origin=" + innerOrigin + "&file=passwordcredential-get.html"; + window.addEventListener("message", t.step_func(e => { + if (e.source == iframe.contentWindow) { + if (expectation == "blocked") { + assert_equals(e.data.exception, "NotAllowedError"); + } else { + assert_equals(e.data.exception, null); + } + t.done(); + } + })); + document.body.appendChild(iframe); + }); + }; +} + +const SAME_ORIGIN = window.origin; +const CROSS_ORIGIN = "https://{{domains[élève]}}:{{ports[https][0]}}"; + +async_test( + create_iframe_test(SAME_ORIGIN, "allowed"), + "Same-origin IFrame does not throw."); +async_test( + create_iframe_test(CROSS_ORIGIN, "blocked"), + "Cross-origin IFrame throws 'NotAllowedError'."); + +async_test( + create_nested_iframe_test(SAME_ORIGIN, SAME_ORIGIN, "allowed"), + "Same-origin IFrame in same-origin IFrame does not throw."); + +async_test( + create_nested_iframe_test(SAME_ORIGIN, CROSS_ORIGIN, "blocked"), + "Same-origin IFrame in same-origin IFrame throws 'NotAllowedError'."); + +async_test( + create_nested_iframe_test(CROSS_ORIGIN, SAME_ORIGIN, "blocked"), + "Cross-origin IFrame in same-origin IFrame throws 'NotAllowedError'."); + +async_test( + create_nested_iframe_test(CROSS_ORIGIN, CROSS_ORIGIN, "blocked"), + "Cross-origin IFrame in same-cross-origin throws 'NotAllowedError'."); +</script> diff --git a/testing/web-platform/tests/credential-management/require_securecontext.html b/testing/web-platform/tests/credential-management/require_securecontext.html new file mode 100644 index 0000000000..b1f3103da0 --- /dev/null +++ b/testing/web-platform/tests/credential-management/require_securecontext.html @@ -0,0 +1,13 @@ +<!doctype html> +<meta charset=utf-8> +<title>Test that Credential Management requires secure contexts</title> +<link rel="help" href="https://w3c.github.io/webappsec-credential-management/#idl-index"> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +"use strict"; + test(() => { + assert_false(isSecureContext); + assert_false('credentials' in navigator); + }, "Credential Management must not be accessible in insecure contexts"); +</script> diff --git a/testing/web-platform/tests/credential-management/support/README.md b/testing/web-platform/tests/credential-management/support/README.md new file mode 100644 index 0000000000..a6d33ff6f7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/README.md @@ -0,0 +1,54 @@ +# CredentialManagement Testing + +## OTPCredential Testing + +In this test suite `otpcredential-helper.js` is a testing framework that enables +engines to test OTPCredential by intercepting the connection between the browser +and the underlying operating system and mock its behavior. + +Usage: + +1. Include the following in your test: +```html +<script src="/resources/test-only-api.js"></script> +<script src="support/otpcredential-helper.js"></script> +``` +2. Set expectations +```javascript +await expect(receive).andReturn(() => { + // mock behavior +}) +``` +3. Call `navigator.credentials.get({otp: {transport: ["sms"]}})` +4. Verify results + +The mocking API is browser agnostic and is designed such that other engines +could implement it too. + +Here are the symbols that are exposed to tests that need to be implemented +per engine: + +- function receive(): the main/only function that can be mocked +- function expect(): the main/only function that enables us to mock it +- enum State {kSuccess, kTimeout}: allows you to mock success/failures + +## FedCM Testing + +`fedcm-mojojs-helper.js` exposes `fedcm_mojo_mock_test` which is a specialized +`promise_test` which comes pre-setup with the appropriate mocking infrastructure +to emulate platform federated auth backend. The mock is passed to the test +function as the second parameter. + +Example usage: +``` +<script type="module"> + import {fedcm_mojo_mock_test} from './support/fedcm-mojojs-helper.js'; + + fedcm_mojo_mock_test(async (t, mock) => { + mock.returnToken("https://idp.test/fedcm.json", "a_token"); + assert_equals("a_token", await navigator.credentials.get(options)); + }, "Successfully obtaining a token using mock."); +</script> +``` + +The chromium implementation uses the MojoJS shim. diff --git a/testing/web-platform/tests/credential-management/support/echoing-nester.html b/testing/web-platform/tests/credential-management/support/echoing-nester.html new file mode 100644 index 0000000000..d4f5899da7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/echoing-nester.html @@ -0,0 +1,16 @@ +<body> + <script> + window.addEventListener('message', m => { + window.parent.postMessage(m.data, '*'); + }); + + var u = new URL(window.location.href); + var origin = u.searchParams.has('origin') ? u.searchParams.get('origin') : window.origin; + var file = u.searchParams.has('file') ? u.searchParams.get('file') : 'passwordcredential-get.html'; + + var url = origin + "/credential-management/support/" + file; + var i = document.createElement('iframe'); + i.src = url; + document.body.appendChild(i); + </script> +</body> diff --git a/testing/web-platform/tests/credential-management/support/fedcm-helper.sub.js b/testing/web-platform/tests/credential-management/support/fedcm-helper.sub.js new file mode 100644 index 0000000000..765b3cc48a --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm-helper.sub.js @@ -0,0 +1,263 @@ +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 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) => { + if (event.origin == origin) { + 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=Strict; Path=/credential-management/support; Secure'; + return Promise.resolve(); + } else { + return open_and_wait_for_popup(host, '/credential-management/support/set_cookie'); + } +} + +// Set the alternate identity provider cookie. +export function set_alt_fedcm_cookie() { + return set_fedcm_cookie(alt_manifest_origin); +} + +export function mark_signed_in(origin = manifest_origin) { + return open_and_wait_for_popup(origin, '/credential-management/support/mark_signedin'); +} + +export function mark_signed_out(origin = manifest_origin) { + return open_and_wait_for_popup(origin, '/credential-management/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}/\ +credential-management/support/fedcm/${manifest_filename}`; + return { + identity: { + providers: [{ + configURL: manifest_path, + clientId: '1', + 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}/\ +credential-management/support/fedcm/${manifest_filename}`; + return { + identity: { + providers: [{ + configURL: manifest_path, + clientId: '1', + nonce: '2' + }], + context: context + }, + mediation: 'required' + }; +} + + +// Test wrapper which does FedCM-specific setup. +export function fedcm_test(test_func, test_name) { + promise_test(async t => { + 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 = `/credential-management/support/fedcm/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 => { + async function helper() { + // Try to get the dialog type. If the UI is not up yet, we'll catch an exception + // and try again in 100ms. + try { + const type = await window.test_driver.get_fedcm_dialog_type(); + resolve(type); + } catch (ex) { + t.step_timeout(helper, 100); + } + } + helper(); + }); +} + +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 100ms. + try { + const title = await window.test_driver.get_fedcm_dialog_title(); + resolve(title); + } catch (ex) { + t.step_timeout(helper, 100); + } + } + helper(); + }); +} + +export function fedcm_select_account_promise(t, account_index) { + 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 100ms. + try { + await window.test_driver.select_fedcm_account(account_index); + resolve(); + } catch (ex) { + t.step_timeout(helper, 100); + } + } + helper(); + }); +} + +export function fedcm_get_and_select_first_account(t, options) { + const credentialPromise = navigator.credentials.get(options); + fedcm_select_account_promise(t, 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 100ms. + 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, 100); + } + } + 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 100ms. + 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, 100); + } + } + helper(); + }); +} + +export function disconnect_options(accountHint, manifest_filename) { + if (manifest_filename === undefined) { + manifest_filename = "manifest.py"; + } + const manifest_path = `${manifest_origin}/\ +credential-management/support/fedcm/${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}/\ +credential-management/support/fedcm/${manifest_filename}`; + return { + configURL: manifest_path, + clientId: '1', + accountHint: accountHint + }; +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm-iframe-level2.html b/testing/web-platform/tests/credential-management/support/fedcm-iframe-level2.html new file mode 100644 index 0000000000..7622d988ff --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm-iframe-level2.html @@ -0,0 +1,23 @@ +<!doctype html> +<link rel="help" href="https://wicg.github.io/FedCM"> +<script src="/common/get-host-info.sub.js"></script> +<div id=log> +<script> +'use strict'; + +const host = get_host_info(); +const remoteBaseURL = + host.AUTHENTICATED_ORIGIN + + window.location.pathname.replace(/\/[^\/]*$/, '/'); + +window.onload = async () => { + const urlParams = new URLSearchParams(window.location.search); + var iframe = document.createElement("iframe"); + iframe.src = remoteBaseURL + "fedcm-iframe.html"; + if (urlParams.get("permission") != '0') { + iframe.allow = "identity-credentials-get"; + } + document.body.appendChild(iframe); +}; + +</script> diff --git a/testing/web-platform/tests/credential-management/support/fedcm-iframe.html b/testing/web-platform/tests/credential-management/support/fedcm-iframe.html new file mode 100644 index 0000000000..ba79c4cf9e --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm-iframe.html @@ -0,0 +1,42 @@ +<!doctype html> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script type="module"> +import {request_options_with_mediation_required} from './fedcm-helper.sub.js'; + +// Loading fedcm-iframe.html in the test will make a FedCM call on load, and +// trigger a postMessage upon completion. +// +// message { +// string result: "Pass" | "Fail" +// string token: token.token +// string errorType: error.name +// } + +window.onload = async () => { + // Use this variable to stop trying to select an account once the get() promise is resolved. + let cancelHelper = false; + try { + const credentialPromise = navigator.credentials.get(request_options_with_mediation_required()); + async function helper() { + try { + if (cancelHelper) + return; + + await window.test_driver.select_fedcm_account(0); + } catch (ex) { + setTimeout(helper, 100); + } + } + helper(); + const cred = await credentialPromise; + window.top.postMessage({result: "Pass", token: cred.token}, '*'); + } catch (error) { + window.top.postMessage({result: "Fail", errorType: error.name}, '*'); + } + // In case the get() call fails and no accounts may be selected, force the + // helper function to stop calling itself. + cancelHelper = true; +}; + +</script> diff --git a/testing/web-platform/tests/credential-management/support/fedcm-mock.js b/testing/web-platform/tests/credential-management/support/fedcm-mock.js new file mode 100644 index 0000000000..271dd9cd94 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm-mock.js @@ -0,0 +1,147 @@ +import { RequestTokenStatus, LogoutRpsStatus, DisconnectStatus, FederatedAuthRequest, FederatedAuthRequestReceiver } from '/gen/third_party/blink/public/mojom/webid/federated_auth_request.mojom.m.js'; + +function toMojoTokenStatus(status) { + return RequestTokenStatus["k" + status]; +} + +// A mock service for responding to federated auth requests. +export class MockFederatedAuthRequest { + constructor() { + this.receiver_ = new FederatedAuthRequestReceiver(this); + this.interceptor_ = new MojoInterfaceInterceptor(FederatedAuthRequest.$interfaceName); + this.interceptor_.oninterfacerequest = e => { + this.receiver_.$.bindHandle(e.handle); + } + this.interceptor_.start(); + this.token_ = null; + this.selected_identity_provider_config_url_ = null; + this.status_ = RequestTokenStatus.kError; + this.logoutRpsStatus_ = LogoutRpsStatus.kError; + this.disconnectStatus_ = DisconnectStatus.kError; + this.returnPending_ = false; + this.pendingPromiseResolve_ = null; + } + + // Causes the subsequent `navigator.credentials.get()` to resolve with the token. + returnToken(selected_identity_provider_config_url, token) { + this.status_ = RequestTokenStatus.kSuccess; + this.selected_identity_provider_config_url_ = selected_identity_provider_config_url; + this.token_ = token; + this.returnPending_ = false; + } + + // Causes the subsequent `navigator.credentials.get()` to reject with the error. + returnError(error) { + if (error == "Success") + throw new Error("Success is not a valid error"); + this.status_ = toMojoTokenStatus(error); + this.selected_identity_provider_config_url_ = null; + this.token_ = null; + this.returnPending_ = false; + } + + // Causes the subsequent `navigator.credentials.get()` to return a pending promise + // that can be cancelled using `cancelTokenRequest()`. + returnPendingPromise() { + this.returnPending_ = true; + } + + logoutRpsReturn(status) { + let validated = LogoutRpsStatus[status]; + if (validated === undefined) + throw new Error("Invalid status: " + status); + this.logoutRpsStatus_ = validated; + } + + // Causes the subsequent `FederatedCredential.disconnect` to reject with this + // status. + disconnectReturn(status) { + let validated = DisconnectStatus[status]; + if (validated === undefined) + throw new Error("Invalid status: " + status); + this.disconnectStatus_ = validated; + } + + // Implements + // RequestToken(array<IdentityProviderGetParameters> idp_get_params) => + // (RequestTokenStatus status, + // url.mojom.Url? selected_identity_provider_config_url, + // string? token); + async requestToken(idp_get_params) { + if (this.returnPending_) { + this.pendingPromise_ = new Promise((resolve, reject) => { + this.pendingPromiseResolve_ = resolve; + }); + return this.pendingPromise_; + } + return Promise.resolve({ + status: this.status_, + selected_identity_provider_config_url: this.selected_identity_provider_config_url_, + token: this.token_ + }); + } + + async cancelTokenRequest() { + this.pendingPromiseResolve_({ + status: toMojoTokenStatus("ErrorCanceled"), + selected_identity_provider_config_url: null, + token: null + }); + this.pendingPromiseResolve_ = null; + } + + // Implements + // RequestUserInfo(IdentityProviderGetParameters idp_get_param) => + // (RequestUserInfoStatus status, array<IdentityUserInfo>? user_info); + async requestUserInfo(idp_get_param) { + return Promise.resolve({ + status: "", + user_info: "" + }); + } + + async logoutRps(logout_endpoints) { + return Promise.resolve({ + status: this.logoutRpsStatus_ + }); + } + + async disconnect(provider, client_id, account_id) { + return Promise.resolve({ + status: this.disconnectStatus_ + }); + } + + async setIdpSigninStatus(origin, status) { + } + + async registerIdP(configURL) { + } + + async unregisterIdP(configURL) { + } + + async resolveTokenRequest(token) { + } + + async closeModalDialogView() { + } + + async preventSilentAccess() { + } + + async reset() { + this.token_ = null; + this.selected_identity_provider_config_url_ = null; + this.status_ = RequestTokenStatus.kError; + this.logoutRpsStatus_ = LogoutRpsStatus.kError; + this.disconnectStatus_ = DisconnectStatus.kError; + this.receiver_.$.close(); + this.interceptor_.stop(); + + // Clean up and reset mock stubs asynchronously, so that the blink side + // closes its proxies and notifies JS sensor objects before new test is + // started. + await new Promise(resolve => { step_timeout(resolve, 0); }); + } +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm-mojojs-helper.js b/testing/web-platform/tests/credential-management/support/fedcm-mojojs-helper.js new file mode 100644 index 0000000000..40ab729b1f --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm-mojojs-helper.js @@ -0,0 +1,20 @@ +// The testing infra for FedCM is loaded using mojo js shim. To enable these +// tests the browser must be run with these options: +// +// --enable-blink-features=MojoJS,MojoJSTest + +import { MockFederatedAuthRequest } from './fedcm-mock.js'; + +export function fedcm_mojo_mock_test(test_func, name, exception, properties) { + promise_test(async (t) => { + assert_implements(navigator.credentials, 'missing navigator.credentials'); + const mock = new MockFederatedAuthRequest(); + try { + await test_func(t, mock); + } catch (e) { + assert_equals(exception, e.message) + } finally { + await mock.reset(); + } + }, name, properties); +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/accounts.py b/testing/web-platform/tests/credential-management/support/fedcm/accounts.py new file mode 100644 index 0000000000..126f911a58 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/accounts.py @@ -0,0 +1,24 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.accountsCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + return """ +{ + "accounts": [{ + "id": "1234", + "given_name": "John", + "name": "John Doe", + "email": "john_doe@idp.example", + "picture": "https://idp.example/profile/123", + "approved_clients": ["123", "456", "789"], + "login_hints": ["john_doe"], + "domain_hints": ["idp.example", "example"] + }] +} +""" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py new file mode 100644 index 0000000000..72ddcc5cd6 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py @@ -0,0 +1,25 @@ +# 'import credential-management.support.fedcm.keys' does not work. +import importlib +keys = importlib.import_module("credential-management.support.fedcm.keys") +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.clientMetadataCheck(request) + if (request_error): + return request_error + + counter = request.server.stash.take(keys.CLIENT_METADATA_COUNTER_KEY) + try: + counter = int(counter) + 1 + except (TypeError, ValueError): + counter = 1 + + request.server.stash.put(keys.CLIENT_METADATA_COUNTER_KEY, str(counter).encode()) + + response.headers.set(b"Content-Type", b"application/json") + + return """ +{{ + "privacy_policy_url": "https://privacypolicy{0}.com" +}} +""".format(str(counter)) diff --git a/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py.headers b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py.headers new file mode 100644 index 0000000000..7164e5f818 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py.headers @@ -0,0 +1 @@ +Cache-Control: public, max-age=86400 diff --git a/testing/web-platform/tests/credential-management/support/fedcm/client_metadata_clear_count.py b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata_clear_count.py new file mode 100644 index 0000000000..3c31bf5077 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata_clear_count.py @@ -0,0 +1,15 @@ +# 'import credential-management.support.fedcm.keys' does not work. +import importlib +keys = importlib.import_module("credential-management.support.fedcm.keys") + +def main(request, response): + client_metadata_url = "/credential-management/support/fedcm/client_metadata.py" + counter = request.server.stash.take(keys.CLIENT_METADATA_COUNTER_KEY, + client_metadata_url) + + try: + counter = counter.decode() + except (UnicodeDecodeError, AttributeError): + pass + + return counter diff --git a/testing/web-platform/tests/credential-management/support/fedcm/continue_on.py b/testing/web-platform/tests/credential-management/support/fedcm/continue_on.py new file mode 100644 index 0000000000..42b4f3f8fd --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/continue_on.py @@ -0,0 +1,12 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.tokenCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + return "{\"continue_on\": \"resolve.html\"}" + diff --git a/testing/web-platform/tests/credential-management/support/fedcm/disconnect-iframe.html b/testing/web-platform/tests/credential-management/support/fedcm/disconnect-iframe.html new file mode 100644 index 0000000000..f65763932b --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/disconnect-iframe.html @@ -0,0 +1,61 @@ +<!doctype html> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script type="module"> + import {disconnect_options, + request_options_with_mediation_required, + set_fedcm_cookie, manifest_origin} from './../fedcm-helper.sub.js'; + +// Loading this iframe in the test will make a FedCM call on load, and +// trigger a postMessage upon completion. +// +// message { +// string result: "Pass" | "Failed get" | "Failed disconnect" +// string errorType: error.name +// } +async function attemptDisconnect() { + try { + await IdentityCredential.disconnect(disconnect_options("1234")); + window.top.postMessage({result: "Pass"}, "*"); + } catch (error) { + window.top.postMessage({result: "Failed disconnect", errorType: error.name}, + "*"); + } +} + +window.onload = async () => { + const params = new URLSearchParams(document.location.search); + if (params.has("skip_get")) { + attemptDisconnect(); + return; + } + + // Use this variable to stop trying to select an account once the get() + // promise is resolved. + let cancelHelper = false; + try { + const credentialPromise = navigator.credentials.get(request_options_with_mediation_required()); + async function helper() { + try { + if (cancelHelper) + return; + + await window.test_driver.select_fedcm_account(0); + } catch (ex) { + setTimeout(helper, 100); + } + } + helper(); + const cred = await credentialPromise; + await set_fedcm_cookie(manifest_origin); + // Now that we have a get(), attempt to disconnect permission. + attemptDisconnect(); + } catch (error) { + window.top.postMessage({result: "Failed get", errorType: error.name}, '*'); + } + // In case the get() call fails and no accounts may be selected, force the + // helper function to stop calling itself. + cancelHelper = true; +}; +</script> + diff --git a/testing/web-platform/tests/credential-management/support/fedcm/disconnect.py b/testing/web-platform/tests/credential-management/support/fedcm/disconnect.py new file mode 100644 index 0000000000..cf62ceda22 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/disconnect.py @@ -0,0 +1,14 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + response.headers.set(b"Content-Type", b"application/json") + response.headers.set(b"Access-Control-Allow-Origin", request.headers.get(b"origin")) + response.headers.set(b"Access-Control-Allow-Credentials", b"true") + request_error = error_checker.revokeCheck(request) + if request_error: + return request_error + + # Pass the account_hint as the accountId. + account_hint = request.POST.get(b"account_hint") + return f"{{\"account_id\": \"{account_hint}\"}}" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/disconnect_failure.py b/testing/web-platform/tests/credential-management/support/fedcm/disconnect_failure.py new file mode 100644 index 0000000000..f880218b2f --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/disconnect_failure.py @@ -0,0 +1,8 @@ +import importlib + +def main(request, response): + response.headers.set(b"Content-Type", b"application/json") + response.headers.set(b"Access-Control-Allow-Origin", request.headers.get(b"origin")) + response.headers.set(b"Access-Control-Allow-Credentials", b"true") + + return (599, [], "Server failure") diff --git a/testing/web-platform/tests/credential-management/support/fedcm/error_with_code_and_url.py b/testing/web-platform/tests/credential-management/support/fedcm/error_with_code_and_url.py new file mode 100644 index 0000000000..71bfea00f4 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/error_with_code_and_url.py @@ -0,0 +1,12 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.tokenCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + response.status = (401, b"Unauthorized") + + return "{\"error\": {\"code\": \"unauthorized_client\", \"url\": \"error.html\"}}" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/intercept_service_worker.js b/testing/web-platform/tests/credential-management/support/fedcm/intercept_service_worker.js new file mode 100644 index 0000000000..773e38fd21 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/intercept_service_worker.js @@ -0,0 +1,10 @@ +var num_overridden = 0; + +self.addEventListener('fetch', event => { + const url = event.request.url; + if (url.indexOf('query_service_worker_intercepts.html') != -1) { + event.respondWith(new Response(num_overridden)); + } else if (url.indexOf('credential-management/support') != -1) { + ++num_overridden; + } +}); diff --git a/testing/web-platform/tests/credential-management/support/fedcm/keys.py b/testing/web-platform/tests/credential-management/support/fedcm/keys.py new file mode 100644 index 0000000000..6b7d67e21e --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/keys.py @@ -0,0 +1,2 @@ +CLIENT_METADATA_COUNTER_KEY = b"bdc14e3e-b8bc-44a1-8eec-78da5fdacbc3" +MANIFEST_URL_IN_MANIFEST_LIST_KEY = b"7f3f7478-b7f0-41c5-b357-f3ac16f5f25a" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/login.html b/testing/web-platform/tests/credential-management/support/fedcm/login.html new file mode 100644 index 0000000000..78d241cda9 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/login.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<script> +async function doLogin() { + document.cookie = "accounts=1"; + navigator.login.setStatus("logged-in"); + IdentityProvider.close(); +} +window.onload = doLogin; +</script> diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest-not-in-list.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest-not-in-list.json new file mode 100644 index 0000000000..0070066667 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest-not-in-list.json @@ -0,0 +1,6 @@ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest.py b/testing/web-platform/tests/credential-management/support/fedcm/manifest.py new file mode 100644 index 0000000000..a40fc100ee --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest.py @@ -0,0 +1,19 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.manifestCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + return """ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token.py", + "disconnect_endpoint": "disconnect.py", + "login_url": "login.html" +} +""" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_id_assertion_endpoint_returns_error.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_id_assertion_endpoint_returns_error.json new file mode 100644 index 0000000000..e098cc4511 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_id_assertion_endpoint_returns_error.json @@ -0,0 +1,6 @@ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "error_with_code_and_url.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_no_login_url.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_no_login_url.json new file mode 100644 index 0000000000..15a657c679 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_no_login_url.json @@ -0,0 +1,5 @@ +{ + "accounts_endpoint": "single_account.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token_with_account_id.py" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_accounts.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_accounts.json new file mode 100644 index 0000000000..6a8972feeb --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_accounts.json @@ -0,0 +1,6 @@ +{ + "accounts_endpoint": "/common/redirect.py?location=/credential-management/support/fedcm/accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_token.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_token.json new file mode 100644 index 0000000000..867b4dffb7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_token.json @@ -0,0 +1,7 @@ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "/common/redirect.py?location=/credential-management/support/fedcm/token.py&status=308", + "disconnect_endpoint": "disconnect.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_token_with_http_error.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_token_with_http_error.json new file mode 100644 index 0000000000..691a1e8d3a --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_token_with_http_error.json @@ -0,0 +1,6 @@ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token_with_http_error.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_auto_selected_flag.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_auto_selected_flag.json new file mode 100644 index 0000000000..591c927153 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_auto_selected_flag.json @@ -0,0 +1,6 @@ +{ + "accounts_endpoint": "two_accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token_with_auto_selected_flag.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_continue_on.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_continue_on.json new file mode 100644 index 0000000000..3f5a954b87 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_continue_on.json @@ -0,0 +1,8 @@ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "continue_on.py", + "disconnect_endpoint": "disconnect.py", + "login_url": "login.html" +} + diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_cross_origin_disconnect.sub.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_cross_origin_disconnect.sub.json new file mode 100644 index 0000000000..a1ad5c71ac --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_cross_origin_disconnect.sub.json @@ -0,0 +1,7 @@ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token.py", + "disconnect_endpoint": "https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_disconnect_failure.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_disconnect_failure.json new file mode 100644 index 0000000000..96035e7e8b --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_disconnect_failure.json @@ -0,0 +1,7 @@ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token.py", + "disconnect_endpoint": "disconnect_failure.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_no_accounts.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_no_accounts.json new file mode 100644 index 0000000000..0d38f26d35 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_no_accounts.json @@ -0,0 +1,6 @@ +{ + "accounts_endpoint": "no_accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token_with_account_id.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_single_account.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_single_account.json new file mode 100644 index 0000000000..5f9b7a81b9 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_single_account.json @@ -0,0 +1,6 @@ +{ + "accounts_endpoint": "single_account.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token_with_account_id.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_two_accounts.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_two_accounts.json new file mode 100644 index 0000000000..6310fb0a0b --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_two_accounts.json @@ -0,0 +1,6 @@ +{ + "accounts_endpoint": "two_accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token_with_account_id.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_variable_accounts.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_variable_accounts.json new file mode 100644 index 0000000000..10c2ddd55d --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_variable_accounts.json @@ -0,0 +1,6 @@ +{ + "accounts_endpoint": "variable_accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token_with_account_id.py", + "login_url": "login.html" +} diff --git a/testing/web-platform/tests/credential-management/support/fedcm/no_accounts.py b/testing/web-platform/tests/credential-management/support/fedcm/no_accounts.py new file mode 100644 index 0000000000..8767c50afb --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/no_accounts.py @@ -0,0 +1,17 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.accountsCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + return """ +{ + "accounts": [ + ] +} +""" + diff --git a/testing/web-platform/tests/credential-management/support/fedcm/pending-userinfo-iframe.html b/testing/web-platform/tests/credential-management/support/fedcm/pending-userinfo-iframe.html new file mode 100644 index 0000000000..0afe279bcc --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/pending-userinfo-iframe.html @@ -0,0 +1,22 @@ +<!doctype html> +<script type="module"> +import {alt_manifest_origin} from './../fedcm-helper.sub.js'; + +// Invokes getUserInfo and immediately sends a message to the parent frame. +window.onload = async () => { + try { + const manifest_path = `${alt_manifest_origin}/\ +credential-management/support/fedcm/manifest.py`; + IdentityProvider.getUserInfo({ + configURL: manifest_path, + // Approved client + clientId: '123', + }); + window.top.postMessage("Pass", '*'); + } catch (error) { + window.top.postMessage("Fail", '*'); + } +}; + +</script> + diff --git a/testing/web-platform/tests/credential-management/support/fedcm/request-params-check.py b/testing/web-platform/tests/credential-management/support/fedcm/request-params-check.py new file mode 100644 index 0000000000..daf91aad8f --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/request-params-check.py @@ -0,0 +1,101 @@ +def commonCheck(request, mode=b"no-cors"): + if request.headers.get(b"Accept") != b"application/json": + return (531, [], "Wrong Accept") + if request.headers.get(b"Sec-Fetch-Dest") != b"webidentity": + return (532, [], "Wrong Sec-Fetch-Dest header") + if request.headers.get(b"Referer"): + return (533, [], "Should not have Referer") + if request.headers.get(b"Sec-Fetch-Mode") != mode: + return (534, [], "Wrong Sec-Fetch-Mode header") + +def commonUncredentialedRequestCheck(request): + if len(request.cookies) > 0: + return (535, [], "Cookie should not be sent to this endpoint") + if request.headers.get(b"Sec-Fetch-Site") != b"cross-site": + return (536, [], "Wrong Sec-Fetch-Site header") + +def commonCredentialedRequestCheck(request): + if request.cookies.get(b"cookie") != b"1": + return (537, [], "Missing cookie") + if request.headers.get(b"Sec-Fetch-Site") != b"none": + return (538, [], "Wrong Sec-Fetch-Site header") + +def commonPostCheck(request): + if not request.headers.get(b"Origin"): + return (540, [], "Missing Origin") + if request.method != "POST": + return (541, [], "Method is not POST") + if request.headers.get(b"Content-Type") != b"application/x-www-form-urlencoded": + return (542, [], "Wrong Content-Type") + if not request.POST.get(b"client_id"): + return (543, [], "Missing 'client_id' POST parameter") + +def manifestCheck(request): + common_error = commonCheck(request) + if (common_error): + return common_error + common_uncredentialed_error = commonUncredentialedRequestCheck(request) + if (common_uncredentialed_error): + return common_uncredentialed_error + + if request.headers.get(b"Origin"): + return (539, [], "Should not have Origin") + +def clientMetadataCheck(request): + if (request.GET.get(b'skip_checks', b'0') != b'1'): + common_error = commonCheck(request) + if (common_error): + return common_error + common_uncredentialed_error = commonUncredentialedRequestCheck(request) + if (common_uncredentialed_error): + return common_uncredentialed_error + + if not request.headers.get(b"Origin"): + return (540, [], "Missing Origin") + +def accountsCheck(request): + common_error = commonCheck(request) + if (common_error): + return common_error + common_credentialed_error = commonCredentialedRequestCheck(request) + if (common_credentialed_error): + return common_credentialed_error + + if request.headers.get(b"Origin"): + return (539, [], "Should not have Origin") + +def tokenCheck(request): + common_error = commonCheck(request) + if (common_error): + return common_error + common_credentialed_error = commonCredentialedRequestCheck(request) + if (common_credentialed_error): + return common_credentialed_error + + post_error = commonPostCheck(request) + if (post_error): + return post_error + + if not request.POST.get(b"account_id"): + return (544, [], "Missing 'account_id' POST parameter") + if not request.POST.get(b"disclosure_text_shown"): + return (545, [], "Missing 'disclosure_text_shown' POST parameter") + +def revokeCheck(request): + common_error = commonCheck(request, b"cors") + if (common_error): + return common_error + + if request.cookies.get(b"cookie") != b"1": + return (537, [], "Missing cookie") + # The value of the Sec-Fetch-Site header can vary depending on the IdP origin + # but it should not be 'none'. + if request.headers.get(b"Sec-Fetch-Site") == b"none": + return (538, [], "Wrong Sec-Fetch-Site header") + + post_error = commonPostCheck(request) + if (post_error): + return post_error + + if not request.POST.get(b"account_hint"): + return (544, [], "Missing 'account_hint' POST parameter") diff --git a/testing/web-platform/tests/credential-management/support/fedcm/resolve.html b/testing/web-platform/tests/credential-management/support/fedcm/resolve.html new file mode 100644 index 0000000000..87f5112cfd --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/resolve.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<script> +async function doResolve() { + IdentityProvider.resolve("resolved token"); +} +window.onload = doResolve; +</script> + diff --git a/testing/web-platform/tests/credential-management/support/fedcm/select_manifest_in_root_manifest.py b/testing/web-platform/tests/credential-management/support/fedcm/select_manifest_in_root_manifest.py new file mode 100644 index 0000000000..d4f1efff6a --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/select_manifest_in_root_manifest.py @@ -0,0 +1,17 @@ +import importlib +from urllib.parse import urlsplit + +# 'import credential-management.support.fedcm.keys' does not work. +keys = importlib.import_module("credential-management.support.fedcm.keys") + +def main(request, response): + root_manifest_url = "/.well-known/web-identity" + + # Clear stash so that a new value can be written. + request.server.stash.take(keys.MANIFEST_URL_IN_MANIFEST_LIST_KEY, root_manifest_url) + + request.server.stash.put(keys.MANIFEST_URL_IN_MANIFEST_LIST_KEY, + request.GET.first(b"manifest_url", b""), + root_manifest_url) + + return root_manifest_url diff --git a/testing/web-platform/tests/credential-management/support/fedcm/simple.html b/testing/web-platform/tests/credential-management/support/fedcm/simple.html new file mode 100644 index 0000000000..d62419ce8a --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/simple.html @@ -0,0 +1,3 @@ +<!DOCTYPE html> +<html><body> +Simple diff --git a/testing/web-platform/tests/credential-management/support/fedcm/single_account.py b/testing/web-platform/tests/credential-management/support/fedcm/single_account.py new file mode 100644 index 0000000000..7c8906ae7b --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/single_account.py @@ -0,0 +1,24 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.accountsCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + return """ +{ + "accounts": [ + { + "id": "john_doe", + "given_name": "John", + "name": "John Doe", + "email": "john_doe@idp.example", + "picture": "https://idp.example/profile/123", + "approved_clients": ["123", "456", "789"] + } + ] +} +""" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/token.py b/testing/web-platform/tests/credential-management/support/fedcm/token.py new file mode 100644 index 0000000000..b914eb2d96 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/token.py @@ -0,0 +1,11 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.tokenCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + return "{\"token\": \"token\"}" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/token_with_account_id.py b/testing/web-platform/tests/credential-management/support/fedcm/token_with_account_id.py new file mode 100644 index 0000000000..52fb20184b --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/token_with_account_id.py @@ -0,0 +1,12 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.tokenCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + account_id = request.POST.get(b"account_id") + return "{\"token\": \"account_id=" + account_id.decode("utf-8") + "\"}" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/token_with_auto_selected_flag.py b/testing/web-platform/tests/credential-management/support/fedcm/token_with_auto_selected_flag.py new file mode 100644 index 0000000000..93ccf3ee7e --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/token_with_auto_selected_flag.py @@ -0,0 +1,12 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.tokenCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + is_auto_selected = request.POST.get(b"is_auto_selected") + return "{\"token\": \"is_auto_selected=" + is_auto_selected.decode("utf-8") + "\"}" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/token_with_http_error.py b/testing/web-platform/tests/credential-management/support/fedcm/token_with_http_error.py new file mode 100644 index 0000000000..c8d95ab63d --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/token_with_http_error.py @@ -0,0 +1,12 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.tokenCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + response.status = (403, b"Forbidden") + + return "{\"token\": \"token\"}" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/two_accounts.py b/testing/web-platform/tests/credential-management/support/fedcm/two_accounts.py new file mode 100644 index 0000000000..4022561ff7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/two_accounts.py @@ -0,0 +1,34 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.accountsCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + return """ +{ + "accounts": [ + { + "id": "jane_doe", + "given_name": "Jane", + "name": "Jane Doe", + "email": "jane_doe@idp.example", + "picture": "https://idp.example/profile/5678", + "approved_clients": ["123", "abc"] + }, + { + "id": "john_doe", + "given_name": "John", + "name": "John Doe", + "email": "john_doe@idp.example", + "picture": "https://idp.example/profile/123", + "approved_clients": ["123", "456", "789"], + "login_hints": ["john_doe"], + "domain_hints": ["idp.example", "example"] + } + ] +} +""" diff --git a/testing/web-platform/tests/credential-management/support/fedcm/userinfo-iframe.html b/testing/web-platform/tests/credential-management/support/fedcm/userinfo-iframe.html new file mode 100644 index 0000000000..45a1a34ce9 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/userinfo-iframe.html @@ -0,0 +1,37 @@ +<!doctype html> +<script type="module"> +import {alt_manifest_origin} from './../fedcm-helper.sub.js'; + +// Loading fedcm-iframe.html in the test will make a FedCM call on load, and +// trigger a postMessage upon completion. +// +// message { +// string result: "Pass" | "Fail" +// string token: token.token +// string errorType: error.name +// } +window.onload = async () => { + try { + const manifest_path = `${alt_manifest_origin}/\ +credential-management/support/fedcm/manifest.py`; + const user_info = await IdentityProvider.getUserInfo({ + configURL: manifest_path, + // Approved client + clientId: '123', + }); + let results = { + result: "Pass", + numAccounts: user_info.length, + firstAccountEmail: user_info[0].email, + firstAccountName: user_info[0].name, + firstAccountGivenName: user_info[0].givenName, + firstAccountPicture: user_info[0].picture + }; + window.top.postMessage(results, '*'); + } catch (error) { + window.top.postMessage({result: "Fail", errorType: error.name}, '*'); + } +}; + +</script> + diff --git a/testing/web-platform/tests/credential-management/support/fedcm/variable_accounts.py b/testing/web-platform/tests/credential-management/support/fedcm/variable_accounts.py new file mode 100644 index 0000000000..c9db2c4528 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/variable_accounts.py @@ -0,0 +1,33 @@ +import importlib +error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check") + +def main(request, response): + request_error = error_checker.accountsCheck(request) + if (request_error): + return request_error + + response.headers.set(b"Content-Type", b"application/json") + + if request.cookies.get(b"accounts") != b"1": + return """ +{ + "accounts": [ + ] +} +""" + + + return """ +{ + "accounts": [{ + "id": "1234", + "given_name": "John", + "name": "John Doe", + "email": "john_doe@idp.example", + "picture": "https://idp.example/profile/123", + "approved_clients": ["123", "456", "789"], + "login_hints": ["john_doe"], + "hosted_domains": ["idp.example", "example"] + }] +} +""" diff --git a/testing/web-platform/tests/credential-management/support/federatedcredential-get.html b/testing/web-platform/tests/credential-management/support/federatedcredential-get.html new file mode 100644 index 0000000000..476f32688f --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/federatedcredential-get.html @@ -0,0 +1,17 @@ +<script> + navigator.credentials.get({ 'federated': { 'providers': ['https://example.com' ] } }) + .then(c => { + window.parent.postMessage({ + "status": "resolved", + "credential": c, + "exception": null + }, "*"); + }) + .catch(omg => { + window.parent.postMessage({ + "status": "rejected", + "credential": null, + "exception": omg.name + }, "*"); + }); +</script> diff --git a/testing/web-platform/tests/credential-management/support/fencedframe-mark-signedin.html b/testing/web-platform/tests/credential-management/support/fencedframe-mark-signedin.html new file mode 100644 index 0000000000..532db7047a --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fencedframe-mark-signedin.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>A page that uses fencedframe to set the login status to signed in</title> + +<fencedframe></fencedframe> +<script> +const url = new URL("mark_signedin", location.href); +document.querySelector("fencedframe").config = new FencedFrameConfig(url); + +// If this page was opened as a popup, notify the opener when we are done loading. +if (window.opener) { + window.onload = function() { + window.opener.postMessage("done_loading", "*"); + }; +} +</script> diff --git a/testing/web-platform/tests/credential-management/support/iframe-mark-signedin.html b/testing/web-platform/tests/credential-management/support/iframe-mark-signedin.html new file mode 100644 index 0000000000..4ca0125cde --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/iframe-mark-signedin.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<title>A page that includes mark_signedin, for use in an iframe</title> + +<img src="mark_signedin"> diff --git a/testing/web-platform/tests/credential-management/support/mark_signedin b/testing/web-platform/tests/credential-management/support/mark_signedin new file mode 100644 index 0000000000..d9adcaa762 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/mark_signedin @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<body> +<script> +// The important part of this page are the headers. + +// If this page was opened as a popup, notify the opener. +if (window.opener) { + window.opener.postMessage("done_loading", "*"); +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/credential-management/support/mark_signedin.sub.headers b/testing/web-platform/tests/credential-management/support/mark_signedin.sub.headers new file mode 100644 index 0000000000..d560fade5a --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/mark_signedin.sub.headers @@ -0,0 +1,5 @@ +Content-Type: text/html +Set-Login: logged-in +Access-Control-Allow-Origin: https://{{host}}:{{ports[https][0]}} +Access-Control-Allow-Credentials: true +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/credential-management/support/mark_signedout b/testing/web-platform/tests/credential-management/support/mark_signedout new file mode 100644 index 0000000000..d9adcaa762 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/mark_signedout @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<body> +<script> +// The important part of this page are the headers. + +// If this page was opened as a popup, notify the opener. +if (window.opener) { + window.opener.postMessage("done_loading", "*"); +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/credential-management/support/mark_signedout.sub.headers b/testing/web-platform/tests/credential-management/support/mark_signedout.sub.headers new file mode 100644 index 0000000000..69157b3a37 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/mark_signedout.sub.headers @@ -0,0 +1,5 @@ +Content-Type: text/html +Set-Login: logged-out +Access-Control-Allow-Origin: https://{{host}}:{{ports[https][0]}} +Access-Control-Allow-Credentials: true +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/credential-management/support/otpcredential-helper.js b/testing/web-platform/tests/credential-management/support/otpcredential-helper.js new file mode 100644 index 0000000000..e07e9f5be3 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/otpcredential-helper.js @@ -0,0 +1,114 @@ +// These tests rely on the User Agent providing an implementation of +// MockWebOTPService. +// +// In Chromium-based browsers this implementation is provided by a polyfill +// in order to reduce the amount of test-only code shipped to users. To enable +// these tests the browser must be run with these options: +// // --enable-blink-features=MojoJS,MojoJSTest + +import {isChromiumBased} from '/resources/test-only-api.m.js'; + +/** + * This enumeration is used by WebOTP WPTs to control mock backend behavior. + * See MockWebOTPService below. + */ +export const Status = { + SUCCESS: 0, + UNHANDLED_REQUEST: 1, + CANCELLED: 2, + ABORTED: 3, +}; + +/** + * A interface which must be implemented by browsers to support WebOTP WPTs. + */ +export class MockWebOTPService { + /** + * Accepts a function to be invoked in response to the next OTP request + * received by the mock. The (optionally async) function, when executed, must + * return an object with a `status` field holding one of the `Status` values + * defined above, and -- if successful -- an `otp` field containing a + * simulated OTP string. + * + * Tests will call this method directly to inject specific response behavior + * into the browser-specific mock implementation. + */ + async handleNextOTPRequest(responseFunc) {} +} + +/** + * Returns a Promise resolving to a browser-specific MockWebOTPService subclass + * instance if one is available. + */ +async function createBrowserSpecificMockImpl() { + if (isChromiumBased) { + return await createChromiumMockImpl(); + } + throw new Error('Unsupported browser.'); +} + +const asyncMock = createBrowserSpecificMockImpl(); + +export function expectOTPRequest() { + return { + async andReturn(callback) { + const mock = await asyncMock; + mock.handleNextOTPRequest(callback); + } + } +} + +/** + * Instantiates a Chromium-specific subclass of MockWebOTPService. + */ +async function createChromiumMockImpl() { + const {SmsStatus, WebOTPService, WebOTPServiceReceiver} = await import( + '/gen/third_party/blink/public/mojom/sms/webotp_service.mojom.m.js'); + const MockWebOTPServiceChromium = class extends MockWebOTPService { + constructor() { + super(); + this.mojoReceiver_ = new WebOTPServiceReceiver(this); + this.interceptor_ = + new MojoInterfaceInterceptor(WebOTPService.$interfaceName); + this.interceptor_.oninterfacerequest = (e) => { + this.mojoReceiver_.$.bindHandle(e.handle); + }; + this.interceptor_.start(); + this.requestHandlers_ = []; + Object.freeze(this); + } + + handleNextOTPRequest(responseFunc) { + this.requestHandlers_.push(responseFunc); + } + + async receive() { + if (this.requestHandlers_.length == 0) { + throw new Error('Mock received unexpected OTP request.'); + } + + const responseFunc = this.requestHandlers_.shift(); + const response = await responseFunc(); + switch (response.status) { + case Status.SUCCESS: + if (typeof response.otp != 'string') { + throw new Error('Mock success results require an OTP string.'); + } + return {status: SmsStatus.kSuccess, otp: response.otp}; + case Status.UNHANDLED_REQUEST: + return {status: SmsStatus.kUnhandledRequest}; + case Status.CANCELLED: + return {status: SmsStatus.kCancelled}; + case Status.ABORTED: + return {status: SmsStatus.kAborted}; + default: + throw new Error( + `Mock result contains unknown status: ${response.status}`); + } + } + + async abort() {} + }; + return new MockWebOTPServiceChromium(); +} + diff --git a/testing/web-platform/tests/credential-management/support/otpcredential-iframe.html b/testing/web-platform/tests/credential-management/support/otpcredential-iframe.html new file mode 100644 index 0000000000..4affc00ecd --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/otpcredential-iframe.html @@ -0,0 +1,26 @@ +<!doctype html> +<script type="module"> +import {Status, expectOTPRequest} from './otpcredential-helper.js'; + +// Loading otpcredential-iframe.html in the test will make an OTPCredentials +// call on load, and trigger a postMessage upon completion. +// +// message { +// string result: "Pass" | "Fail" +// string code: credentials.code +// string errorType: error.name +// } + +window.onload = async () => { + try { + await expectOTPRequest().andReturn( + () => ({status: Status.SUCCESS, otp: "ABC123"})); + const credentials = + await navigator.credentials.get({otp: {transport: ["sms"]}}); + window.parent.postMessage({result: "Pass", code: credentials.code}, '*'); + } catch (error) { + window.parent.postMessage({result: "Fail", errorType: error.name}, '*'); + } +} + +</script> diff --git a/testing/web-platform/tests/credential-management/support/passwordcredential-get.html b/testing/web-platform/tests/credential-management/support/passwordcredential-get.html new file mode 100644 index 0000000000..0ec584d73d --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/passwordcredential-get.html @@ -0,0 +1,17 @@ +<script> + navigator.credentials.get({ 'password': true }) + .then(c => { + window.parent.postMessage({ + "status": "resolved", + "credential": c, + "exception": null + }, "*"); + }) + .catch(omg => { + window.parent.postMessage({ + "status": "rejected", + "credential": null, + "exception": omg.name + }, "*"); + }); +</script> diff --git a/testing/web-platform/tests/credential-management/support/set_cookie b/testing/web-platform/tests/credential-management/support/set_cookie new file mode 100644 index 0000000000..1080b366e4 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/set_cookie @@ -0,0 +1,12 @@ +<html> +<body> +<script> +// The important part of this page are the headers. + +// If this page was opened as a popup, notify the opener. +if (window.opener) { + window.opener.postMessage("done_loading", "*"); +} +</script> +</body> +</html> diff --git a/testing/web-platform/tests/credential-management/support/set_cookie.headers b/testing/web-platform/tests/credential-management/support/set_cookie.headers new file mode 100644 index 0000000000..b19ff933a6 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/set_cookie.headers @@ -0,0 +1,2 @@ +Content-Type: text/html +Set-Cookie: cookie=1; SameSite=None; Secure |