[java-idp-oidc] branch main updated: More unit tests, enforce token type header.
Scott Cantor
cantor.2 at osu.edu
Tue Feb 1 15:14:45 UTC 2022
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=fd06f85b0b3a91a12e8a98756a889b6cfd04b11c
The following commit(s) were added to refs/heads/main by this push:
new fd06f85b More unit tests, enforce token type header.
fd06f85b is described below
commit fd06f85b0b3a91a12e8a98756a889b6cfd04b11c
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Feb 1 10:14:41 2022 -0500
More unit tests, enforce token type header.
---
.../profile/impl/ProcessTokenForIntrospection.java | 12 ++-
.../op/profile/flow/AbstractOidcApiFlowTest.java | 12 ++-
.../op/profile/flow/IntrospectionFlowTest.java | 102 ++++++++++++++++++++-
3 files changed, 119 insertions(+), 7 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
index 4cf70f0b..55a2215b 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ProcessTokenForIntrospection.java
@@ -35,6 +35,7 @@ import org.opensaml.security.criteria.UsageCriterion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.TokenIntrospectionRequest;
@@ -149,6 +150,7 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
return true;
}
+// Checkstyle: CyclomaticComplexity OFF
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -174,6 +176,13 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
}
if (signedJWT != null) {
+ // Check typ header.
+ final JOSEObjectType typ = signedJWT.getHeader().getType();
+ if (typ == null || !"at+jwt".equals(typ.getType())) {
+ log.warn("{} Missing or invalid token type: {}", getLogPrefix(), typ != null ? typ.getType() : "null");
+ return;
+ }
+
if (credentialResolver == null) {
log.error("{} No CredentialResolver available, can't verify JWT signature", getLogPrefix());
return;
@@ -203,7 +212,7 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
try {
claimsValidator.validate(tokenClaimsSet, profileRequestContext);
} catch (final JWTValidationException e) {
- log.warn("{} Claims validation failed, token is invalid", getLogPrefix(), e.getMessage());
+ log.warn("{} Claims validation failed, token is invalid: {}", getLogPrefix(), e.getMessage());
return;
}
@@ -211,6 +220,7 @@ public class ProcessTokenForIntrospection extends AbstractOIDCRequestAction<Toke
profileRequestContext.getOutboundMessageContext().getSubcontext(
OAuth2TokenIntrospectionResponseContext.class).setTokenClaimsSet(tokenClaimsSet);
}
+// Checkstyle: CyclomaticComplexity ON
/**
* Attempt to parse token.
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
index 3d1d10ad..de784930 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
@@ -102,7 +102,7 @@ public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
protected BearerAccessToken buildJWTToken(final String clientId, final String subject, final Scope scope,
final Collection<String> audience, final PrivateKey key, final String alg) throws JOSEException {
- final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+ final AccessTokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
.setJWTID(idGenerator)
.setClientID(new ClientID(clientId))
.setIssuer("https://op.example.org")
@@ -114,10 +114,16 @@ public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
.setScope(scope)
.setAudience(audience)
.build();
+ return buildJWTToken(claims, key, alg);
+ }
+
+ protected BearerAccessToken buildJWTToken(final AccessTokenClaimsSet claims, final PrivateKey key, final String alg)
+ throws JOSEException {
if (key != null) {
final JWSAlgorithm jwsAlgorithm = new JWSAlgorithm(alg);
final JWSSigner signer = getSigner(key, jwsAlgorithm);
- final JWSHeader.Builder headerBuilder = new JWSHeader.Builder(jwsAlgorithm);
+ final JWSHeader.Builder headerBuilder =
+ new JWSHeader.Builder(jwsAlgorithm).type(new JOSEObjectType("at+jwt"));
final SignedJWT jwt = new SignedJWT(headerBuilder.build(), claims.getClaimsSet());
jwt.sign(signer);
return new BearerAccessToken(jwt.serialize());
@@ -125,7 +131,7 @@ public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
return new BearerAccessToken(new PlainJWT(claims.getClaimsSet()).serialize());
}
}
-
+
/**
* Returns correct implementation of signer based on algorithm type.
*
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
index d8d53200..43b78d28 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
@@ -20,8 +20,10 @@ package net.shibboleth.idp.plugin.oidc.op.profile.flow;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.opensaml.storage.StorageService;
@@ -41,14 +43,16 @@ import com.nimbusds.oauth2.sdk.TokenIntrospectionErrorResponse;
import com.nimbusds.oauth2.sdk.TokenIntrospectionSuccessResponse;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
import com.nimbusds.oauth2.sdk.id.Audience;
+import com.nimbusds.oauth2.sdk.id.ClientID;
-import net.shibboleth.idp.plugin.oidc.op.profile.spring.factory.BasicJWKCredentialFactoryBean;
-import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
import net.shibboleth.oidc.security.credential.JWKCredential;
import net.shibboleth.utilities.java.support.collection.Pair;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.security.DataSealerException;
+// Checkstyle: ThrowsCount OFF
+
/**
* Unit tests for the OAuth2 introspection flow.
*/
@@ -166,7 +170,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
}
@Test
- public void testSuccessJWT() throws JOSEException, IOException {
+ public void testSuccessJWTNoAudience() throws JOSEException, IOException {
storeMetadata(storageService, clientId, clientSecret, scope);
setBasicAuth(clientId, clientSecret);
setHttpFormRequest("POST", Map.of(
@@ -184,6 +188,98 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
Assert.assertNull(resp.getAudience());
}
+ @Test
+ public void testSuccessJWTAudience() throws JOSEException, IOException {
+ storeMetadata(storageService, clientId, clientSecret, scope);
+ setBasicAuth(clientId, clientSecret);
+ setHttpFormRequest("POST", Map.of(
+ "token",
+ buildJWTToken("https://sp2.example.org", "sub", scope,
+ List.of("https://sp.example.org", clientId),
+ signingKey.getPrivateKey(), "RS256").toJSONObject()
+ .getAsString("access_token"),
+ "token_type",
+ "access_token"));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final TokenIntrospectionSuccessResponse resp =
+ parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+ Assert.assertTrue(resp.isActive());
+ Assert.assertEquals(resp.getClientID().getValue(), "https://sp2.example.org");
+ Assert.assertEquals(resp.getScope(), scope);
+ Assert.assertEquals(resp.getAudience(),
+ List.of(new Audience("https://sp.example.org"), new Audience(clientId)));
+ }
+
+ @Test
+ public void testFailureJWTExpired() throws JOSEException, IOException {
+ storeMetadata(storageService, clientId, clientSecret, scope);
+ setBasicAuth(clientId, clientSecret);
+ final AccessTokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+ .setJWTID(idGenerator)
+ .setClientID(new ClientID(clientId))
+ .setIssuer("https://op.example.org")
+ .setSubject(clientId)
+ .setIssuedAt(Instant.now().minusSeconds(300))
+ .setNotBefore(Instant.now().minusSeconds(300))
+ .setExpiresAt(Instant.now().minusSeconds(200))
+ .setAuthenticationTime(Instant.now())
+ .setScope(scope)
+ .build();
+ setHttpFormRequest("POST", Map.of(
+ "token",
+ buildJWTToken(claims, signingKey.getPrivateKey(), "RS256").toJSONObject().getAsString("access_token"),
+ "token_type",
+ "access_token"));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final TokenIntrospectionSuccessResponse resp =
+ parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+ Assert.assertFalse(resp.isActive());
+ }
+
+ @Test
+ public void testFailureJWTNotYetValid() throws JOSEException, IOException {
+ storeMetadata(storageService, clientId, clientSecret, scope);
+ setBasicAuth(clientId, clientSecret);
+ final AccessTokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+ .setJWTID(idGenerator)
+ .setClientID(new ClientID(clientId))
+ .setIssuer("https://op.example.org")
+ .setSubject(clientId)
+ .setIssuedAt(Instant.now())
+ .setNotBefore(Instant.now().plusSeconds(300))
+ .setExpiresAt(Instant.now().plusSeconds(1800))
+ .setAuthenticationTime(Instant.now())
+ .setScope(scope)
+ .build();
+ setHttpFormRequest("POST", Map.of(
+ "token",
+ buildJWTToken(claims, signingKey.getPrivateKey(), "RS256").toJSONObject().getAsString("access_token"),
+ "token_type",
+ "access_token"));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final TokenIntrospectionSuccessResponse resp =
+ parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+ Assert.assertFalse(resp.isActive());
+ }
+
+ @Test
+ public void testFailureJWTNotAuthorized() throws JOSEException, IOException {
+ storeMetadata(storageService, clientId, clientSecret, scope);
+ setBasicAuth(clientId, clientSecret);
+ setHttpFormRequest("POST", Map.of(
+ "token",
+ buildJWTToken("https://sp3.example.org", "sub", scope,
+ List.of("https://sp.example.org", "https://sp2.example.org"),
+ signingKey.getPrivateKey(), "RS256").toJSONObject()
+ .getAsString("access_token"),
+ "token_type",
+ "access_token"));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final TokenIntrospectionSuccessResponse resp =
+ parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+ Assert.assertFalse(resp.isActive());
+ }
+
@Test
public void testFailureJWTWrongKey() throws JOSEException, IOException {
storeMetadata(storageService, clientId, clientSecret, scope);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list