Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!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 {alt_manifest_origin,
default_request_options,
default_alt_request_options,
request_options_with_auto_reauthn,
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);
});
}
async function createIframeWithPermissionPolicyAndWaitForMessage(test, iframeUrl) {
const messageWatcher = new EventWatcher(test, window, "message");
let iframe = document.createElement("iframe");
iframe.src = iframeUrl;
iframe.allow = "identity-credentials-get";
document.body.appendChild(iframe);
const message = await messageWatcher.wait_for("message");
return message.data;
}
fedcm_test(async t => {
const cred = await 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');
fedcm_test(async t => {
const cred = await navigator.credentials.get(default_alt_request_options());
assert_equals(cred.token, "token");
const iframe_in_idp_scope = `${alt_manifest_origin}/\
credential-management/support/fedcm/userinfo-iframe.html`;
const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
assert_equals(message.result, "Pass");
assert_equals(message.numAccounts, 1);
assert_equals(message.firstAccountEmail, "john_doe@idp.example");
}, 'Test basic User InFo API flow');
fedcm_test(async t => {
const cred = await navigator.credentials.get(default_alt_request_options());
assert_equals(cred.token, "token");
const iframe_in_idp_scope = `support/fedcm/userinfo-iframe.html`;
const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
assert_equals(message.result, "Fail");
}, 'Test that User Info API only works when invoked from iframe that is same origin as the IDP');
fedcm_test(async t => {
const cred = await navigator.credentials.get(default_alt_request_options());
assert_equals(cred.token, "token");
try {
const manifest_path = `${alt_manifest_origin}/\
credential-management/support/fedcm/manifest.py`;
const user_info = await IdentityProvider.getUserInfo({
configURL: manifest_path,
// Approved client
clientId: '123',
});
assert_unreached("Failure message");
} catch (error) {
assert_equals(error.message, "UserInfo request must be initiated from a frame that is the same origin with the provider.");
// Expect failure
}
}, 'Test that User Info API does not work in the top frame');
fedcm_test(async t => {
let test_options = request_options_with_auto_reauthn("manifest_with_single_account.json");
await select_manifest(t, test_options);
// Signs in john_doe so that they will be a returning user
let cred = await navigator.credentials.get(test_options);
assert_equals(cred.token, "account_id=john_doe");
test_options = request_options_with_auto_reauthn("manifest_with_two_accounts.json");
await select_manifest(t, test_options);
// There are two accounts "Jane" and "John" returned in that order. Without
// auto re-authn, the first account "Jane" would be selected and an token
// would be issued to that account. However, since "John" is returning and
// "Jane" is a new user, the second account "John" will be selected.
cred = await navigator.credentials.get(test_options);
assert_equals(cred.token, "account_id=john_doe");
}, "Test that the returning account from the two accounts will be auto re-authenticated.");
</script>