From 9a0744e0ab04f65f0e90f40253a7bef5c8038b51 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Wed, 10 May 2023 19:43:40 +0100 Subject: [PATCH] Make it easier to get well known properties from TokenIntrospection --- .../oidc/common/runtime/OidcConstants.java | 3 + .../io/quarkus/oidc/TokenIntrospection.java | 42 +++++++ .../oidc/runtime/OidcIdentityProvider.java | 22 ++-- .../io/quarkus/oidc/runtime/OidcProvider.java | 2 +- .../oidc/runtime/TokenIntrospectionTest.java | 108 ++++++++++++++++++ 5 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionTest.java diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java index 39d66426f92d0..931070f98e9df 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java @@ -23,10 +23,13 @@ public final class OidcConstants { public static final String INTROSPECTION_TOKEN_TYPE_HINT = "token_type_hint"; public static final String INTROSPECTION_TOKEN = "token"; public static final String INTROSPECTION_TOKEN_ACTIVE = "active"; + public static final String INTROSPECTION_TOKEN_CLIENT_ID = "client_id"; public static final String INTROSPECTION_TOKEN_EXP = "exp"; public static final String INTROSPECTION_TOKEN_IAT = "iat"; public static final String INTROSPECTION_TOKEN_USERNAME = "username"; public static final String INTROSPECTION_TOKEN_SUB = "sub"; + public static final String INTROSPECTION_TOKEN_AUD = "aud"; + public static final String INTROSPECTION_TOKEN_ISS = "iss"; public static final String REVOCATION_TOKEN = "token"; diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenIntrospection.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenIntrospection.java index 137f139f06423..109d675f47652 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenIntrospection.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/TokenIntrospection.java @@ -1,7 +1,11 @@ package io.quarkus.oidc; +import java.util.HashSet; +import java.util.Set; + import jakarta.json.JsonObject; +import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.oidc.runtime.AbstractJsonObjectResponse; /** @@ -21,6 +25,44 @@ public TokenIntrospection(JsonObject json) { super(json); } + public boolean isActive() { + return getBoolean(OidcConstants.INTROSPECTION_TOKEN_ACTIVE); + } + + public String getUsername() { + return getString(OidcConstants.INTROSPECTION_TOKEN_USERNAME); + } + + public String getSubject() { + return getString(OidcConstants.INTROSPECTION_TOKEN_SUB); + } + + public String getAudience() { + return getString(OidcConstants.INTROSPECTION_TOKEN_AUD); + } + + public String getIssuer() { + return getString(OidcConstants.INTROSPECTION_TOKEN_ISS); + } + + public Set getScopes() { + if (this.contains(OidcConstants.TOKEN_SCOPE)) { + String[] scopesArray = getString(OidcConstants.TOKEN_SCOPE).split(" "); + Set scopes = new HashSet<>(scopesArray.length); + for (String scope : scopesArray) { + scopes.add(scope.trim()); + } + return scopes; + } else { + return null; + } + + } + + public String getClientId() { + return getString(OidcConstants.INTROSPECTION_TOKEN_CLIENT_ID); + } + public String getIntrospectionString() { return getNonNullJsonString(); } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 7595cf1a04a19..25598779b7eac 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -3,6 +3,7 @@ import static io.quarkus.oidc.runtime.OidcUtils.validateAndCreateIdentity; import java.security.Principal; +import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; @@ -282,20 +283,15 @@ && tokenAutoRefreshPrepared(result, vertxContext, resolvedContext.oidcConfig)) { } } else { OidcUtils.setSecurityIdentityIntrospection(builder, result.introspectionResult); - String principalMember = ""; - if (result.introspectionResult.contains(OidcConstants.INTROSPECTION_TOKEN_USERNAME)) { - principalMember = OidcConstants.INTROSPECTION_TOKEN_USERNAME; - } else if (result.introspectionResult.contains(OidcConstants.INTROSPECTION_TOKEN_SUB)) { - // fallback to "sub", if "username" is not present - principalMember = OidcConstants.INTROSPECTION_TOKEN_SUB; + String principalName = result.introspectionResult.getUsername(); + if (principalName == null) { + principalName = result.introspectionResult.getSubject(); } - userName = principalMember.isEmpty() ? "" - : result.introspectionResult.getString(principalMember); - if (result.introspectionResult.contains(OidcConstants.TOKEN_SCOPE)) { - for (String role : result.introspectionResult.getString(OidcConstants.TOKEN_SCOPE) - .split(" ")) { - builder.addRole(role.trim()); - } + userName = principalName != null ? principalName : ""; + + Set scopes = result.introspectionResult.getScopes(); + if (scopes != null) { + builder.addRoles(scopes); } } builder.setPrincipal(new Principal() { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java index 8c40298b1ff29..eb993936834e5 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProvider.java @@ -225,7 +225,7 @@ public TokenIntrospection apply(TokenIntrospection introspectionResult, Throwabl if (t != null) { throw new AuthenticationFailedException(t); } - if (!Boolean.TRUE.equals(introspectionResult.getBoolean(OidcConstants.INTROSPECTION_TOKEN_ACTIVE))) { + if (!introspectionResult.isActive()) { LOG.debugf("Token issued to client %s is not active", oidcConfig.clientId.get()); verifyTokenExpiry(introspectionResult.getLong(OidcConstants.INTROSPECTION_TOKEN_EXP)); throw new AuthenticationFailedException(); diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionTest.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionTest.java new file mode 100644 index 0000000000000..3b7ba097308e5 --- /dev/null +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/TokenIntrospectionTest.java @@ -0,0 +1,108 @@ +package io.quarkus.oidc.runtime; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Set; + +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.oidc.TokenIntrospection; + +public class TokenIntrospectionTest { + TokenIntrospection introspection = new TokenIntrospection( + "{" + + "\"active\": true," + + "\"username\": \"alice\"," + + "\"sub\": \"1234567\"," + + "\"aud\": \"http://localhost:8080\"," + + "\"iss\": \"http://keycloak/realm\"," + + "\"client_id\": \"quarkus\"," + + "\"custom\": null," + + "\"id\": 1234," + + "\"permissions\": [\"read\", \"write\"]," + + "\"scope\": \"add divide\"," + + "\"scopes\": {\"scope\": \"see\"}" + + "}"); + + @Test + public void testActive() { + assertTrue(introspection.isActive()); + } + + @Test + public void testGetUsername() { + assertEquals("alice", introspection.getUsername()); + } + + @Test + public void testGetSubject() { + assertEquals("1234567", introspection.getSubject()); + } + + @Test + public void testGetAudience() { + assertEquals("http://localhost:8080", introspection.getAudience()); + } + + @Test + public void testGetIssuer() { + assertEquals("http://keycloak/realm", introspection.getIssuer()); + } + + @Test + public void testGetScopes() { + assertEquals(Set.of("add", "divide"), introspection.getScopes()); + } + + @Test + public void testGetClientId() { + assertEquals("quarkus", introspection.getClientId()); + } + + @Test + public void testGetString() { + assertEquals("alice", introspection.getString("username")); + assertNull(introspection.getString("usernames")); + } + + @Test + public void testGetBoolean() { + assertTrue(introspection.getBoolean("active")); + assertNull(introspection.getBoolean("activate")); + } + + @Test + public void testGetLong() { + assertEquals(1234, introspection.getLong("id")); + assertNull(introspection.getLong("ids")); + } + + @Test + public void testGetArray() { + JsonArray array = introspection.getArray("permissions"); + assertNotNull(array); + assertEquals(2, array.size()); + assertEquals("read", array.getString(0)); + assertEquals("write", array.getString(1)); + assertNull(introspection.getArray("permit")); + } + + @Test + public void testGetObject() { + JsonObject map = introspection.getObject("scopes"); + assertNotNull(map); + assertEquals(1, map.size()); + assertEquals("see", map.getString("scope")); + } + + @Test + public void testGetNullProperty() { + assertNull(introspection.getString("custom")); + } +}