Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion fhir-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,8 @@
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.13.0</version>
Comment thread
lmsurpre marked this conversation as resolved.
<version>3.18.1</version>
Comment thread
lmsurpre marked this conversation as resolved.
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
Expand Down
2 changes: 2 additions & 0 deletions fhir-smart/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fhir-examples</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
Expand All @@ -57,6 +58,7 @@
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -622,7 +621,7 @@ private List<Scope> 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()
Expand All @@ -641,7 +640,7 @@ private Set<String> 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(" "))
Expand Down
99 changes: 99 additions & 0 deletions fhir-smart/src/main/java/com/ibm/fhir/smart/JWT.java
Original file line number Diff line number Diff line change
@@ -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<String> asList() {
List<String> 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;
}
}
}
39 changes: 39 additions & 0 deletions fhir-smart/src/test/java/com/ibm/fhir/smart/test/JWTTest.java
Original file line number Diff line number Diff line change
@@ -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<String> 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());
}
}