|  | @@ -0,0 +1,363 @@
 | 
	
		
			
				|  |  | +/*
 | 
	
		
			
				|  |  | + * Copyright 2004-present the original author or authors.
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * Licensed under the Apache License, Version 2.0 (the "License");
 | 
	
		
			
				|  |  | + * you may not use this file except in compliance with the License.
 | 
	
		
			
				|  |  | + * You may obtain a copy of the License at
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + *      https://www.apache.org/licenses/LICENSE-2.0
 | 
	
		
			
				|  |  | + *
 | 
	
		
			
				|  |  | + * Unless required by applicable law or agreed to in writing, software
 | 
	
		
			
				|  |  | + * distributed under the License is distributed on an "AS IS" BASIS,
 | 
	
		
			
				|  |  | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
	
		
			
				|  |  | + * See the License for the specific language governing permissions and
 | 
	
		
			
				|  |  | + * limitations under the License.
 | 
	
		
			
				|  |  | + */
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +package org.springframework.security.web.webauthn.jackson;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import java.time.Duration;
 | 
	
		
			
				|  |  | +import java.util.Arrays;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import com.fasterxml.jackson.core.type.TypeReference;
 | 
	
		
			
				|  |  | +import com.fasterxml.jackson.databind.ObjectMapper;
 | 
	
		
			
				|  |  | +import org.junit.jupiter.api.BeforeEach;
 | 
	
		
			
				|  |  | +import org.junit.jupiter.api.Test;
 | 
	
		
			
				|  |  | +import org.skyscreamer.jsonassert.JSONAssert;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientOutputs;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.AuthenticatorAttestationResponse;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.Bytes;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientOutputs;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.PublicKeyCredential;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions;
 | 
	
		
			
				|  |  | +import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import static org.assertj.core.api.Assertions.assertThat;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class Jackson2Tests {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	private ObjectMapper mapper;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@BeforeEach
 | 
	
		
			
				|  |  | +	void setup() {
 | 
	
		
			
				|  |  | +		this.mapper = new ObjectMapper();
 | 
	
		
			
				|  |  | +		this.mapper.registerModule(new WebauthnJackson2Module());
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void readAuthenticatorTransport() throws Exception {
 | 
	
		
			
				|  |  | +		AuthenticatorTransport transport = this.mapper.readValue("\"hybrid\"", AuthenticatorTransport.class);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		assertThat(transport).isEqualTo(AuthenticatorTransport.HYBRID);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void readAuthenticatorAttachment() throws Exception {
 | 
	
		
			
				|  |  | +		AuthenticatorAttachment value = this.mapper.readValue("\"cross-platform\"", AuthenticatorAttachment.class);
 | 
	
		
			
				|  |  | +		assertThat(value).isEqualTo(AuthenticatorAttachment.CROSS_PLATFORM);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void writeAuthenticatorAttachment() throws Exception {
 | 
	
		
			
				|  |  | +		String value = this.mapper.writeValueAsString(AuthenticatorAttachment.CROSS_PLATFORM);
 | 
	
		
			
				|  |  | +		assertThat(value).isEqualTo("\"cross-platform\"");
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void readAuthenticationExtensionsClientOutputs() throws Exception {
 | 
	
		
			
				|  |  | +		String json = """
 | 
	
		
			
				|  |  | +				{
 | 
	
		
			
				|  |  | +					"credProps": {
 | 
	
		
			
				|  |  | +						"rk": false
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				""";
 | 
	
		
			
				|  |  | +		ImmutableAuthenticationExtensionsClientOutputs clientExtensionResults = new ImmutableAuthenticationExtensionsClientOutputs(
 | 
	
		
			
				|  |  | +				new CredentialPropertiesOutput(false));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json,
 | 
	
		
			
				|  |  | +				AuthenticationExtensionsClientOutputs.class);
 | 
	
		
			
				|  |  | +		assertThat(outputs).usingRecursiveComparison().isEqualTo(clientExtensionResults);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void readAuthenticationExtensionsClientOutputsWhenAuthenticatorDisplayName() throws Exception {
 | 
	
		
			
				|  |  | +		String json = """
 | 
	
		
			
				|  |  | +				{
 | 
	
		
			
				|  |  | +					"credProps": {
 | 
	
		
			
				|  |  | +						"rk": false,
 | 
	
		
			
				|  |  | +						"authenticatorDisplayName": "1Password"
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				""";
 | 
	
		
			
				|  |  | +		ImmutableAuthenticationExtensionsClientOutputs clientExtensionResults = new ImmutableAuthenticationExtensionsClientOutputs(
 | 
	
		
			
				|  |  | +				new CredentialPropertiesOutput(false));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		AuthenticationExtensionsClientOutputs outputs = this.mapper.readValue(json,
 | 
	
		
			
				|  |  | +				AuthenticationExtensionsClientOutputs.class);
 | 
	
		
			
				|  |  | +		assertThat(outputs).usingRecursiveComparison().isEqualTo(clientExtensionResults);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void readCredPropsWhenAuthenticatorDisplayName() throws Exception {
 | 
	
		
			
				|  |  | +		String json = """
 | 
	
		
			
				|  |  | +				{
 | 
	
		
			
				|  |  | +					"rk": false,
 | 
	
		
			
				|  |  | +					"authenticatorDisplayName": "1Password"
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				""";
 | 
	
		
			
				|  |  | +		CredentialPropertiesOutput credProps = new CredentialPropertiesOutput(false);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		CredentialPropertiesOutput outputs = this.mapper.readValue(json, CredentialPropertiesOutput.class);
 | 
	
		
			
				|  |  | +		assertThat(outputs).usingRecursiveComparison().isEqualTo(credProps);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void readAuthenticationExtensionsClientOutputsWhenFieldAfter() throws Exception {
 | 
	
		
			
				|  |  | +		String json = """
 | 
	
		
			
				|  |  | +				{
 | 
	
		
			
				|  |  | +					"clientOutputs": {
 | 
	
		
			
				|  |  | +						"credProps": {
 | 
	
		
			
				|  |  | +							"rk": false
 | 
	
		
			
				|  |  | +						}
 | 
	
		
			
				|  |  | +					},
 | 
	
		
			
				|  |  | +					"label": "Cell Phone"
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				""";
 | 
	
		
			
				|  |  | +		ImmutableAuthenticationExtensionsClientOutputs clientExtensionResults = new ImmutableAuthenticationExtensionsClientOutputs(
 | 
	
		
			
				|  |  | +				new CredentialPropertiesOutput(false));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		ClassWithOutputsAndAnotherField expected = new ClassWithOutputsAndAnotherField();
 | 
	
		
			
				|  |  | +		expected.setClientOutputs(clientExtensionResults);
 | 
	
		
			
				|  |  | +		expected.setLabel("Cell Phone");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		ClassWithOutputsAndAnotherField actual = this.mapper.readValue(json, ClassWithOutputsAndAnotherField.class);
 | 
	
		
			
				|  |  | +		assertThat(actual).usingRecursiveComparison().isEqualTo(expected);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void writePublicKeyCredentialCreationOptions() throws Exception {
 | 
	
		
			
				|  |  | +		String expected = """
 | 
	
		
			
				|  |  | +				{
 | 
	
		
			
				|  |  | +				    "attestation": "none",
 | 
	
		
			
				|  |  | +				    "authenticatorSelection": {
 | 
	
		
			
				|  |  | +				        "residentKey": "required"
 | 
	
		
			
				|  |  | +				    },
 | 
	
		
			
				|  |  | +				    "challenge": "q7lCdd3SVQxdC-v8pnRAGEn1B2M-t7ZECWPwCAmhWvc",
 | 
	
		
			
				|  |  | +				    "excludeCredentials": [],
 | 
	
		
			
				|  |  | +				    "extensions": {
 | 
	
		
			
				|  |  | +				        "credProps": true
 | 
	
		
			
				|  |  | +				    },
 | 
	
		
			
				|  |  | +				    "pubKeyCredParams": [
 | 
	
		
			
				|  |  | +				        {
 | 
	
		
			
				|  |  | +				            "alg": -7,
 | 
	
		
			
				|  |  | +				            "type": "public-key"
 | 
	
		
			
				|  |  | +				        },{
 | 
	
		
			
				|  |  | +				            "alg": -8,
 | 
	
		
			
				|  |  | +				            "type": "public-key"
 | 
	
		
			
				|  |  | +				        },
 | 
	
		
			
				|  |  | +				        {
 | 
	
		
			
				|  |  | +				            "alg": -257,
 | 
	
		
			
				|  |  | +				            "type": "public-key"
 | 
	
		
			
				|  |  | +				        }
 | 
	
		
			
				|  |  | +				    ],
 | 
	
		
			
				|  |  | +				    "rp": {
 | 
	
		
			
				|  |  | +				        "id": "example.localhost",
 | 
	
		
			
				|  |  | +				        "name": "SimpleWebAuthn Example"
 | 
	
		
			
				|  |  | +				    },
 | 
	
		
			
				|  |  | +				    "timeout": 300000,
 | 
	
		
			
				|  |  | +				    "user": {
 | 
	
		
			
				|  |  | +				        "displayName": "user@example.localhost",
 | 
	
		
			
				|  |  | +				        "id": "oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w",
 | 
	
		
			
				|  |  | +				        "name": "user@example.localhost"
 | 
	
		
			
				|  |  | +				    }
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				""";
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions
 | 
	
		
			
				|  |  | +			.createPublicKeyCredentialCreationOptions()
 | 
	
		
			
				|  |  | +			.build();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		String string = this.mapper.writeValueAsString(options);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		JSONAssert.assertEquals(expected, string, false);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void readPublicKeyCredentialAuthenticatorAttestationResponse() throws Exception {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		PublicKeyCredential<AuthenticatorAttestationResponse> publicKeyCredential = this.mapper.readValue(
 | 
	
		
			
				|  |  | +				PublicKeyCredentialJson.PUBLIC_KEY_JSON,
 | 
	
		
			
				|  |  | +				new TypeReference<PublicKeyCredential<AuthenticatorAttestationResponse>>() {
 | 
	
		
			
				|  |  | +				});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		ImmutableAuthenticationExtensionsClientOutputs clientExtensionResults = new ImmutableAuthenticationExtensionsClientOutputs(
 | 
	
		
			
				|  |  | +				new CredentialPropertiesOutput(false));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		PublicKeyCredential<AuthenticatorAttestationResponse> expected = PublicKeyCredential.builder()
 | 
	
		
			
				|  |  | +			.id("AX6nVVERrH6opMafUGn3Z9EyNEy6cftfBKV_2YxYl1jdW8CSJxMKGXFV3bnrKTiMSJeInkG7C6B2lPt8E5i3KaM")
 | 
	
		
			
				|  |  | +			.rawId(Bytes
 | 
	
		
			
				|  |  | +				.fromBase64("AX6nVVERrH6opMafUGn3Z9EyNEy6cftfBKV_2YxYl1jdW8CSJxMKGXFV3bnrKTiMSJeInkG7C6B2lPt8E5i3KaM"))
 | 
	
		
			
				|  |  | +			.response(AuthenticatorAttestationResponse.builder()
 | 
	
		
			
				|  |  | +				.attestationObject(Bytes.fromBase64(
 | 
	
		
			
				|  |  | +						"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQF-p1VREax-qKTGn1Bp92fRMjRMunH7XwSlf9mMWJdY3VvAkicTChlxVd256yk4jEiXiJ5BuwugdpT7fBOYtymjpQECAyYgASFYIJK-2epPEw0ujHN-gvVp2Hp3ef8CzU3zqwO5ylx8L2OsIlggK5x5OlTGEPxLS-85TAABum4aqVK4CSWJ7LYDdkjuBLk"))
 | 
	
		
			
				|  |  | +				.clientDataJSON(Bytes.fromBase64(
 | 
	
		
			
				|  |  | +						"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiSUJRbnVZMVowSzFIcUJvRldDcDJ4bEpsOC1vcV9hRklYenlUX0YwLTBHVSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZX0"))
 | 
	
		
			
				|  |  | +				.transports(AuthenticatorTransport.HYBRID, AuthenticatorTransport.INTERNAL)
 | 
	
		
			
				|  |  | +				.build())
 | 
	
		
			
				|  |  | +			.type(PublicKeyCredentialType.PUBLIC_KEY)
 | 
	
		
			
				|  |  | +			.clientExtensionResults(clientExtensionResults)
 | 
	
		
			
				|  |  | +			.authenticatorAttachment(AuthenticatorAttachment.CROSS_PLATFORM)
 | 
	
		
			
				|  |  | +			.build();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		assertThat(publicKeyCredential).usingRecursiveComparison().isEqualTo(expected);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void readPublicKeyCredentialAuthenticatorAttestationResponseWhenExtraFields() throws Exception {
 | 
	
		
			
				|  |  | +		final String json = """
 | 
	
		
			
				|  |  | +				{
 | 
	
		
			
				|  |  | +					 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQF-p1VREax-qKTGn1Bp92fRMjRMunH7XwSlf9mMWJdY3VvAkicTChlxVd256yk4jEiXiJ5BuwugdpT7fBOYtymjpQECAyYgASFYIJK-2epPEw0ujHN-gvVp2Hp3ef8CzU3zqwO5ylx8L2OsIlggK5x5OlTGEPxLS-85TAABum4aqVK4CSWJ7LYDdkjuBLk",
 | 
	
		
			
				|  |  | +					 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiSUJRbnVZMVowSzFIcUJvRldDcDJ4bEpsOC1vcV9hRklYenlUX0YwLTBHVSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
 | 
	
		
			
				|  |  | +					 "transports": [
 | 
	
		
			
				|  |  | +					   "hybrid",
 | 
	
		
			
				|  |  | +					   "internal"
 | 
	
		
			
				|  |  | +					 ],
 | 
	
		
			
				|  |  | +					 "publicKeyAlgorithm": -7,
 | 
	
		
			
				|  |  | +					 "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkr7Z6k8TDS6Mc36C9WnYend5_wLNTfOrA7nKXHwvY6wrnHk6VMYQ_EtL7zlMAAG6bhqpUrgJJYnstgN2SO4EuQ",
 | 
	
		
			
				|  |  | +					 "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQF-p1VREax-qKTGn1Bp92fRMjRMunH7XwSlf9mMWJdY3VvAkicTChlxVd256yk4jEiXiJ5BuwugdpT7fBOYtymjpQECAyYgASFYIJK-2epPEw0ujHN-gvVp2Hp3ef8CzU3zqwO5ylx8L2OsIlggK5x5OlTGEPxLS-85TAABum4aqVK4CSWJ7LYDdkjuBLk"
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				""";
 | 
	
		
			
				|  |  | +		AuthenticatorAttestationResponse response = this.mapper.readValue(json, AuthenticatorAttestationResponse.class);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		ImmutableAuthenticationExtensionsClientOutputs clientExtensionResults = new ImmutableAuthenticationExtensionsClientOutputs(
 | 
	
		
			
				|  |  | +				new CredentialPropertiesOutput(false));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		AuthenticatorAttestationResponse expected = AuthenticatorAttestationResponse.builder()
 | 
	
		
			
				|  |  | +			.attestationObject(Bytes.fromBase64(
 | 
	
		
			
				|  |  | +					"o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjFSZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NFAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQF-p1VREax-qKTGn1Bp92fRMjRMunH7XwSlf9mMWJdY3VvAkicTChlxVd256yk4jEiXiJ5BuwugdpT7fBOYtymjpQECAyYgASFYIJK-2epPEw0ujHN-gvVp2Hp3ef8CzU3zqwO5ylx8L2OsIlggK5x5OlTGEPxLS-85TAABum4aqVK4CSWJ7LYDdkjuBLk"))
 | 
	
		
			
				|  |  | +			.clientDataJSON(Bytes.fromBase64(
 | 
	
		
			
				|  |  | +					"eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiSUJRbnVZMVowSzFIcUJvRldDcDJ4bEpsOC1vcV9hRklYenlUX0YwLTBHVSIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZX0"))
 | 
	
		
			
				|  |  | +			.transports(AuthenticatorTransport.HYBRID, AuthenticatorTransport.INTERNAL)
 | 
	
		
			
				|  |  | +			.build();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		assertThat(response).usingRecursiveComparison().isEqualTo(expected);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void writeAuthenticationOptions() throws Exception {
 | 
	
		
			
				|  |  | +		PublicKeyCredentialRequestOptions credentialRequestOptions = PublicKeyCredentialRequestOptions.builder()
 | 
	
		
			
				|  |  | +			.allowCredentials(Arrays.asList())
 | 
	
		
			
				|  |  | +			.challenge(Bytes.fromBase64("I69THX904Q8ONhCgUgOu2PCQCcEjTDiNmokdbgsAsYU"))
 | 
	
		
			
				|  |  | +			.rpId("example.localhost")
 | 
	
		
			
				|  |  | +			.timeout(Duration.ofMinutes(5))
 | 
	
		
			
				|  |  | +			.userVerification(UserVerificationRequirement.REQUIRED)
 | 
	
		
			
				|  |  | +			.build();
 | 
	
		
			
				|  |  | +		String actual = this.mapper.writeValueAsString(credentialRequestOptions);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		String expected = """
 | 
	
		
			
				|  |  | +						{
 | 
	
		
			
				|  |  | +				    "challenge": "I69THX904Q8ONhCgUgOu2PCQCcEjTDiNmokdbgsAsYU",
 | 
	
		
			
				|  |  | +				    "allowCredentials": [],
 | 
	
		
			
				|  |  | +				    "timeout": 300000,
 | 
	
		
			
				|  |  | +				    "userVerification": "required",
 | 
	
		
			
				|  |  | +				    "rpId": "example.localhost"
 | 
	
		
			
				|  |  | +				  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +				""";
 | 
	
		
			
				|  |  | +		JSONAssert.assertEquals(expected, actual, false);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void readPublicKeyCredentialAuthenticatorAssertionResponse() throws Exception {
 | 
	
		
			
				|  |  | +		String json = """
 | 
	
		
			
				|  |  | +					{
 | 
	
		
			
				|  |  | +					   "id": "IquGb208Fffq2cROa1ZxMg",
 | 
	
		
			
				|  |  | +					   "rawId": "IquGb208Fffq2cROa1ZxMg",
 | 
	
		
			
				|  |  | +					   "response": {
 | 
	
		
			
				|  |  | +						 "authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA",
 | 
	
		
			
				|  |  | +						 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiaDB2Z3dHUWpvQ3pBekRVc216UHBrLUpWSUpSUmduMEw0S1ZTWU5SY0VaYyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
 | 
	
		
			
				|  |  | +						 "signature": "MEUCIAdfzPAn3voyXynwa0IXk1S0envMY5KP3NEe9aj4B2BuAiEAm_KJhQoWXdvfhbzwACU3NM4ltQe7_Il46qFUwtpuTdg",
 | 
	
		
			
				|  |  | +						 "userHandle": "oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w"
 | 
	
		
			
				|  |  | +					   },
 | 
	
		
			
				|  |  | +					   "type": "public-key",
 | 
	
		
			
				|  |  | +					   "clientExtensionResults": {},
 | 
	
		
			
				|  |  | +					   "authenticatorAttachment": "cross-platform"
 | 
	
		
			
				|  |  | +					 }
 | 
	
		
			
				|  |  | +				""";
 | 
	
		
			
				|  |  | +		PublicKeyCredential<AuthenticatorAssertionResponse> publicKeyCredential = this.mapper.readValue(json,
 | 
	
		
			
				|  |  | +				new TypeReference<PublicKeyCredential<AuthenticatorAssertionResponse>>() {
 | 
	
		
			
				|  |  | +				});
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		ImmutableAuthenticationExtensionsClientOutputs clientExtensionResults = new ImmutableAuthenticationExtensionsClientOutputs();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		PublicKeyCredential<AuthenticatorAssertionResponse> expected = PublicKeyCredential.builder()
 | 
	
		
			
				|  |  | +			.id("IquGb208Fffq2cROa1ZxMg")
 | 
	
		
			
				|  |  | +			.rawId(Bytes.fromBase64("IquGb208Fffq2cROa1ZxMg"))
 | 
	
		
			
				|  |  | +			.response(AuthenticatorAssertionResponse.builder()
 | 
	
		
			
				|  |  | +				.authenticatorData(Bytes.fromBase64("SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA"))
 | 
	
		
			
				|  |  | +				.clientDataJSON(Bytes.fromBase64(
 | 
	
		
			
				|  |  | +						"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiaDB2Z3dHUWpvQ3pBekRVc216UHBrLUpWSUpSUmduMEw0S1ZTWU5SY0VaYyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCIsImNyb3NzT3JpZ2luIjpmYWxzZX0"))
 | 
	
		
			
				|  |  | +				.signature(Bytes.fromBase64(
 | 
	
		
			
				|  |  | +						"MEUCIAdfzPAn3voyXynwa0IXk1S0envMY5KP3NEe9aj4B2BuAiEAm_KJhQoWXdvfhbzwACU3NM4ltQe7_Il46qFUwtpuTdg"))
 | 
	
		
			
				|  |  | +				.userHandle(Bytes.fromBase64("oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w"))
 | 
	
		
			
				|  |  | +				.build())
 | 
	
		
			
				|  |  | +			.type(PublicKeyCredentialType.PUBLIC_KEY)
 | 
	
		
			
				|  |  | +			.clientExtensionResults(clientExtensionResults)
 | 
	
		
			
				|  |  | +			.authenticatorAttachment(AuthenticatorAttachment.CROSS_PLATFORM)
 | 
	
		
			
				|  |  | +			.build();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		assertThat(publicKeyCredential).usingRecursiveComparison().isEqualTo(expected);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	@Test
 | 
	
		
			
				|  |  | +	void writeAuthenticationExtensionsClientInputsWhenCredPropsTrue() throws Exception {
 | 
	
		
			
				|  |  | +		String expected = """
 | 
	
		
			
				|  |  | +					{
 | 
	
		
			
				|  |  | +						"credProps": true
 | 
	
		
			
				|  |  | +					}
 | 
	
		
			
				|  |  | +				""";
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		ImmutableAuthenticationExtensionsClientInputs clientInputs = new ImmutableAuthenticationExtensionsClientInputs(
 | 
	
		
			
				|  |  | +				ImmutableAuthenticationExtensionsClientInput.credProps);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		String actual = this.mapper.writeValueAsString(clientInputs);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		JSONAssert.assertEquals(expected, actual, false);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	public static class ClassWithOutputsAndAnotherField {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		private String label;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		private AuthenticationExtensionsClientOutputs clientOutputs;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		public String getLabel() {
 | 
	
		
			
				|  |  | +			return this.label;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		public void setLabel(String label) {
 | 
	
		
			
				|  |  | +			this.label = label;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		public AuthenticationExtensionsClientOutputs getClientOutputs() {
 | 
	
		
			
				|  |  | +			return this.clientOutputs;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		public void setClientOutputs(AuthenticationExtensionsClientOutputs clientOutputs) {
 | 
	
		
			
				|  |  | +			this.clientOutputs = clientOutputs;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 |