Skip to content

Commit

Permalink
Digital Credentials API: handle mediation requirement
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=277322
rdar://133266859

Reviewed by Anne van Kesteren.

Make sure mediation is alway required when getting digital credentials
as required by the spec:
WICG/digital-credentials#149

* LayoutTests/http/wpt/identity/identitycredentialscontainer-get-hidden.https.html:
* LayoutTests/imported/w3c/web-platform-tests/digital-credentials/allow-attribute.https.html:
* LayoutTests/imported/w3c/web-platform-tests/digital-credentials/dc-types.ts:
* LayoutTests/imported/w3c/web-platform-tests/digital-credentials/default-permissions-policy.https.sub.html:
* LayoutTests/imported/w3c/web-platform-tests/digital-credentials/disabled-by-permissions-policy.https.sub.html:
* LayoutTests/imported/w3c/web-platform-tests/digital-credentials/enabled-on-self-origin-by-permissions-policy.https.sub.html:
* LayoutTests/imported/w3c/web-platform-tests/digital-credentials/identity-get.tentative.https-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/digital-credentials/identity-get.tentative.https.html:
* LayoutTests/imported/w3c/web-platform-tests/digital-credentials/support/helper.js:
(export.makeGetOptions):
* LayoutTests/imported/w3c/web-platform-tests/permissions-policy/resources/digital-credentials-get.html:
* LayoutTests/platform/ios/imported/w3c/web-platform-tests/digital-credentials/identity-get.tentative.https-expected.txt:
* Source/WebCore/Modules/identity/IdentityCredentialsContainer.cpp:
(WebCore::IdentityCredentialsContainer::get):

Canonical link: https://commits.webkit.org/283148@main
  • Loading branch information
marcoscaceres committed Sep 4, 2024
1 parent 3a6f818 commit 58fcbbf
Show file tree
Hide file tree
Showing 12 changed files with 50 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
digital: {
providers: [],
},
mediation: "required",
});

await promise_rejects_dom(t, "NotAllowedError", p);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
// Results in TypeError when allowed, NotAllowedError when disallowed
providers: [],
},
mediation: "required",
};
const { data } = await new Promise((resolve) => {
window.addEventListener("message", resolve, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export type ProviderType = "default" | "openid4vp";
export type CredentialMediationRequirement =
| "conditional"
| "optional"
| "required"
| "silent";

/**
* @see https://wicg.github.io/digital-credentials/#dom-identityrequestprovider
Expand Down Expand Up @@ -26,12 +31,17 @@ export interface CredentialRequestOptions {
* The digital credential request options.
*/
digital: DigitalCredentialRequestOptions;
mediation: CredentialMediationRequirement;
}

/**
* The actions that can be performed on the API via the iframe.
*/
export type IframeActionType = "create" | "get" | "ping" | "preventSilentAccess" ;
export type IframeActionType =
| "create"
| "get"
| "ping"
| "preventSilentAccess";

/**
* If present, when the abort controller should be aborted
Expand All @@ -53,8 +63,8 @@ export interface EventData {
*/
options?: object;
/**
* If the API needs to blessed before the action is performed.
*/
* If the API needs to blessed before the action is performed.
*/
needsUserActivation?: boolean;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<script src="/permissions-policy/resources/permissions-policy.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body></body>
<script>
"use strict";
<script type="module">
import { makeGetOptions } from "./support/helper.js";
const { HTTPS_REMOTE_ORIGIN } = get_host_info();
const same_origin_src =
"/permissions-policy/resources/digital-credentials-get.html";
Expand All @@ -19,7 +19,7 @@
await promise_rejects_js(
test,
TypeError,
navigator.identity.get({ digital: { providers: [] } })
navigator.identity.get(makeGetOptions([]))
);

await test_feature_availability({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<script src="/permissions-policy/resources/permissions-policy.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body></body>
<script>
"use strict";
<script type="module">
import { makeGetOptions } from "/digital-credentials/support/helper.js";
const { HTTPS_REMOTE_ORIGIN } = get_host_info();
const same_origin_src =
"/permissions-policy/resources/digital-credentials-get.html";
Expand All @@ -19,7 +19,7 @@
await promise_rejects_dom(
test,
"NotAllowedError",
navigator.identity.get({ digital: { providers: [] } })
navigator.identity.get(makeGetOptions([]))
);
}, "Permissions-Policy header digital-credentials-get=() disallows the top-level document.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<script src="/permissions-policy/resources/permissions-policy.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body></body>
<script>
"use strict";
<script type="module">
import { makeGetOptions } from "./support/helper.js";
const { HTTPS_REMOTE_ORIGIN } = get_host_info();
const same_origin_src =
"/permissions-policy/resources/digital-credentials-get.html";
Expand All @@ -19,7 +19,7 @@
await promise_rejects_js(
test,
TypeError,
navigator.identity.get({ digital: { providers: [] } })
navigator.identity.get(makeGetOptions([]))
);
}, "Permissions-Policy header digital-credentials-get=(self) allows the top-level document.");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ PASS navigator.identity.get() promise is rejected if called with an aborted sign
PASS navigator.identity.get() promise is rejected if called with an aborted signal in cross-origin iframe.
FAIL navigator.identity.get() promise is rejected if abort controller is aborted after call to get(). promise_rejects_dom: function "function() { throw e }" threw object "NotSupportedError: Not implemented" that is not a DOMException AbortError: property "code" is equal to 9, expected 20
FAIL navigator.identity.get() promise is rejected if abort controller is aborted after call to get() in cross-origin iframe. assert_equals: expected "AbortError" but got "NotAllowedError"
PASS Mediation is required to get a DigitalCredential.

Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,13 @@
assert_equals(result.constructor, "DOMException");
assert_equals(result.name, "AbortError");
}, "navigator.identity.get() promise is rejected if abort controller is aborted after call to get() in cross-origin iframe.");

promise_test(async (t) => {
/** @type sequence<CredentialMediationRequirement> */
const disallowedMediations = [ "conditional", "optional", "silent"];
for (const mediation of disallowedMediations) {
const options = makeGetOptions("default", mediation);
await promise_rejects_js(t, TypeError, navigator.identity.get(options));
}
}, "Mediation is required to get a DigitalCredential.");
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@
*/
/**
* @param {ProviderType | ProviderType[]} [providersToUse=["default"]]
* @param {CredentialMediationRequirement} [mediation="required"]
* @returns {CredentialRequestOptions}
*/
export function makeGetOptions(providersToUse = ["default"]) {
export function makeGetOptions(providersToUse = ["default"], mediation = "required") {
if (typeof providersToUse === "string") {
if (providersToUse === "default" || providersToUse === "openid4vp"){
return makeGetOptions([providersToUse]);
}
}
if (!Array.isArray(providersToUse) || !providersToUse?.length) {
return { digital: { providers: providersToUse } };
return { digital: { providers: providersToUse }, mediation };
}
const providers = [];
for (const provider of providersToUse) {
Expand All @@ -31,10 +32,9 @@ export function makeGetOptions(providersToUse = ["default"]) {
break;
default:
throw new Error(`Unknown provider type: ${provider}`);
break;
}
}
return { digital: { providers } };
return { digital: { providers }, mediation };
}
/**
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
<meta charset="utf-8" />
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<body></body>
<script>
<script type="module">
import { makeGetOptions } from "/digital-credentials/support/helper.js";
const type = "availability-result";
async function notify() {
if (!navigator.userActivation.isActive) {
await test_driver.bless("user activation", null, window);
}
let enabled = undefined;
try {
await navigator.identity.get({ digital: { providers: [] } });
await navigator.identity.get(makeGetOptions([]));
} catch (e) {
switch (e.name) {
case "NotAllowedError":
Expand All @@ -27,7 +27,8 @@
window.parent.postMessage({ type, enabled }, "*");
}
}
window.onload = notify;
</script>
<body onload="notify()">
<body>
<h1>Digital Credentials iframe</h1>
</body>
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ PASS navigator.identity.get() promise is rejected if called with an aborted sign
PASS navigator.identity.get() promise is rejected if called with an aborted signal in cross-origin iframe.
FAIL navigator.identity.get() promise is rejected if abort controller is aborted after call to get(). promise_rejects_dom: function "function() { throw e }" threw object "NotSupportedError: Not implemented" that is not a DOMException AbortError: property "code" is equal to 9, expected 20
FAIL navigator.identity.get() promise is rejected if abort controller is aborted after call to get() in cross-origin iframe. assert_equals: expected "AbortError" but got "NotAllowedError"
PASS Mediation is required to get a DigitalCredential.

Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "JSDOMPromiseDeferred.h"
#include "JSDigitalCredential.h"
#include "LocalDOMWindow.h"
#include "MediationRequirement.h"
#include "Page.h"
#include "VisibilityState.h"

Expand All @@ -56,6 +57,11 @@ void IdentityCredentialsContainer::get(CredentialRequestOptions&& options, Crede
return;
}

if (options.mediation != MediationRequirement::Required) {
promise.reject(Exception { ExceptionCode::TypeError, "User mediation is required for DigitalCredential."_s });
return;
}

RefPtr document = this->document();
ASSERT(document);

Expand Down Expand Up @@ -85,9 +91,6 @@ void IdentityCredentialsContainer::get(CredentialRequestOptions&& options, Crede
return;
}

// FIXME: <https://webkit.org/b/277322> mediation requirement,
// which is waiting on https://github.com/WICG/digital-credentials/pull/149

document->page()->credentialRequestCoordinator().discoverFromExternalSource(*document, WTFMove(options), WTFMove(promise));
}

Expand Down

0 comments on commit 58fcbbf

Please sign in to comment.