From 2dff8930b7770eada7a6602cf138d07093d92efc Mon Sep 17 00:00:00 2001 From: Stefan Kalscheuer Date: Sun, 3 Dec 2023 11:27:13 +0100 Subject: [PATCH] model: add MFA requirement data to auth response (#71) --- CHANGELOG.md | 1 + .../model/response/embedded/AuthData.java | 16 +++- .../response/embedded/MfaConstraintAny.java | 62 ++++++++++++ .../model/response/embedded/MfaMethodId.java | 94 +++++++++++++++++++ .../response/embedded/MfaRequirement.java | 73 ++++++++++++++ .../model/response/AuthResponseTest.java | 83 +++++++++++----- 6 files changed, 306 insertions(+), 23 deletions(-) create mode 100644 src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaConstraintAny.java create mode 100644 src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaMethodId.java create mode 100644 src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaRequirement.java diff --git a/CHANGELOG.md b/CHANGELOG.md index e4c5983..855f165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Parse timestamps as `ZonedDateTime` instead of `String` representation * Remove redundant `java.base` requirement from _module-info.java_ (#69) * Close Java HTTP Client when running on Java 21 or later (#70) +* Add MFA requirements tu `AuthResponse` (#71) ### Dependencies * Updated Jackson to 2.16.0 diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthData.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthData.java index 5d4fc91..2c8164e 100644 --- a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthData.java +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/AuthData.java @@ -65,6 +65,9 @@ public final class AuthData implements Serializable { @JsonProperty("orphan") private boolean orphan; + @JsonProperty("mfa_requirement") + private MfaRequirement mfaRequirement; + /** * @return Client token */ @@ -139,6 +142,14 @@ public boolean isOrphan() { return orphan; } + /** + * @return multi-factor requirement + * @since 1.2 + */ + public MfaRequirement getMfaRequirement() { + return mfaRequirement; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -157,12 +168,13 @@ public boolean equals(Object o) { Objects.equals(metadata, authData.metadata) && Objects.equals(leaseDuration, authData.leaseDuration) && Objects.equals(entityId, authData.entityId) && - Objects.equals(tokenType, authData.tokenType); + Objects.equals(tokenType, authData.tokenType) && + Objects.equals(mfaRequirement, authData.mfaRequirement); } @Override public int hashCode() { return Objects.hash(clientToken, accessor, policies, tokenPolicies, metadata, leaseDuration, renewable, - entityId, tokenType, orphan); + entityId, tokenType, orphan, mfaRequirement); } } diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaConstraintAny.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaConstraintAny.java new file mode 100644 index 0000000..9583cec --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaConstraintAny.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016-2023 Stefan Kalscheuer + * + * 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 + * + * http://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 de.stklcode.jvault.connector.model.response.embedded; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; + +/** + * Embedded multi-factor-authentication (MFA) constraint "any". + * + * @author Stefan Kalscheuer + * @since 1.2 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class MfaConstraintAny implements Serializable { + private static final long serialVersionUID = 1226126781813149627L; + + @JsonProperty("any") + private List any; + + /** + * @return List of "any" MFA methods + */ + public List getAny() { + return any; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MfaConstraintAny mfaRequirement = (MfaConstraintAny) o; + return Objects.equals(any, mfaRequirement.any); + } + + @Override + public int hashCode() { + return Objects.hash(any); + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaMethodId.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaMethodId.java new file mode 100644 index 0000000..0128a68 --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaMethodId.java @@ -0,0 +1,94 @@ +/* + * Copyright 2016-2023 Stefan Kalscheuer + * + * 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 + * + * http://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 de.stklcode.jvault.connector.model.response.embedded; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Embedded multi-factor-authentication (MFA) requirement. + * + * @author Stefan Kalscheuer + * @since 1.2 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class MfaMethodId implements Serializable { + private static final long serialVersionUID = 691298070242998814L; + + @JsonProperty("type") + private String type; + + @JsonProperty("id") + private String id; + + @JsonProperty("uses_passcode") + private Boolean usesPasscode; + + @JsonProperty("name") + private String name; + + /** + * @return MFA method type + */ + public String getType() { + return type; + } + + /** + * @return MFA method id + */ + public String getId() { + return id; + } + + /** + * @return MFA uses passcode id + */ + public Boolean getUsesPasscode() { + return usesPasscode; + } + + /** + * @return MFA method name + */ + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MfaMethodId mfaMethodId = (MfaMethodId) o; + return Objects.equals(type, mfaMethodId.type) && + Objects.equals(id, mfaMethodId.id) && + Objects.equals(usesPasscode, mfaMethodId.usesPasscode) && + Objects.equals(name, mfaMethodId.name); + } + + @Override + public int hashCode() { + return Objects.hash(type, id, usesPasscode, name); + } +} diff --git a/src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaRequirement.java b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaRequirement.java new file mode 100644 index 0000000..c05ec6d --- /dev/null +++ b/src/main/java/de/stklcode/jvault/connector/model/response/embedded/MfaRequirement.java @@ -0,0 +1,73 @@ +/* + * Copyright 2016-2023 Stefan Kalscheuer + * + * 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 + * + * http://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 de.stklcode.jvault.connector.model.response.embedded; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; +import java.util.Map; +import java.util.Objects; + +/** + * Embedded multi-factor-authentication (MFA) requirement. + * + * @author Stefan Kalscheuer + * @since 1.2 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class MfaRequirement implements Serializable { + private static final long serialVersionUID = -2516941512455319638L; + + @JsonProperty("mfa_request_id") + private String mfaRequestId; + + @JsonProperty("mfa_constraints") + private Map mfaConstraints; + + /** + * @return MFA request ID + */ + public String getMfaRequestId() { + return mfaRequestId; + } + + /** + * @return MFA constraints + */ + public Map getMfaConstraints() { + return mfaConstraints; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MfaRequirement mfaRequirement = (MfaRequirement) o; + return Objects.equals(mfaRequestId, mfaRequirement.mfaRequestId) && + Objects.equals(mfaConstraints, mfaRequirement.mfaConstraints); + } + + @Override + public int hashCode() { + return Objects.hash(mfaRequestId, mfaConstraints); + } +} diff --git a/src/test/java/de/stklcode/jvault/connector/model/response/AuthResponseTest.java b/src/test/java/de/stklcode/jvault/connector/model/response/AuthResponseTest.java index d5d2e4c..07bf7cc 100644 --- a/src/test/java/de/stklcode/jvault/connector/model/response/AuthResponseTest.java +++ b/src/test/java/de/stklcode/jvault/connector/model/response/AuthResponseTest.java @@ -19,6 +19,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import de.stklcode.jvault.connector.model.AbstractModelTest; import de.stklcode.jvault.connector.model.response.embedded.AuthData; +import de.stklcode.jvault.connector.model.response.embedded.MfaConstraintAny; +import de.stklcode.jvault.connector.model.response.embedded.MfaMethodId; +import de.stklcode.jvault.connector.model.response.embedded.MfaRequirement; +import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.Test; import java.util.Map; @@ -44,29 +48,50 @@ class AuthResponseTest extends AbstractModelTest { private static final String AUTH_ENTITY_ID = ""; private static final String AUTH_TOKEN_TYPE = "service"; private static final Boolean AUTH_ORPHAN = false; + private static final String MFA_REQUEST_ID = "d0c9eec7-6921-8cc0-be62-202b289ef163"; + private static final String MFA_KEY = "enforcementConfigUserpass"; + private static final String MFA_METHOD_TYPE = "totp"; + private static final String MFA_METHOD_ID = "820997b3-110e-c251-7e8b-ff4aa428a6e1"; + private static final Boolean MFA_METHOD_USES_PASSCODE = true; + private static final String MFA_METHOD_NAME = "sample_mfa_method_name"; private static final String RES_JSON = "{\n" + - " \"auth\": {\n" + - " \"accessor\": \"" + AUTH_ACCESSOR + "\",\n" + - " \"client_token\": \"" + AUTH_CLIENT_TOKEN + "\",\n" + - " \"policies\": [\n" + - " \"" + AUTH_POLICY_1 + "\", \n" + - " \"" + AUTH_POLICY_2 + "\"\n" + - " ],\n" + - " \"token_policies\": [\n" + - " \"" + AUTH_POLICY_2 + "\",\n" + - " \"" + AUTH_POLICY_1 + "\" \n" + - " ],\n" + - " \"metadata\": {\n" + - " \"" + AUTH_META_KEY + "\": \"" + AUTH_META_VALUE + "\"\n" + - " },\n" + - " \"lease_duration\": " + AUTH_LEASE_DURATION + ",\n" + - " \"renewable\": " + AUTH_RENEWABLE + ",\n" + - " \"entity_id\": \"" + AUTH_ENTITY_ID + "\",\n" + - " \"token_type\": \"" + AUTH_TOKEN_TYPE + "\",\n" + - " \"orphan\": " + AUTH_ORPHAN + "\n" + - " }\n" + - "}"; + " \"auth\": {\n" + + " \"accessor\": \"" + AUTH_ACCESSOR + "\",\n" + + " \"client_token\": \"" + AUTH_CLIENT_TOKEN + "\",\n" + + " \"policies\": [\n" + + " \"" + AUTH_POLICY_1 + "\", \n" + + " \"" + AUTH_POLICY_2 + "\"\n" + + " ],\n" + + " \"token_policies\": [\n" + + " \"" + AUTH_POLICY_2 + "\",\n" + + " \"" + AUTH_POLICY_1 + "\" \n" + + " ],\n" + + " \"metadata\": {\n" + + " \"" + AUTH_META_KEY + "\": \"" + AUTH_META_VALUE + "\"\n" + + " },\n" + + " \"lease_duration\": " + AUTH_LEASE_DURATION + ",\n" + + " \"renewable\": " + AUTH_RENEWABLE + ",\n" + + " \"entity_id\": \"" + AUTH_ENTITY_ID + "\",\n" + + " \"token_type\": \"" + AUTH_TOKEN_TYPE + "\",\n" + + " \"orphan\": " + AUTH_ORPHAN + ",\n" + + " \"mfa_requirement\": {\n" + + " \"mfa_request_id\": \"" + MFA_REQUEST_ID + "\",\n" + + " \"mfa_constraints\": {\n" + + " \"" + MFA_KEY + "\": {\n" + + " \"any\": [\n" + + " {\n" + + " \"type\": \"" + MFA_METHOD_TYPE + "\",\n" + + " \"id\": \"" + MFA_METHOD_ID + "\",\n" + + " \"uses_passcode\": " + MFA_METHOD_USES_PASSCODE + ",\n" + + " \"name\": \"" + MFA_METHOD_NAME + "\"\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + "}"; AuthResponseTest() { super(AuthResponse.class); @@ -82,6 +107,13 @@ protected AuthResponse createFull() { } } + @Test + void testEqualsHashcodeMfa() { + EqualsVerifier.simple().forClass(MfaRequirement.class).verify(); + EqualsVerifier.simple().forClass(MfaConstraintAny.class).verify(); + EqualsVerifier.simple().forClass(MfaMethodId.class).verify(); + } + /** * Test creation from JSON value as returned by Vault (JSON example copied from Vault documentation). */ @@ -107,5 +139,14 @@ void jsonRoundtrip() { assertEquals(2, data.getTokenPolicies().size(), "Incorrect number of token policies"); assertTrue(data.getTokenPolicies().containsAll(Set.of(AUTH_POLICY_2, AUTH_POLICY_1)), "Incorrect token policies"); assertEquals(Map.of(AUTH_META_KEY, AUTH_META_VALUE), data.getMetadata(), "Incorrect auth metadata"); + + assertEquals(MFA_REQUEST_ID, data.getMfaRequirement().getMfaRequestId(), "Incorrect MFA request ID"); + assertEquals(Set.of(MFA_KEY), data.getMfaRequirement().getMfaConstraints().keySet(), "Incorrect MFA constraint keys"); + var mfaConstraint = data.getMfaRequirement().getMfaConstraints().get(MFA_KEY); + assertEquals(1, mfaConstraint.getAny().size(), "Incorrect number of any constraints"); + assertEquals(MFA_METHOD_TYPE, mfaConstraint.getAny().get(0).getType(), "Incorrect MFA method type"); + assertEquals(MFA_METHOD_ID, mfaConstraint.getAny().get(0).getId(), "Incorrect MFA method type"); + assertEquals(MFA_METHOD_USES_PASSCODE, mfaConstraint.getAny().get(0).getUsesPasscode(), "Incorrect MFA method uses passcode"); + assertEquals(MFA_METHOD_NAME, mfaConstraint.getAny().get(0).getName(), "Incorrect MFA method uses passcode"); } }