From 9114085cd0d438ee92106763ef20aec99a6ea54f Mon Sep 17 00:00:00 2001 From: Lee Surprenant Date: Tue, 5 Oct 2021 11:17:06 -0400 Subject: [PATCH 1/2] Remove runtime dependency on the auth0 java-jwt lib We use such a small portion of this library that I was always on the fence about whether we should even bother with it. Recently, a user hit some issues with conflicting jackson dependency versions and so I decided to see what fhir-smart might look like without this dependency. Signed-off-by: Lee Surprenant --- fhir-parent/pom.xml | 2 +- fhir-smart/pom.xml | 2 + ...licyEnforcementPersistenceInterceptor.java | 9 +- .../src/main/java/com/ibm/fhir/smart/JWT.java | 99 +++++++++++++++++++ .../java/com/ibm/fhir/smart/test/JWTTest.java | 39 ++++++++ 5 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 fhir-smart/src/main/java/com/ibm/fhir/smart/JWT.java create mode 100644 fhir-smart/src/test/java/com/ibm/fhir/smart/test/JWTTest.java diff --git a/fhir-parent/pom.xml b/fhir-parent/pom.xml index b3262e358c6..ff577f89181 100644 --- a/fhir-parent/pom.xml +++ b/fhir-parent/pom.xml @@ -415,7 +415,7 @@ com.auth0 java-jwt - 3.13.0 + 3.18.1 org.antlr diff --git a/fhir-smart/pom.xml b/fhir-smart/pom.xml index 51a1cd38148..e4b2fae1002 100644 --- a/fhir-smart/pom.xml +++ b/fhir-smart/pom.xml @@ -41,6 +41,7 @@ ${project.groupId} fhir-examples + test ${project.groupId} @@ -57,6 +58,7 @@ com.auth0 java-jwt + test org.testng diff --git a/fhir-smart/src/main/java/com/ibm/fhir/smart/AuthzPolicyEnforcementPersistenceInterceptor.java b/fhir-smart/src/main/java/com/ibm/fhir/smart/AuthzPolicyEnforcementPersistenceInterceptor.java index 619b755ed62..10a314e9616 100644 --- a/fhir-smart/src/main/java/com/ibm/fhir/smart/AuthzPolicyEnforcementPersistenceInterceptor.java +++ b/fhir-smart/src/main/java/com/ibm/fhir/smart/AuthzPolicyEnforcementPersistenceInterceptor.java @@ -18,9 +18,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.auth0.jwt.JWT; -import com.auth0.jwt.interfaces.Claim; -import com.auth0.jwt.interfaces.DecodedJWT; import com.ibm.fhir.config.FHIRRequestContext; import com.ibm.fhir.model.resource.Bundle; import com.ibm.fhir.model.resource.Provenance; @@ -53,6 +50,8 @@ import com.ibm.fhir.search.util.ReferenceUtil; import com.ibm.fhir.search.util.ReferenceValue; import com.ibm.fhir.search.util.SearchUtil; +import com.ibm.fhir.smart.JWT.Claim; +import com.ibm.fhir.smart.JWT.DecodedJWT; import com.ibm.fhir.smart.Scope.ContextType; import com.ibm.fhir.smart.Scope.Permission; @@ -622,7 +621,7 @@ private List getScopesFromToken(DecodedJWT jwt) throws FHIRPersistenceInt scopeStrings = Arrays.asList(claim.asString().split("\\s+")); } else { log.fine("Found scope claim was expected to be a string but is not; processing as a list"); - scopeStrings = claim.asList(String.class); + scopeStrings = claim.asList(); } return scopeStrings.stream() @@ -641,7 +640,7 @@ private Set getPatientIdFromToken(DecodedJWT jwt) throws FHIRPersistence String patientId = claim.asString(); if (patientId == null) { - return new HashSet<>(claim.asList(String.class)); + return new HashSet<>(claim.asList()); } return Stream.of(patientId.split(" ")) diff --git a/fhir-smart/src/main/java/com/ibm/fhir/smart/JWT.java b/fhir-smart/src/main/java/com/ibm/fhir/smart/JWT.java new file mode 100644 index 00000000000..ccb7b6b25a4 --- /dev/null +++ b/fhir-smart/src/main/java/com/ibm/fhir/smart/JWT.java @@ -0,0 +1,99 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.fhir.smart; + +import java.io.StringReader; +import java.util.Base64; +import java.util.Base64.Decoder; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReaderFactory; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; +import jakarta.json.JsonValue.ValueType; + +/** + * A minimal alternative to the com.auth0:java-jwt library. + * This flavor mimics the java-jwt API but implement only the [very small] subset of functionality that is + * actually needed by the fhir-smart policy enforcement. + */ +public class JWT { + private static final JsonReaderFactory JSON_READER_FACTORY = Json.createReaderFactory(null); + private static final Decoder decoder = Base64.getDecoder(); + + public static DecodedJWT decode(String jwt) { + String[] parts = jwt.split("\\."); + if (parts.length < 2 || parts.length > 3 || (parts.length == 2 && !jwt.endsWith("."))) { + throw new IllegalArgumentException("Invalid JWT: expected 3 parts but found " + parts.length); + } + + return new DecodedJWT( + new String(decoder.decode(parts[0])), + new String(decoder.decode(parts[1])), + parts.length == 2 ? null : new String(decoder.decode(parts[2]).toString()) + ); + } + + public static class DecodedJWT { + final JsonObject header; + final JsonObject data; + final JsonObject signature; + + private DecodedJWT(String header, String data, String signature) { + this.header = JSON_READER_FACTORY.createReader(new StringReader(header)).readObject(); + this.data = JSON_READER_FACTORY.createReader(new StringReader(data)).readObject(); + this.signature = signature == null ? null : + JSON_READER_FACTORY.createReader(new StringReader(signature)).readObject(); + } + + public Claim getClaim(String name) { + return new Claim(data.get(name)); + } + } + + public static class Claim { + final JsonValue value; + + private Claim(JsonValue value) { + this.value = value; + } + + public String asString() { + if (value == null) return null; + + switch (value.getValueType()) { + case STRING: + return ((JsonString) value).getString(); + case TRUE: + case FALSE: + case NUMBER: + case NULL: + case OBJECT: + case ARRAY: + default: + return value.toString(); + } + } + + public List asList() { + List list = value.asJsonArray().getValuesAs(JsonString.class).stream() + .map(s -> s.getString()) + .collect(Collectors.toList()); + return Collections.unmodifiableList(list); + } + + public boolean isNull() { + if (value == null || value.getValueType() == ValueType.NULL) { + return true; + } + return false; + } + } +} diff --git a/fhir-smart/src/test/java/com/ibm/fhir/smart/test/JWTTest.java b/fhir-smart/src/test/java/com/ibm/fhir/smart/test/JWTTest.java new file mode 100644 index 00000000000..bf8066af3a5 --- /dev/null +++ b/fhir-smart/src/test/java/com/ibm/fhir/smart/test/JWTTest.java @@ -0,0 +1,39 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.fhir.smart.test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.testng.annotations.Test; + +import com.auth0.jwt.algorithms.Algorithm; +import com.ibm.fhir.smart.JWT; +import com.ibm.fhir.smart.JWT.DecodedJWT; + +/** + * + */ +public class JWTTest { + private String testString = "value"; + private List testList = Arrays.asList(new String[] {"value1", "value2"}); + + private String jwt = com.auth0.jwt.JWT.create() + .withClaim("string", testString) + .withClaim("array", testList) + .sign(Algorithm.none()); + + @Test + public void test() { + DecodedJWT decodedJwt = JWT.decode(jwt); + assertEquals(decodedJwt.getClaim("string").asString(), testString); + assertEquals(decodedJwt.getClaim("array").asList(), testList); + assertTrue(decodedJwt.getClaim("bogus").isNull()); + } +} From fb09408dbda659e82ffbb303417ea0aa5619e3ff Mon Sep 17 00:00:00 2001 From: Lee Surprenant Date: Wed, 6 Oct 2021 12:00:32 -0400 Subject: [PATCH 2/2] Update fhir-parent/pom.xml Signed-off-by: Lee Surprenant --- fhir-parent/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/fhir-parent/pom.xml b/fhir-parent/pom.xml index ff577f89181..e8375f452c7 100644 --- a/fhir-parent/pom.xml +++ b/fhir-parent/pom.xml @@ -416,6 +416,7 @@ com.auth0 java-jwt 3.18.1 + test org.antlr