diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/credential-management | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/credential-management')
51 files changed, 1759 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/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..808b7f6ea7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html @@ -0,0 +1,18 @@ +<!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> + +<body> + +<script type="module"> +import {default_request_options, + fedcm_test} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + const cred = await navigator.credentials.get(default_request_options()); + 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..59c97e8c38 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-csp.https.html @@ -0,0 +1,17 @@ +<!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 {default_request_options, fedcm_test, set_fedcm_cookie} from './support/fedcm-helper.sub.js'; + +fedcm_test(async t => { + const cred = navigator.credentials.get(default_request_options()); + 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-iframe.https.html b/testing/web-platform/tests/credential-management/fedcm-iframe.https.html new file mode 100644 index 0000000000..43fe39f7bc --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-iframe.https.html @@ -0,0 +1,61 @@ +<!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(); +const remoteBaseURL = + host.HTTPS_REMOTE_ORIGIN + + window.location.pathname.replace(/\/[^\/]*$/, '/'); + +async function createIframeAndWaitForMessage(test, iframeUrl, setPermissionPolicy) { + const messageWatcher = new EventWatcher(test, window, "message"); + var iframe = document.createElement("iframe"); + iframe.src = iframeUrl; + if (setPermissionPolicy) { + 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, 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-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"); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-logout-rps.https.html b/testing/web-platform/tests/credential-management/fedcm-logout-rps.https.html new file mode 100644 index 0000000000..51b123003c --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-logout-rps.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>IdentityCredential.logoutRPs() promise resolution</title> +<link rel="author" title="Peter Kotwicz" href="mailto:pkotwicz@chromium.org"> +<link rel="help" href="https://wicg.github.io/FedCM/#browser-api-idp-sign-out"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<script type="module"> + import {fedcm_mojo_mock_test} from './support/fedcm-mojojs-helper.js'; + + fedcm_mojo_mock_test(async (t, mock) => { + mock.logoutRpsReturn("kError"); + return promise_rejects_dom(t, "NetworkError", + IdentityCredential.logoutRPs([{ + accountId: "1234", + url: "https://rp.example/logout.php" + }]) + ); + }, "IdentityCredential.logoutRPs() error."); + + fedcm_mojo_mock_test(async (t, mock) => { + mock.logoutRpsReturn("kSuccess"); + await IdentityCredential.logoutRPs([{ + accountId: "1234", + url: "https://rp.example/logout.php" + }]); + }, "IdentityCredential.logoutRPs() success."); + + fedcm_mojo_mock_test(async (t, mock) => { + return promise_rejects_dom(t, "NetworkError", + IdentityCredential.logoutRPs([{ + accountId: "1234", + url: "https://other-rp.example/logout.php" + }]) + ); + }, "Logout URL should honor Content-Security-Policy."); +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-logout-rps.https.html.headers b/testing/web-platform/tests/credential-management/fedcm-logout-rps.https.html.headers new file mode 100644 index 0000000000..90454dbb22 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-logout-rps.https.html.headers @@ -0,0 +1 @@ +Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; connect-src https://rp.example 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..fa9ec7b52f --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-after-onload.https.html @@ -0,0 +1,37 @@ +<!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> + +<body> + +<script type="module"> +import {set_fedcm_cookie, set_alt_fedcm_cookie, default_request_options, default_alt_request_options} 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(default_request_options()).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 + const second_cred = navigator.credentials.get(default_alt_request_options()); + const rejection = promise_rejects_dom(t, 'NotAllowedError', second_cred); + 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..93ee2075fe --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-during-onload.https.html @@ -0,0 +1,32 @@ +<!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> + +<body> + +<script type="module"> +import {set_fedcm_cookie, set_alt_fedcm_cookie, default_request_options, default_alt_request_options} 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; + const window_loaded = new Promise(resolve => { + window.addEventListener('load', async () => { + const second_cred = navigator.credentials.get(default_alt_request_options()); + await promise_rejects_dom(t, 'NetworkError', second_cred); + has_window_loaded = true; + resolve(); + }); + }); + assert_false(has_window_loaded); + const first_cred = navigator.credentials.get(default_request_options()); + await Promise.all([cookies_promise, window_loaded]); + assert_true(has_window_loaded); + const first = await first_cred; + assert_equals(first.token, "token"); +}, "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..edc293daff --- /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,32 @@ +<!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> + +<body> + +<script type="module"> +import {set_fedcm_cookie, set_alt_fedcm_cookie, default_request_options, default_alt_request_options} 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; + const dom_content_loaded = new Promise(resolve => { + document.addEventListener('DOMContentLoaded', async () => { + const second_cred = navigator.credentials.get(default_alt_request_options()); + await 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(default_request_options()); + await Promise.all([cookies_promise, dom_content_loaded]); + assert_true(has_dom_content_loaded); + const first = await first_cred; + assert_equals(first.token, "token"); +}, "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-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..1a806a2049 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-onload.https.html @@ -0,0 +1,29 @@ +<!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> + +<body> + +<script type="module"> +import {set_fedcm_cookie, set_alt_fedcm_cookie, default_request_options, default_alt_request_options} 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(default_request_options()); + const second_cred = navigator.credentials.get(default_alt_request_options()); + // 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..69a70ce47d --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-before-onload.https.html @@ -0,0 +1,29 @@ +<!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> + +<body> + +<script type="module"> +import {set_fedcm_cookie, set_alt_fedcm_cookie, default_request_options, default_alt_request_options} 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; +}); + +// TODO(crbug.com/1374869): Add multi IDP test where second IDP is selected. +promise_test(async t => { + assert_false(has_window_loaded); + const first_cred = navigator.credentials.get(default_alt_request_options()); + const second_cred = navigator.credentials.get(default_request_options()); + await cookies_promise; + await promise_rejects_dom(t, 'NetworkError', second_cred); + const first = await first_cred; + assert_equals(first.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..e98b63ff33 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-during-onload.https.html @@ -0,0 +1,29 @@ +<!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> + +<body> + +<script type="module"> +import {set_fedcm_cookie, set_alt_fedcm_cookie, default_request_options, default_alt_request_options} 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(default_request_options()); + const second_cred = navigator.credentials.get(default_alt_request_options()); + await cookies_promise; + 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..a34ff84aab --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-after-onload.https.html @@ -0,0 +1,25 @@ +<!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> + +<body> + +<script type="module"> +import {set_fedcm_cookie, default_request_options} 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 navigator.credentials.get(default_request_options()); + 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..91195fc5a7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-before-onload.https.html @@ -0,0 +1,30 @@ +<!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> + +<body> + +<script type="module"> +import {set_fedcm_cookie, default_request_options} 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(default_request_options()); + assert_false(has_window_loaded); + await set_fedcm_cookie(); + await window_loaded; + assert_true(has_window_loaded); + const first = await first_cred; + assert_equals(first.token, "token"); +}, "Single `get` call before onload is allowed."); + +</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..17939d2d93 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-during-onload.https.html @@ -0,0 +1,25 @@ +<!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> + +<body> + +<script type="module"> +import {default_request_options, set_fedcm_cookie} from '../support/fedcm-helper.sub.js'; + +promise_test(async t => { + const window_loaded = new Promise(resolve => { + window.addEventListener('load', async () => { + const first_cred = navigator.credentials.get(default_request_options()); + await set_fedcm_cookie(); + const first = await first_cred; + assert_equals(first.token, "token"); + resolve(); + }); + }); + await window_loaded; +}, "Single `get` call during onload is allowed."); + +</script> diff --git a/testing/web-platform/tests/credential-management/fedcm-network-requests.https.html b/testing/web-platform/tests/credential-management/fedcm-network-requests.https.html new file mode 100644 index 0000000000..ebcf61ba18 --- /dev/null +++ b/testing/web-platform/tests/credential-management/fedcm-network-requests.https.html @@ -0,0 +1,192 @@ +<!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="/service-workers/service-worker/resources/test-helpers.sub.js"></script> + +<body> + +<script type="module"> +import {default_request_options, + default_alt_request_options, + fedcm_test, + select_manifest, + set_fedcm_cookie} 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 cred = await navigator.credentials.get(default_request_options()); + assert_equals(cred.token, "token"); +}, "Successfully obtaining token should resolve the promise."); + +fedcm_test(async t => { + const first = navigator.credentials.get(default_request_options()); + const second = navigator.credentials.get(default_alt_request_options()); + + // 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. "); + +fedcm_test(async t => { + let test_options = default_request_options(); + test_options.identity.providers = []; + const cred = navigator.credentials.get(test_options); + return promise_rejects_js(t, TypeError, cred); +}, "Reject when provider list is empty"); + +fedcm_test(async t => { + let test_options = default_request_options(); + delete test_options.identity.providers[0].configURL; + const cred = navigator.credentials.get(test_options); + return promise_rejects_js(t, TypeError, cred); +}, "Reject when configURL is missing" ); + +fedcm_test(async t => { + let test_options = default_request_options(); + test_options.identity.providers[0].configURL = 'test'; + const cred = navigator.credentials.get(test_options); + return promise_rejects_dom(t, "InvalidStateError", cred); +}, "Reject when configURL is invalid"); + +fedcm_test(async t => { + let test_options = default_request_options(); + test_options.identity.providers[0].clientId = ''; + const cred = navigator.credentials.get(test_options); + return promise_rejects_dom(t, "InvalidStateError", cred); +}, "Reject when clientId is empty"); + +fedcm_test(async t => { + let test_options = default_request_options(); + assert_true("nonce" in test_options.identity.providers[0]); + delete test_options.identity.providers[0].nonce; + const cred = await navigator.credentials.get(test_options); + assert_equals(cred.token, "token"); +}, "nonce is not required in FederatedIdentityProvider."); + +fedcm_test(async t => { + let test_options = default_request_options(); + delete test_options.identity.providers[0].clientId; + const cred = navigator.credentials.get(test_options); + return promise_rejects_js(t, TypeError, cred); +}, "Reject when clientId is missing" ); + +fedcm_test(async t => { + let controller = new AbortController(); + let test_options = default_request_options(); + test_options.signal = controller.signal; + const cred = navigator.credentials.get(test_options); + controller.abort(); + return promise_rejects_dom(t, 'AbortError', cred); +}, "Test the abort signal"); + +fedcm_test(async t => { + let controller = new AbortController(); + let test_options = default_request_options(); + test_options.signal = controller.signal; + const first_cred = navigator.credentials.get(test_options); + controller.abort(); + await promise_rejects_dom(t, 'AbortError', first_cred); + + const second_cred = await navigator.credentials.get(default_request_options()); + assert_equals(second_cred.token, "token"); +}, "Get after abort should work"); + +fedcm_test(async t => { + let test_options = default_request_options('manifest-not-in-list.json'); + const cred = navigator.credentials.get(test_options); + return promise_rejects_dom(t, 'NetworkError', cred); +}, 'Test that the promise is rejected if the manifest is not in the manifest list'); + +fedcm_test(async t => { + let test_options = default_request_options("manifest_redirect_accounts.json"); + await select_manifest(t, test_options); + + const cred = navigator.credentials.get(test_options); + return promise_rejects_dom(t, 'NetworkError', cred); +}, 'Test that promise is rejected if accounts endpoint redirects'); +// A malicious site might impersonate an IDP, redirecting the accounts endpoint to a +// legitimate IDP in order to get the list of user accounts. + +fedcm_test(async t => { + let test_options = default_request_options("manifest_redirect_token.json"); + await select_manifest(t, test_options); + + const cred = navigator.credentials.get(test_options); + return promise_rejects_dom(t, 'NetworkError', cred); +}, 'Test that token endpoint does not follow redirects'); +// The token endpoint should not follow redirects because the user has not consented +// to share their identity with the redirect destination. + +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); + + const cred = await navigator.credentials.get(default_request_options()); + assert_equals(cred.token, "token"); + + await new Promise(resolve => { + 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() + assert_equals(client_metadata_counter_text, "2"); +}, 'Test client_metadata request'); +// Test: +// - Headers sent to client metadata endpoint. (Counter is not incremented if the headers are +// wrong.) +// - That the client metadata response is not cached. If the client metadata response were +// 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. + +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 navigator.credentials.get(default_request_options()); + 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/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/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..87a1337a06 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm-helper.sub.js @@ -0,0 +1,99 @@ +const alt_manifest_origin = 'https://{{hosts[alt][]}}:{{ports[https][0]}}'; + +// Set the identity provider cookie. +export function set_fedcm_cookie(host) { + return new Promise(resolve => { + if (host == undefined) { + document.cookie = 'cookie=1; SameSite=Strict; Path=/credential-management/support; Secure'; + resolve(); + } else { + let popup_window = window.open(host + '/credential-management/support/set_cookie'); + + const popup_message_handler = (event) => { + if (event.origin == host) { + popup_window.close(); + window.removeEventListener('message', popup_message_handler); + resolve(); + } + }; + + window.addEventListener('message', popup_message_handler); + } + }); +} + +// Set the alternate identity provider cookie. +export function set_alt_fedcm_cookie() { + return set_fedcm_cookie(alt_manifest_origin); +} + +// Returns FedCM CredentialRequestOptions for which navigator.credentials.get() +// succeeds. +export function default_request_options(manifest_filename) { + if (manifest_filename === undefined) { + manifest_filename = "manifest.py"; + } + const manifest_path = `https://{{host}}:{{ports[https][0]}}/\ +credential-management/support/fedcm/${manifest_filename}`; + return { + identity: { + providers: [{ + configURL: manifest_path, + clientId: '1', + nonce: '2', + }] + } + }; +} + +// Returns alternate FedCM CredentialRequestOptions for which navigator.credentials.get() +// succeeds. +export function default_alt_request_options(manifest_filename) { + if (manifest_filename === undefined) { + manifest_filename = "manifest.py"; + } + const manifest_path = `${alt_manifest_origin}/\ +credential-management/support/fedcm/${manifest_filename}`; + return { + identity: { + providers: [{ + configURL: manifest_path, + clientId: '1', + nonce: '2', + }] + } + }; +} + +// 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 = '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); +} 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..c57f54e1da --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm-iframe.html @@ -0,0 +1,23 @@ +<!doctype html> +<script type="module"> +import {default_request_options} 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 cred = await navigator.credentials.get(default_request_options()); + window.top.postMessage({result: "Pass", token: cred.token}, '*'); + } catch (error) { + window.top.postMessage({result: "Fail", errorType: error.name}, '*'); + } +}; + +</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..f52bd6e0e7 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm-mock.js @@ -0,0 +1,105 @@ +import { RequestTokenStatus, LogoutRpsStatus, 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.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; + } + + // 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; + } + + async logoutRps(logout_endpoints) { + return Promise.resolve({ + status: this.logoutRpsStatus_ + }); + } + + async setIdpSigninStatus(origin, status) { + } + + async reset() { + this.token_ = null; + this.selected_identity_provider_config_url_ = null; + this.status_ = RequestTokenStatus.kError; + this.logoutRpsStatus_ = LogoutRpsStatus.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..088ce5967c --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/accounts.py @@ -0,0 +1,24 @@ +def main(request, response): + if request.cookies.get(b"cookie") != b"1": + return (530, [], "Missing cookie") + 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"Origin"): + return (534, [], "Should not have Origin") + + 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"] + }] +} +""" 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..e6f6a77632 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py @@ -0,0 +1,30 @@ +# 'import credential-management.support.fedcm.keys' does not work. +import importlib +keys = importlib.import_module("credential-management.support.fedcm.keys") + +def main(request, response): + if (request.GET.get(b'skip_checks', b'0') != b'1'): + if len(request.cookies) > 0: + return (530, [], "Cookie should not be sent to this endpoint") + 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 not request.headers.get(b"Origin"): + return (534, [], "Missing Origin") + + 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()) + + 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/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/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..c66903cfd2 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest-not-in-list.json @@ -0,0 +1,5 @@ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token.py" +} 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..3e72faf75a --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest.py @@ -0,0 +1,20 @@ +def main(request, response): + if len(request.cookies) > 0: + return (530, [], "Cookie should not be sent to manifest endpoint") + 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"Origin"): + return (534, [], "Should not have Origin") + + return """ +{ + "accounts_endpoint": "accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token.py", + "revocation_endpoint": "revoke.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..590704cfeb --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_accounts.json @@ -0,0 +1,5 @@ +{ + "accounts_endpoint": "/common/redirect.py?location=/credential-management/support/fedcm/accounts.py", + "client_metadata_endpoint": "client_metadata.py", + "id_assertion_endpoint": "token.py" +} 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..190420736d --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_token.json @@ -0,0 +1,6 @@ +{ + "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", + "revocation_endpoint": "revoke.py" +} 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/token.py b/testing/web-platform/tests/credential-management/support/fedcm/token.py new file mode 100644 index 0000000000..867dab9592 --- /dev/null +++ b/testing/web-platform/tests/credential-management/support/fedcm/token.py @@ -0,0 +1,24 @@ +def main(request, response): + if request.cookies.get(b"cookie") != b"1": + return (530, [], "Missing cookie") + if request.method != "POST": + return (531, [], "Method is not POST") + if request.headers.get(b"Content-Type") != b"application/x-www-form-urlencoded": + return (532, [], "Wrong Content-Type") + if request.headers.get(b"Accept") != b"application/json": + return (533, [], "Wrong Accept") + if request.headers.get(b"Sec-Fetch-Dest") != b"webidentity": + return (500, [], "Wrong Sec-Fetch-Dest header") + if request.headers.get(b"Referer"): + return (534, [], "Should not have Referer") + if not request.headers.get(b"Origin"): + return (535, [], "Missing Origin") + + if not request.POST.get(b"client_id"): + return (536, [], "Missing 'client_id' POST parameter") + if not request.POST.get(b"account_id"): + return (537, [], "Missing 'account_id' POST parameter") + if not request.POST.get(b"disclosure_text_shown"): + return (538, [], "Missing 'disclosure_text_shown' POST parameter") + + return "{\"token\": \"token\"}" 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/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..cf5ea7fff9 --- /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=Strict; Secure |