Improve Equivalence Tests
This commit adds equals and hashCode implementations as well as a readResolve implementation to ensure that deserialization mechanisms can correctly assess the equality of a constnat and a corresponding deserialized instance. For defense-in-depth reasons, this commit also favors .equals over == for these constants. Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
This commit is contained in:
+22
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.web.webauthn.api;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -121,4 +122,25 @@ public final class AuthenticatorTransport implements Serializable {
|
||||
return new AuthenticatorTransport[] { USB, NFC, BLE, HYBRID, INTERNAL };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof AuthenticatorTransport other)) {
|
||||
return false;
|
||||
}
|
||||
return this.value.equals(other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.value.hashCode();
|
||||
}
|
||||
|
||||
@Serial
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
return valueOf(this.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.web.webauthn.api;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@@ -58,4 +59,25 @@ public final class PublicKeyCredentialType implements Serializable {
|
||||
return new PublicKeyCredentialType(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof PublicKeyCredentialType other)) {
|
||||
return false;
|
||||
}
|
||||
return this.value.equals(other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.value.hashCode();
|
||||
}
|
||||
|
||||
@Serial
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
return valueOf(this.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+33
@@ -16,9 +16,12 @@
|
||||
|
||||
package org.springframework.security.web.webauthn.api;
|
||||
|
||||
import java.io.ObjectStreamException;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* <a href=
|
||||
* "https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement">UserVerificationRequirement</a>
|
||||
@@ -72,4 +75,34 @@ public final class UserVerificationRequirement implements Serializable {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof UserVerificationRequirement other)) {
|
||||
return false;
|
||||
}
|
||||
return this.value.equals(other.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.value.hashCode();
|
||||
}
|
||||
|
||||
@Serial
|
||||
private Object readResolve() throws ObjectStreamException {
|
||||
switch (this.value) {
|
||||
case "required":
|
||||
return REQUIRED;
|
||||
case "preferred":
|
||||
return PREFERRED;
|
||||
case "discouraged":
|
||||
return DISCOURAGED;
|
||||
default:
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-6
@@ -257,8 +257,8 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
||||
byte[] clientDataJSON = response.getClientDataJSON().getBytes();
|
||||
Challenge challenge = new DefaultChallenge(base64Challenge);
|
||||
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);
|
||||
boolean userVerificationRequired = creationOptions.getAuthenticatorSelection()
|
||||
.getUserVerification() == UserVerificationRequirement.REQUIRED;
|
||||
boolean userVerificationRequired = UserVerificationRequirement.REQUIRED
|
||||
.equals(creationOptions.getAuthenticatorSelection().getUserVerification());
|
||||
// requireUserPresence The constant Boolean value true
|
||||
// https://www.w3.org/TR/webauthn-3/#sctn-op-make-cred
|
||||
boolean userPresenceRequired = true;
|
||||
@@ -317,8 +317,7 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
||||
|
||||
private com.webauthn4j.data.PublicKeyCredentialParameters convertParamToWebauthn4j(
|
||||
PublicKeyCredentialParameters parameter) {
|
||||
PublicKeyCredentialType credentialType = PublicKeyCredentialType.valueOf(parameter.getType().getValue());
|
||||
if (credentialType != PublicKeyCredentialType.PUBLIC_KEY) {
|
||||
if (!PublicKeyCredentialType.PUBLIC_KEY.equals(parameter.getType())) {
|
||||
throw new IllegalArgumentException(
|
||||
"Cannot convert unknown credential type " + parameter.getType() + " to webauthn4j");
|
||||
}
|
||||
@@ -394,8 +393,8 @@ public class Webauthn4JRelyingPartyOperations implements WebAuthnRelyingPartyOpe
|
||||
String rpId = requestOptions.getRpId();
|
||||
Assert.notNull(rpId, "rpId cannot be null");
|
||||
ServerProperty serverProperty = new ServerProperty(origins, rpId, challenge);
|
||||
boolean userVerificationRequired = request.getRequestOptions()
|
||||
.getUserVerification() == UserVerificationRequirement.REQUIRED;
|
||||
boolean userVerificationRequired = UserVerificationRequirement.REQUIRED
|
||||
.equals(request.getRequestOptions().getUserVerification());
|
||||
|
||||
com.webauthn4j.data.AuthenticationRequest authenticationRequest = new com.webauthn4j.data.AuthenticationRequest(
|
||||
request.getPublicKey().getRawId().getBytes(), assertionResponse.getAuthenticatorData().getBytes(),
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Verifies that {@link AuthenticatorAttachment} correctly restores singleton identity
|
||||
* after Java deserialization. This class already implements {@code readResolve()} and
|
||||
* serves as the reference for the pattern required by the other pseudo-enum types.
|
||||
*/
|
||||
class AuthenticatorAttachmentTests {
|
||||
|
||||
@Test
|
||||
void platformWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(AuthenticatorAttachment.PLATFORM))
|
||||
.isSameAs(AuthenticatorAttachment.PLATFORM);
|
||||
}
|
||||
|
||||
@Test
|
||||
void crossPlatformWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(AuthenticatorAttachment.CROSS_PLATFORM))
|
||||
.isSameAs(AuthenticatorAttachment.CROSS_PLATFORM);
|
||||
}
|
||||
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AuthenticatorTransportTests {
|
||||
|
||||
@Test
|
||||
void usbWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(AuthenticatorTransport.USB))
|
||||
.isSameAs(AuthenticatorTransport.USB);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nfcWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(AuthenticatorTransport.NFC))
|
||||
.isSameAs(AuthenticatorTransport.NFC);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bleWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(AuthenticatorTransport.BLE))
|
||||
.isSameAs(AuthenticatorTransport.BLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void smartCardWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(AuthenticatorTransport.SMART_CARD))
|
||||
.isSameAs(AuthenticatorTransport.SMART_CARD);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hybridWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(AuthenticatorTransport.HYBRID))
|
||||
.isSameAs(AuthenticatorTransport.HYBRID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(AuthenticatorTransport.INTERNAL))
|
||||
.isSameAs(AuthenticatorTransport.INTERNAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void usbWhenSameValueThenEquals() {
|
||||
assertThat(new AuthenticatorTransport("usb")).isEqualTo(AuthenticatorTransport.USB);
|
||||
}
|
||||
|
||||
@Test
|
||||
void usbWhenSameValueThenHashCodeMatches() {
|
||||
assertThat(new AuthenticatorTransport("usb")).hasSameHashCodeAs(AuthenticatorTransport.USB);
|
||||
}
|
||||
|
||||
@Test
|
||||
void usbWhenDifferentValueThenNotEquals() {
|
||||
assertThat(AuthenticatorTransport.USB).isNotEqualTo(AuthenticatorTransport.NFC);
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PublicKeyCredentialDescriptorTests {
|
||||
|
||||
@Test
|
||||
void typeWhenDeserializedThenSameAsConstant() {
|
||||
PublicKeyCredentialDescriptor descriptor = PublicKeyCredentialDescriptor.builder()
|
||||
.id(TestBytes.get())
|
||||
.type(PublicKeyCredentialType.PUBLIC_KEY)
|
||||
.build();
|
||||
PublicKeyCredentialDescriptor deserialized = SerializationTestUtils.serializeAndDeserialize(descriptor);
|
||||
assertThat(deserialized.getType()).isSameAs(PublicKeyCredentialType.PUBLIC_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void transportsWhenDeserializedThenSameAsConstants() {
|
||||
PublicKeyCredentialDescriptor descriptor = PublicKeyCredentialDescriptor.builder()
|
||||
.id(TestBytes.get())
|
||||
.type(PublicKeyCredentialType.PUBLIC_KEY)
|
||||
.transports(Set.of(AuthenticatorTransport.USB, AuthenticatorTransport.HYBRID))
|
||||
.build();
|
||||
PublicKeyCredentialDescriptor deserialized = SerializationTestUtils.serializeAndDeserialize(descriptor);
|
||||
assertThat(deserialized.getTransports()).containsExactlyInAnyOrder(AuthenticatorTransport.USB,
|
||||
AuthenticatorTransport.HYBRID);
|
||||
}
|
||||
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PublicKeyCredentialRequestOptionsTests {
|
||||
|
||||
@Test
|
||||
void userVerificationRequiredWhenDeserializedThenSameAsConstant() {
|
||||
PublicKeyCredentialRequestOptions options = TestPublicKeyCredentialRequestOptions.create()
|
||||
.userVerification(UserVerificationRequirement.REQUIRED)
|
||||
.build();
|
||||
PublicKeyCredentialRequestOptions deserialized = SerializationTestUtils.serializeAndDeserialize(options);
|
||||
assertThat(deserialized.getUserVerification()).isSameAs(UserVerificationRequirement.REQUIRED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void userVerificationPreferredWhenDeserializedThenSameAsConstant() {
|
||||
PublicKeyCredentialRequestOptions options = TestPublicKeyCredentialRequestOptions.create()
|
||||
.userVerification(UserVerificationRequirement.PREFERRED)
|
||||
.build();
|
||||
PublicKeyCredentialRequestOptions deserialized = SerializationTestUtils.serializeAndDeserialize(options);
|
||||
assertThat(deserialized.getUserVerification()).isSameAs(UserVerificationRequirement.PREFERRED);
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PublicKeyCredentialTypeTests {
|
||||
|
||||
@Test
|
||||
void publicKeyWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(PublicKeyCredentialType.PUBLIC_KEY))
|
||||
.isSameAs(PublicKeyCredentialType.PUBLIC_KEY);
|
||||
}
|
||||
|
||||
@Test
|
||||
void publicKeyWhenSameValueThenEquals() {
|
||||
PublicKeyCredentialType first = PublicKeyCredentialType.valueOf("custom");
|
||||
PublicKeyCredentialType second = PublicKeyCredentialType.valueOf("custom");
|
||||
assertThat(first).isEqualTo(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void publicKeyWhenSameValueThenHashCodeMatches() {
|
||||
PublicKeyCredentialType first = PublicKeyCredentialType.valueOf("custom");
|
||||
PublicKeyCredentialType second = PublicKeyCredentialType.valueOf("custom");
|
||||
assertThat(first).hasSameHashCodeAs(second);
|
||||
}
|
||||
|
||||
@Test
|
||||
void publicKeyWhenDifferentValueThenNotEquals() {
|
||||
assertThat(PublicKeyCredentialType.valueOf("custom-1"))
|
||||
.isNotEqualTo(PublicKeyCredentialType.valueOf("custom-2"));
|
||||
}
|
||||
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
public final class SerializationTestUtils {
|
||||
|
||||
private SerializationTestUtils() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Serializable> T serializeAndDeserialize(T object) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
|
||||
oos.writeObject(object);
|
||||
}
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
||||
try (ObjectInputStream ois = new ObjectInputStream(bais)) {
|
||||
return (T) ois.readObject();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new RuntimeException("Serialization round-trip failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.api;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class UserVerificationRequirementTests {
|
||||
|
||||
@Test
|
||||
void requiredWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(UserVerificationRequirement.REQUIRED))
|
||||
.isSameAs(UserVerificationRequirement.REQUIRED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preferredWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(UserVerificationRequirement.PREFERRED))
|
||||
.isSameAs(UserVerificationRequirement.PREFERRED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void discouragedWhenDeserializedThenSameAsConstant() {
|
||||
assertThat(SerializationTestUtils.serializeAndDeserialize(UserVerificationRequirement.DISCOURAGED))
|
||||
.isSameAs(UserVerificationRequirement.DISCOURAGED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requiredWhenSameValueThenEquals() {
|
||||
assertThat(new UserVerificationRequirement("required")).isEqualTo(UserVerificationRequirement.REQUIRED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requiredWhenSameValueThenHashCodeMatches() {
|
||||
assertThat(new UserVerificationRequirement("required")).hasSameHashCodeAs(UserVerificationRequirement.REQUIRED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void requiredWhenDifferentValueThenNotEquals() {
|
||||
assertThat(UserVerificationRequirement.REQUIRED).isNotEqualTo(UserVerificationRequirement.PREFERRED);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user