[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