summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/credential-management/support
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/credential-management/support
parentInitial commit. (diff)
downloadfirefox-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/support')
-rw-r--r--testing/web-platform/tests/credential-management/support/README.md54
-rw-r--r--testing/web-platform/tests/credential-management/support/echoing-nester.html16
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-helper.sub.js99
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-iframe-level2.html23
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-iframe.html23
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-mock.js105
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-mojojs-helper.js20
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/accounts.py24
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py30
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py.headers1
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/client_metadata_clear_count.py15
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/intercept_service_worker.js10
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/keys.py2
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest-not-in-list.json5
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest.py20
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_accounts.json5
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_token.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/select_manifest_in_root_manifest.py17
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/simple.html3
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/token.py24
-rw-r--r--testing/web-platform/tests/credential-management/support/federatedcredential-get.html17
-rw-r--r--testing/web-platform/tests/credential-management/support/otpcredential-helper.js114
-rw-r--r--testing/web-platform/tests/credential-management/support/otpcredential-iframe.html26
-rw-r--r--testing/web-platform/tests/credential-management/support/passwordcredential-get.html17
-rw-r--r--testing/web-platform/tests/credential-management/support/set_cookie12
-rw-r--r--testing/web-platform/tests/credential-management/support/set_cookie.headers2
26 files changed, 690 insertions, 0 deletions
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