[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