[java-oidc-common] branch main updated: JCOMOIDC-54 - Add untrusted audience validation to validator

Phil Smart philip.smart at jisc.ac.uk
Wed Dec 14 14:11:07 UTC 2022


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-oidc-common.

View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=0956a94059273fa6f9c4ac60bc6fcd92ce5d10e7

The following commit(s) were added to refs/heads/main by this push:
     new 0956a94  JCOMOIDC-54 - Add untrusted audience validation to validator
0956a94 is described below

commit 0956a94059273fa6f9c4ac60bc6fcd92ce5d10e7
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Dec 14 14:11:04 2022 +0000

    JCOMOIDC-54 - Add untrusted audience validation to validator
    
            - Add a way to set untrusted audience values
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-54
---
 .../jwt/claims/impl/AudienceClaimsValidator.java   | 66 +++++++++++++++++++-
 .../claims/impl/AudienceClaimsValidatorTest.java   | 71 ++++++++++++++++++++++
 2 files changed, 134 insertions(+), 3 deletions(-)

diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java
index 0a3e18d..b0fc706 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidator.java
@@ -18,8 +18,12 @@
  
 package net.shibboleth.oidc.security.jwt.claims.impl;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
 import javax.annotation.Nonnull;
 
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -44,9 +48,20 @@ public class AudienceClaimsValidator extends AbstractClaimsValidator{
     /** Strategy to find the audience value from the context.*/
     @NonnullAfterInit private BiFunction<ProfileRequestContext,JWTClaimsSet, String> audienceLookupStrategy;
     
+    /** 
+     * A strategy to lookup a list of untrusted audiences that, if contained in the 'aud' claim, 
+     * should invalidate the token.
+     */
+    @Nonnull private BiFunction<ProfileRequestContext, JWTClaimsSet, List<String>> untrustedAudiencesLookupStrategy;
+    
     /** Allow a missing aud claim. */
     private boolean allowMissing;
     
+    /** Constructor. */
+    public AudienceClaimsValidator() {
+        untrustedAudiencesLookupStrategy = (prc, claims) -> Collections.emptyList();
+    }
+    
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -69,6 +84,39 @@ public class AudienceClaimsValidator extends AbstractClaimsValidator{
         audienceLookupStrategy = Constraint.isNotNull(strategy, "Audience lookup strategy can not be null");
     }
     
+    /**
+     * Set the strategy used to lookup the list of untrusted audiences. 
+     * 
+     * @param strategy the strategy
+     * 
+     * @since 2.2.0
+     */
+    public void setUntrustedAudiencesLookupStrategy(
+            @Nonnull final BiFunction<ProfileRequestContext, JWTClaimsSet, List<String>> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        untrustedAudiencesLookupStrategy = Constraint.isNotNull(strategy,
+                "Untrusted audiences lookup strategy can not be null");
+    }
+    
+    /**
+     * Set a list of untrusted audiences. Can be empty but never {@literal null}.
+     * 
+     * @param untrusted the list of untrusted audiences
+     * 
+     * @since 2.2.0
+     */
+    public void setUntrustedAudiences(@Nonnull final List<String> untrusted) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        
+        Constraint.isNotNull(untrusted, "Untrusted audiences list can not be null");
+        
+        untrustedAudiencesLookupStrategy = (prc, claims) -> 
+            Collections.unmodifiableList(untrusted.stream().filter(Objects::nonNull).collect(Collectors.toList()));
+    }
+    
     /**
      * Set whether a missing 'aud' claim is valid or not.
      * 
@@ -91,16 +139,28 @@ public class AudienceClaimsValidator extends AbstractClaimsValidator{
             throw new JWTValidationException("Audience value not returned by lookup function");
         }
         
-        final List<String> audList = claims.getAudience();
+        List<String> untrustedAudiences = untrustedAudiencesLookupStrategy.apply(context, claims);
+        if (untrustedAudiences == null) {
+            untrustedAudiences = Collections.emptyList();
+        }
+        
+        final List<String> audList = claims.getAudience();        
+        
         if (audList != null && !audList.isEmpty()) {
             boolean audMatch = false;
+            boolean untrustedMatch = false;
             for (final String aud : audList) {
+                if (aud == null) {
+                    continue;
+                }
                 if (acceptedAudience.equals(aud)) {
                     audMatch = true;
-                    break;
+                }
+                if (untrustedAudiences.contains(aud)) {
+                    untrustedMatch = true;
                 }
             }
-            if (!audMatch) {
+            if (!audMatch || untrustedMatch) {
                 throw new JWTValidationException("JWT audience rejected: " + audList);
             }
         } else if (!allowMissing) {
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java
index 04f4af7..67babd4 100644
--- a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/jwt/claims/impl/AudienceClaimsValidatorTest.java
@@ -17,6 +17,7 @@
 
 package net.shibboleth.oidc.security.jwt.claims.impl;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -29,6 +30,7 @@ import com.nimbusds.jwt.JWTClaimsSet;
 
 import net.shibboleth.oidc.jwt.claims.JWTValidationException;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
 
 /** Test for the {@link AudienceClaimsValidator}. */
 public class AudienceClaimsValidatorTest extends AbstractClaimsValidatorTest {
@@ -37,6 +39,7 @@ public class AudienceClaimsValidatorTest extends AbstractClaimsValidatorTest {
     @Nonnull private AudienceClaimsValidator validator;
 
     
+    @Override
     @BeforeMethod
     public void setup() throws ComponentInitializationException {
         super.setup();
@@ -90,6 +93,74 @@ public class AudienceClaimsValidatorTest extends AbstractClaimsValidatorTest {
         validator.validate(claimsSet, prc);
     }
     
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doInValidateTest_UntrustedAudience() 
+            throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = 
+                new JWTClaimsSet.Builder().audience(List.of("client-id", "untrusted-client")).build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy((prc,claims) -> "client-id");
+        validator.setUntrustedAudiences(List.of("untrusted-client"));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test
+    public void doValidateTest_UntrustedAudiencesNotInClaim() 
+            throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = 
+                new JWTClaimsSet.Builder().audience(List.of("client-id", "client-id-2")).build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy((prc,claims) -> "client-id");
+        validator.setUntrustedAudiences(List.of("untrusted-client"));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = ConstraintViolationException.class)
+    public void doValidateTest_SetNullUntrustedAudienceList() 
+            throws JWTValidationException, ComponentInitializationException {
+        validator.setUntrustedAudiences(null);
+    }
+    
+    @Test
+    public void doValidateTest_NoAudienceAllow() 
+            throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = 
+                new JWTClaimsSet.Builder().build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy((prc,claims) -> "client-id");
+        validator.setAllowMissing(true);
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doValidateTest_NoAudienceDisallow() 
+            throws JWTValidationException, ComponentInitializationException {
+        final JWTClaimsSet claimsSet = 
+                new JWTClaimsSet.Builder().build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy((prc,claims) -> "client-id");
+        validator.setAllowMissing(false);
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
+    @Test(expectedExceptions = JWTValidationException.class)
+    public void doValidateTest_NullClaims() 
+            throws JWTValidationException, ComponentInitializationException {
+        final List<String> nullClaims = new ArrayList<>();
+        nullClaims.add(null);
+        final JWTClaimsSet claimsSet = 
+                new JWTClaimsSet.Builder().audience(nullClaims).build();
+        validator.setId("test-validator");
+        validator.setAudienceLookupStrategy((prc,claims) -> "client-id");
+        validator.setUntrustedAudiences(List.of("untrusted-client"));
+        validator.initialize();
+        validator.validate(claimsSet, prc);
+    }
+    
     @Test(expectedExceptions = JWTValidationException.class)
     public void doInValidateTestNullAudienceInContext() 
             throws JWTValidationException, ComponentInitializationException {

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list