diff options
Diffstat (limited to 'testing/web-platform/tests/webauthn/public-key-credential-to-json.https.window.js')
-rw-r--r-- | testing/web-platform/tests/webauthn/public-key-credential-to-json.https.window.js | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/testing/web-platform/tests/webauthn/public-key-credential-to-json.https.window.js b/testing/web-platform/tests/webauthn/public-key-credential-to-json.https.window.js new file mode 100644 index 0000000000..8de3b8c3cd --- /dev/null +++ b/testing/web-platform/tests/webauthn/public-key-credential-to-json.https.window.js @@ -0,0 +1,157 @@ +// META: script=/resources/testharness.js +// META: script=/resources/testharnessreport.js +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js +// META: script=/resources/utils.js +// META: script=helpers.js + +function assertObjectKeysEq(a, b) { + let a_keys = new Set(Object.keys(a)); + let b_keys = new Set(Object.keys(b)); + assert_true( + a_keys.length == b_keys.length && [...a_keys].every(k => b_keys.has(k)), + `keys differ: ${a_keys} != ${b_keys}`); +} + +// Returns the JSON encoding for `value`. If `value` is a function, `optParent` +// is the object to which execution should be bound. +function convertValue(value, optParent) { + switch (typeof value) { + case 'undefined': + case 'boolean': + case 'number': + case 'bigint': + case 'string': + case 'symbol': + return value; + case 'function': + return value.apply(optParent); + case 'object': + if (value.__proto__.constructor === Object) { + var result = {}; + Object.entries(value).map((k, v) => { + result[k] = convertValue(k, v); + }); + return result; + } + if (value instanceof Array) { + return value.map(convertValue); + } + if (value instanceof ArrayBuffer) { + return base64urlEncode(new Uint8Array(value)); + } + throw `can't convert value ${value} in ${parent}`; + default: + throw `${value} has unexpected type`; + } +} + +// Conversion spec for a single attribute. +// @typedef {Object} ConvertParam +// @property {string} name - The name of the attribute to convert from +// @property {string=} target - The name of the attribute to convert to, if +// different from `name` +// @property {function=} func - Method to convert this property. Defaults to +// convertValue(). + +// Returns the JSON object for `obj`. +// +// @param obj +// @param {Array<(string|ConvertParam)>} keys - The names of parameters in +// `obj` to convert, or instances of ConvertParam for complex cases. +function convertObject(obj, params) { + let result = {}; + params.forEach((param) => { + switch (typeof (param)) { + case 'string': + assert_true(param in obj, `missing ${param}`); + if (obj[param] !== null) { + result[param] = convertValue(obj[param], obj); + } + break; + case 'object': + assert_true(param.name in obj, `missing ${param.name}`); + const val = obj[param.name]; + const target_key = param.target || param.name; + const convert_func = param.func || convertValue; + try { + result[target_key] = + convert_func(((typeof val) == 'function' ? val.apply(obj) : val)); + } catch (e) { + throw `failed to convert ${param.name}: ${e}` + } + break; + default: + throw `invalid key ${param}`; + } + }); + return result; +} + +// Converts an AuthenticatorResponse instance into a JSON object. +// @param {!AuthenticatorResponse} +function authenticatorResponseToJson(response) { + assert_true( + (response instanceof AuthenticatorAttestationResponse) || + (response instanceof AuthenticatorAssertionResponse)); + const isAttestation = (response instanceof AuthenticatorAttestationResponse); + const keys = + (isAttestation ? + [ + 'clientDataJSON', 'attestationObject', + {name: 'getTransports', target: 'transports'} + ] : + ['clientDataJSON', 'authenticatorData', 'signature', 'userHandle']); + return convertObject(response, keys); +} + +// Converts a PublicKeyCredential instance to a JSON object. +// @param {!PublicKeyCredential} +function publicKeyCredentialToJson(cred) { + const keys = [ + 'id', 'rawId', {name: 'response', func: authenticatorResponseToJson}, + 'authenticatorAttachment', + {name: 'getClientExtensionResults', target: 'clientExtensionResults'}, + 'type' + ]; + return convertObject(cred, keys); +} + +// Returns a copy of `jsonObj`, which must be a JSON type, with object keys +// recursively sorted in lexicographic order; or simply `jsonObj` if it is not +// an instance of Object. +function deepSortKeys(jsonObj) { + if (typeof jsonObj !== 'object' || jsonObj === null || + jsonObj.__proto__.constructor !== Object || + Object.keys(jsonObj).length === 0) { + return jsonObj; + } + return Object.keys(jsonObj).sort().reduce((acc, key) => { + acc[key] = deepSortKeys(jsonObj[key]); + return acc; + }, {}); +} + +// Asserts that `actual` and `expected`, which are both JSON types, are equal. +// The object key order is ignored for comparison. +function assertJsonEquals(actual, expected, optMsg) { + assert_equals( + JSON.stringify(deepSortKeys(actual)), + JSON.stringify(deepSortKeys(expected)), optMsg); +} + +virtualAuthenticatorPromiseTest( + async t => { + let credential = await createCredential(); + assertJsonEquals( + credential.toJSON(), publicKeyCredentialToJson(credential)); + + let assertion = await assertCredential(credential); + assertJsonEquals( + assertion.toJSON(), publicKeyCredentialToJson(assertion)); + }, + { + protocol: 'ctap2_1', + transport: 'usb', + }, + 'toJSON()'); |