[java-idp-oidc] 02/03: JOIDC-222 - Support for OpenID Federation
Henri Mikkonen
henri.mikkonen at iki.fi
Fri May 2 12:02:00 UTC 2025
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=f60166601fb9e7c9a2a14b82789eb549c7a821c3
commit f60166601fb9e7c9a2a14b82789eb549c7a821c3
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri May 2 11:16:51 2025 +0300
JOIDC-222 - Support for OpenID Federation
https://shibboleth.atlassian.net/browse/JOIDC-222
Add federation-specific request object validation to authorize and PAR -flows
- The federation draft mandates the use of jti and exp -claims and denies the use of sub -claim
- Also the audience validation is stricter: no additional audiences must be specified
- Default chain specified by flow beans shibboleth.oidfed(.par).DefaultSignedRequestObjectClaimsValidation
- May be overridden via shibboleth.oidfed(.par).RequestObjectClaimsValidation
- Covered by the flow tests
---
.../oauth2/profile/impl/ValidateRequestObject.java | 46 +++++++++-
.../pushed-authorization-beans.xml | 34 +++++++-
.../idp/flows/oidc/authorize/authorize-beans.xml | 32 ++++++-
.../profile/impl/ValidateRequestObjectTest.java | 27 ++++++
.../AuthorizeFlowAutomaticRegistrationTest.java | 74 +++++++++++++++++
...shedAuthorizeFlowAutomaticRegistrationTest.java | 97 ++++++++++++++++++++++
6 files changed, 306 insertions(+), 4 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObject.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObject.java
index 874eac2b..3f0ddec4 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObject.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObject.java
@@ -15,6 +15,7 @@
package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
import java.text.ParseException;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -62,6 +63,12 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
/** The claims validator to be applied for validating the plain/unsigned request object. */
@NonnullAfterInit private ClaimsValidator plainClaimsValidator;
+ /** The claims validator to be applied for validating the federation request object. */
+ @NonnullAfterInit private ClaimsValidator federationClaimsValidator;
+
+ /** The predicate used for using federation claims validator instead of signed claims validator. */
+ @NonnullAfterInit private Predicate<ProfileRequestContext> useFederationClaimsValidatorCondition;
+
/** The flag to control whether to use the PAR-endpoint logic for the validation. */
private boolean parEndpointLogic;
@@ -80,6 +87,18 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
signedClaimsValidator = Constraint.isNotNull(validator, "Signed claims validator cannot be null");
}
+ /**
+ * Set the claims validator used for validating the federation request object.
+ *
+ * @param validator What to set
+ *
+ * @since 4.3.0
+ */
+ public void setFederationClaimsValidator(@Nonnull final ClaimsValidator validator) {
+ ifInitializedThrowUnmodifiabledComponentException();
+ federationClaimsValidator = Constraint.isNotNull(validator, "Federation claims validator cannot be null");
+ }
+
/**
* Set the claims validator used for validating the plain/unsigned request object.
*
@@ -90,6 +109,17 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
plainClaimsValidator = Constraint.isNotNull(validator, "Plain claims validator cannot be null");
}
+ /**
+ * Set the predicate used for using federation claims validator instead of signed claims validator.
+ *
+ * @param condition What to set
+ *
+ * @since 4.3.0
+ */
+ public void setUseFederationClaimsValidatorCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ useFederationClaimsValidatorCondition =
+ Constraint.isNotNull(condition, "Use federation claims validator condition cannot be null");
+ }
/**
* Set the flag to control whether to use the PAR-endpoint logic for the validation.
*
@@ -109,10 +139,18 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
throw new ComponentInitializationException("ClaimsValidator for signed requests cannot be null");
}
+ if (federationClaimsValidator == null) {
+ throw new ComponentInitializationException("ClaimsValidator for federation requests cannot be null");
+ }
+
if (plainClaimsValidator == null) {
throw new ComponentInitializationException("ClaimsValidator for plain requests cannot be null");
}
-}
+
+ if (useFederationClaimsValidatorCondition == null) {
+ throw new ComponentInitializationException("UseFederationClaimsValidatorCondition cannot be null");
+ }
+ }
/** {@inheritDoc} */
@Override
@@ -224,7 +262,11 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
try {
if (requestObject instanceof SignedJWT) {
// Signature of signed request object is handled earlier in the flow
- signedClaimsValidator.validate(claimsSet, profileRequestContext);
+ if (useFederationClaimsValidatorCondition.test(profileRequestContext)) {
+ federationClaimsValidator.validate(claimsSet, profileRequestContext);
+ } else {
+ signedClaimsValidator.validate(claimsSet, profileRequestContext);
+ }
} else {
plainClaimsValidator.validate(claimsSet, profileRequestContext);
}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
index 1707e1ef..8b499cd4 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
@@ -230,7 +230,13 @@
scope="prototype"
p:plainClaimsValidator="#{getObject('shibboleth.oidc.par.DefaultPlainRequestObjectClaimsValidation')}"
p:signedClaimsValidator="#{getObject('shibboleth.oidc.par.SignedRequestObjectClaimsValidation') ?: getObject('shibboleth.oidc.par.DefaultSignedRequestObjectClaimsValidation')}"
- p:parEndpointLogic="true"/>
+ p:parEndpointLogic="true"
+ p:federationClaimsValidator="#{getObject('shibboleth.oidfed.par.RequestObjectClaimsValidation') ?: getObject('shibboleth.oidfed.par.DefaultRequestObjectClaimsValidation')}">
+ <property name="useFederationClaimsValidatorCondition">
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext))" />
+ </property>
+ </bean>
<bean id="shibboleth.oidc.par.DefaultPlainRequestObjectClaimsValidation"
class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
@@ -243,6 +249,10 @@
class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
p:claimValidators-ref="SignedClaimsValidators" />
+ <bean id="shibboleth.oidfed.par.DefaultRequestObjectClaimsValidation"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
+ p:claimValidators-ref="FederationClaimsValidators" />
+
<bean id="ExpiryClaimsValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
@@ -288,6 +298,28 @@
<ref bean="AudienceClaimsValidator" />
</util:list>
+ <util:list id="FederationClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <ref bean="ExpiryClaimsValidator" />
+ <ref bean="NotBeforeClaimsValidator" />
+ <ref bean="IssuerClaimsValidator" />
+ <ref bean="ClientIdClaimsValidator" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator"
+ p:extraAudienceValidation="true">
+ <property name="audienceLookupStrategy">
+ <bean parent="shibboleth.BiFunctions.Expression"
+ c:expression="#custom.apply(#input1)"
+ p:customObject-ref="shibboleth.ResponderIdLookup.Simple" />
+ </property>
+ </bean>
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.JWTIdentifierClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:replayCache-ref="shibboleth.ReplayCache" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="exp" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="sub" />
+ </util:list>
+
<bean id="DefaultParUseOnlyRequestObjectCondition" parent="shibboleth.BiConditions.Expression"
c:expression="#input1 != null and #input1.getFirst() != null and #input1.getFirst().specifiesRequestObject()" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 9a09cce2..5acb6b53 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -390,7 +390,12 @@
<bean id="ValidateRequestObject" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateRequestObject"
scope="prototype"
p:plainClaimsValidator="#{getObject('shibboleth.oidc.PlainRequestObjectClaimsValidation') ?: getObject('shibboleth.oidc.DefaultPlainRequestObjectClaimsValidation')}"
- p:signedClaimsValidator="#{getObject('shibboleth.oidc.SignedRequestObjectClaimsValidation') ?: getObject('shibboleth.oidc.DefaultSignedRequestObjectClaimsValidation')}">
+ p:signedClaimsValidator="#{getObject('shibboleth.oidc.SignedRequestObjectClaimsValidation') ?: getObject('shibboleth.oidc.DefaultSignedRequestObjectClaimsValidation')}"
+ p:federationClaimsValidator="#{getObject('shibboleth.oidfed.RequestObjectClaimsValidation') ?: getObject('shibboleth.oidfed.DefaultRequestObjectClaimsValidation')}">
+ <property name="useFederationClaimsValidatorCondition">
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext))" />
+ </property>
</bean>
<bean id="shibboleth.oidc.DefaultPlainRequestObjectClaimsValidation"
@@ -401,6 +406,10 @@
class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
p:claimValidators-ref="SignedClaimsValidators" />
+ <bean id="shibboleth.oidfed.DefaultRequestObjectClaimsValidation"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
+ p:claimValidators-ref="FederationClaimsValidators" />
+
<bean id="ExpiryClaimsValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
@@ -440,6 +449,27 @@
<ref bean="AudienceClaimsValidator" />
</util:list>
+ <util:list id="FederationClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <ref bean="ExpiryClaimsValidator" />
+ <ref bean="NotBeforeClaimsValidator" />
+ <ref bean="IssuerClaimsValidator" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator"
+ p:extraAudienceValidation="true">
+ <property name="audienceLookupStrategy">
+ <bean parent="shibboleth.BiFunctions.Expression"
+ c:expression="#custom.apply(#input1)"
+ p:customObject-ref="shibboleth.ResponderIdLookup.Simple" />
+ </property>
+ </bean>
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.JWTIdentifierClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:replayCache-ref="shibboleth.ReplayCache" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="exp" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="sub" />
+ </util:list>
+
<bean id="DefaultRequestRedirectURILookupFunction"
class="net.shibboleth.idp.plugin.oidc.op.messaging.context.navigate.DefaultRequestRedirectURILookupFunction"
p:useOnlyRequestObjectPredicate-ref="#{'%{idp.oauth2.authorize.useOnlyRequestObject:DefaultUseOnlyRequestObjectCondition}'.trim()}"/>
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObjectTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObjectTest.java
index f7b80f58..354b6aba 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObjectTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObjectTest.java
@@ -26,6 +26,7 @@ import net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator;
import net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator;
import net.shibboleth.oidc.security.jwt.claims.impl.NotBeforeClaimsValidator;
import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.PredicateSupport;
import java.net.URI;
import java.net.URISyntaxException;
@@ -98,6 +99,8 @@ public class ValidateRequestObjectTest {
action = new ValidateRequestObject();
action.setPlainClaimsValidator(buildPlainClaimsValidation());
action.setSignedClaimsValidator(buildSignedClaimsValidation("000123"));
+ action.setFederationClaimsValidator(buildSignedClaimsValidation("000123"));
+ action.setUseFederationClaimsValidatorCondition(PredicateSupport.alwaysFalse());
action.initialize();
}
@@ -134,6 +137,8 @@ public class ValidateRequestObjectTest {
public void testInitFailsNoPlainValidator() throws ComponentInitializationException {
action = new ValidateRequestObject();
action.setSignedClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setFederationClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setUseFederationClaimsValidatorCondition(PredicateSupport.alwaysFalse());
action.initialize();
}
@@ -141,6 +146,26 @@ public class ValidateRequestObjectTest {
public void testInitFailsNoSignedValidators() throws ComponentInitializationException {
action = new ValidateRequestObject();
action.setPlainClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setFederationClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setUseFederationClaimsValidatorCondition(PredicateSupport.alwaysFalse());
+ action.initialize();
+ }
+
+ @Test(expectedExceptions = ComponentInitializationException.class)
+ public void testInitFailsNoFederationValidators() throws ComponentInitializationException {
+ action = new ValidateRequestObject();
+ action.setPlainClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setSignedClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setUseFederationClaimsValidatorCondition(PredicateSupport.alwaysFalse());
+ action.initialize();
+ }
+
+ @Test(expectedExceptions = ComponentInitializationException.class)
+ public void testInitFailsNoUseFederationClaimsCondition() throws ComponentInitializationException {
+ action = new ValidateRequestObject();
+ action.setPlainClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setSignedClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setFederationClaimsValidator(new ChainingJWTClaimsValidator());
action.initialize();
}
@@ -149,6 +174,8 @@ public class ValidateRequestObjectTest {
action = new ValidateRequestObject();
action.setPlainClaimsValidator(new ChainingJWTClaimsValidator());
action.setSignedClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setFederationClaimsValidator(new ChainingJWTClaimsValidator());
+ action.setUseFederationClaimsValidatorCondition(PredicateSupport.alwaysFalse());
action.initialize();
}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
index 4cdb852f..592d071c 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
@@ -20,9 +20,12 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.text.ParseException;
+import java.time.Duration;
+import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.webflow.executor.FlowExecutionResult;
@@ -66,6 +69,8 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
launchAuthenticationRequest(clientId, "openid profile", plainRequestObject(Map.of(
"iss", clientId,
"aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
"response_type", "code",
"scope", "openid profile",
"redirect_uri", redirectUri)));
@@ -81,6 +86,8 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
"iss", clientId,
"aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
"response_type", "code",
"scope", "openid profile",
"redirect_uri", redirectUri)));
@@ -93,6 +100,73 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
Assert.assertEquals(unwrapTrustChainFromAuthorizeCode(successResponse), List.of(clientId, anchorId));
}
+ @Test
+ public void testWithValidTrustChain_signedRequestObject_missingJti()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+ "iss", clientId,
+ "aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri)));
+ assertErrorCode(result, "invalid_request_object");
+ }
+
+ @Test
+ public void testWithValidTrustChain_signedRequestObject_missingExp()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+ "iss", clientId,
+ "aud", issuer,
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri)));
+ assertErrorCode(result, "invalid_request_object");
+ }
+
+ @Test
+ public void testWithValidTrustChain_signedRequestObject_containsForbiddenSub()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+ "iss", clientId,
+ "sub", "mockValue",
+ "aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri)));
+ assertErrorCode(result, "invalid_request_object");
+ }
+
+ @Test
+ public void testWithValidTrustChain_signedRequestObject_containsAdditionalAudience()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+ "iss", clientId,
+ "aud", List.of(issuer, "https://another.example.org"),
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri)));
+ assertErrorCode(result, "invalid_request_object");
+ }
+
@Test
public void testWithValidTrustChain_leafKeySignedRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
index b84a33f8..ddf5dd40 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
@@ -19,12 +19,14 @@ import java.net.URI;
import java.net.URLEncoder;
import java.security.PublicKey;
import java.text.ParseException;
+import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.webflow.executor.FlowExecutionResult;
@@ -95,6 +97,8 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
"client_id", clientId,
"iss", clientId,
"aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
"response_type", "code",
"scope", "openid",
"redirect_uri", redirectUri)).serialize()));
@@ -106,6 +110,97 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
}
+ @Test
+ public void testWithPublicClientWithSignedRequestObject_missingJti() throws Exception {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId, metadata);
+ setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
+ "client_id", clientId,
+ "iss", clientId,
+ "aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "response_type", "code",
+ "scope", "openid",
+ "redirect_uri", redirectUri)).serialize()));
+ final FlowExecutionResult result =
+ flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testWithPublicClientWithSignedRequestObject_missingExp() throws Exception {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId, metadata);
+ setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
+ "client_id", clientId,
+ "iss", clientId,
+ "aud", issuer,
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid",
+ "redirect_uri", redirectUri)).serialize()));
+ final FlowExecutionResult result =
+ flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testWithPublicClientWithSignedRequestObject_constainsForbiddenSub() throws Exception {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId, metadata);
+ setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
+ "client_id", clientId,
+ "sub", "mockValue",
+ "iss", clientId,
+ "aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid",
+ "redirect_uri", redirectUri)).serialize()));
+ final FlowExecutionResult result =
+ flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testWithPublicClientWithSignedRequestObject_containsAdditionalAudience() throws Exception {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId, metadata);
+ setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
+ "client_id", clientId,
+ "iss", clientId,
+ "aud", List.of(issuer, "https://additional.example.org"),
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid",
+ "redirect_uri", redirectUri)).serialize()));
+ final FlowExecutionResult result =
+ flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
@Test
public void testWithPublicClientWithPlainRequestObject() throws Exception {
final OIDCClientMetadata metadata = new OIDCClientMetadata();
@@ -119,6 +214,8 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
"client_id", clientId,
"iss", clientId,
"aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
"response_type", "code",
"scope", "openid",
"redirect_uri", redirectUri)).serialize()));
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list