[java-idp-oidc] branch main updated: JOIDC-200 - Support for OAuth2 Pushed Authorization Requests (PAR)
Henri Mikkonen
henri.mikkonen at iki.fi
Thu Sep 12 14:50:43 UTC 2024
This is an automated email from the git hooks/post-receive script.
hjmikkon 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=7fcb59ee7a320039196e6e574f9699fcde210434
The following commit(s) were added to refs/heads/main by this push:
new 7fcb59ee JOIDC-200 - Support for OAuth2 Pushed Authorization Requests (PAR)
7fcb59ee is described below
commit 7fcb59ee7a320039196e6e574f9699fcde210434
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Sep 12 17:50:13 2024 +0300
JOIDC-200 - Support for OAuth2 Pushed Authorization Requests (PAR)
https://shibboleth.atlassian.net/browse/JOIDC-200
Modified the request object logic to strictly follow the JAR spec, as mandated by the PAR RFC.
- The request object must be signed or signed and encrypted - i.e. no plain JWT allowed
- If the request object is involved, the parameters are solely read from there - i.e. form-parameters are ignored
- client_id is a mandatory claim in the request object
- The chain of claims validators may be customized via shibboleth.oidc.par.SignedRequestObjectClaimsValidation
---
...mOutbounPushedAuthorizationResponseMessage.java | 64 +---
.../ValidatePushedAuthorizationClientIDMatch.java | 2 +-
.../oauth2/profile/impl/ValidateRequestObject.java | 16 +-
.../pushed-authorization-beans.xml | 116 +++---
.../op/profile/flow/PushedAuthorizeFlowTest.java | 424 ++++++++++++++++++++-
.../flow/PushedAuthorizeRequestObjectJWETest.java | 45 +--
6 files changed, 518 insertions(+), 149 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java
index 147a38dc..141cbe11 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java
@@ -17,8 +17,6 @@ package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
import java.net.URI;
import java.text.ParseException;
import java.time.Duration;
-import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -32,7 +30,6 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import com.nimbusds.jwt.JWT;
-import com.nimbusds.oauth2.sdk.AuthorizationRequest;
import com.nimbusds.oauth2.sdk.PushedAuthorizationRequest;
import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
@@ -47,7 +44,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.primitive.StringSupport;
/**
- * Action that forms outbound token introspection success message. Formed message is set to
+ * Action that forms outbound pushed authorization response message. Formed message is set to
* {@link ProfileRequestContext#getOutboundMessageContext()}.
*/
public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuthAuthorizationResponseAction {
@@ -136,12 +133,19 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
/** {@inheritDoc} */
@Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- final Map<String, Object> claimsSet = buildClaimsSet(profileRequestContext);
+ final Map<String, Object> claimsSet;
+ try {
+ claimsSet = buildClaimsSet(profileRequestContext);
+ } catch (final ParseException e) {
+ log.error("{} Could not parse the claims set from the request object", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return;
+ }
final BiFunction<ProfileRequestContext,Map<String,Object>,URI> serializationStrategy =
requestUriClaimsSetSerializationStrategies.get(requestUriType == null ? "" : requestUriType);
if (serializationStrategy == null) {
- log.error("{} Could not find a seralization strategy for request URI type {}", getLogPrefix(),
+ log.error("{} Could not find a serialization strategy for request URI type {}", getLogPrefix(),
requestUriType);
ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
return;
@@ -168,27 +172,26 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
}
/**
- * Build the claims set by combining the contents of the pushed authorization request form parameters with the ones
- * defined in request object (if included).
+ * Build the claims set by exploiting the contents of the pushed authorization request form parameters or request
+ * object. Finally, if defined, the manipulation strategy is applied against the claims set.
*
* @param profileRequestContext the profile request context to operate on
* @return the claims set
+ * @throws ParseException if the request object cannot be parsed into the claims set
*/
@Nonnull
- protected Map<String,Object> buildClaimsSet(@Nonnull final ProfileRequestContext profileRequestContext) {
+ protected Map<String,Object> buildClaimsSet(@Nonnull final ProfileRequestContext profileRequestContext)
+ throws ParseException {
final OIDCAuthenticationResponseContext oidcContext = getOidcResponseContext();
assert oidcContext != null;
- assert requestMessage != null;
- final Map<String, Object> claimsSet = getRequestClaimsSetWithoutRequestObject(requestMessage);
+ final Map<String, Object> claimsSet;
final JWT requestObject = oidcContext.getRequestObject();
- if (requestObject != null) {
- try {
- claimsSet.putAll(requestObject.getJWTClaimsSet().getClaims());
- log.debug("{} Request object claims set successfully merged", getLogPrefix());
- } catch (ParseException e) {
- log.error("{} Could not parse request object claims set", getLogPrefix());
- }
+ if (requestObject == null) {
+ assert requestMessage != null;
+ claimsSet = requestMessage.getAuthorizationRequest().toJWTClaimsSet().getClaims();
+ } else {
+ claimsSet = requestObject.getJWTClaimsSet().getClaims();
}
if (manipulationStrategy != null) {
@@ -210,29 +213,4 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
assert claimsSet != null;
return claimsSet;
}
-
- /**
- * Get the claims set included in the pushed authorization request form parameters, not including the request
- * object if it exists.
- *
- * @param requestMessage the request message to operate on
- * @return the claims set
- */
- @Nonnull protected Map<String,Object> getRequestClaimsSetWithoutRequestObject(
- @Nonnull final PushedAuthorizationRequest requestMessage) {
- final AuthorizationRequest authorizationRequest = requestMessage.getAuthorizationRequest();
- final Map<String, Object> result = new HashMap<>();
- if (authorizationRequest.specifiesRequestObject()) {
- final Map<String, List<String>> parameters = authorizationRequest.toParameters();
- parameters.remove("request");
- try {
- result.putAll(AuthorizationRequest.parse(parameters).toJWTClaimsSet().getClaims());
- } catch (final com.nimbusds.oauth2.sdk.ParseException e) {
- log.error("{} Could not rebuild authorization request without request object", getLogPrefix());
- }
- } else {
- result.putAll(authorizationRequest.toJWTClaimsSet().getClaims());
- }
- return result;
- }
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidatePushedAuthorizationClientIDMatch.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidatePushedAuthorizationClientIDMatch.java
index 8cabcd0a..eaeaa2db 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidatePushedAuthorizationClientIDMatch.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidatePushedAuthorizationClientIDMatch.java
@@ -79,7 +79,7 @@ public class ValidatePushedAuthorizationClientIDMatch extends AbstractOAuthAuth
final ClientID requestClientId = authorizationRequest.getClientID();
if (!authenticatedClientId.equals(requestClientId)) {
log.warn("{} The client ID used in authentication {} did not match with one in request {}",
- authenticatedClientId, requestClientId, getLogPrefix());
+ getLogPrefix(), authenticatedClientId, requestClientId);
ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
return;
}
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 b1c777e1..16df6fb2 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
@@ -62,8 +62,12 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
/** The claims validator to be applied for validating the plain/unsigned request object. */
@NonnullAfterInit private ClaimsValidator plainClaimsValidator;
+ /** The flag to control whether to use the PAR-endpoint logic for the validation. */
+ private boolean parEndpointLogic;
+
/** Constructor. */
public ValidateRequestObject() {
+ parEndpointLogic = false;
}
/**
@@ -86,6 +90,16 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
plainClaimsValidator = Constraint.isNotNull(validator, "Plain claims validator cannot be null");
}
+ /**
+ * Set the flag to control whether to use the PAR-endpoint logic for the validation.
+ *
+ * @param flag What to set
+ */
+ public void setParEndpointLogic(final boolean flag) {
+ ifInitializedThrowUnmodifiabledComponentException();
+ parEndpointLogic = flag;
+ }
+
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
@@ -173,7 +187,7 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REQUEST_OBJECT);
return;
}
- if (authorizationRequest instanceof AuthenticationRequest authenticationRequest) {
+ if (!parEndpointLogic && authorizationRequest instanceof AuthenticationRequest authenticationRequest) {
final ResponseType requestedType = authenticationRequest.getResponseType();
if (requestedType == null) {
log.error("{} mandatory response_type is missing from the request", getLogPrefix());
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 f8f5669c..8171ed79 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
@@ -150,13 +150,8 @@
</property>
</bean>
- <bean id="RequestObjectSignedCondition" parent="shibboleth.Conditions.Expression"
- c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getRequestObject() instanceof T(com.nimbusds.jwt.SignedJWT)" />
-
<bean id="UseRequestObjectPredicate" class="net.shibboleth.oidc.profile.config.logic.UseRequestObjectPredicate" />
- <bean id="SignRequestObjectPredicate" class="net.shibboleth.oidc.profile.config.logic.SignRequestObjectPredicate" />
-
<bean id="ValidateRequestObjectSignature" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
scope="prototype" c:executionDirection="INBOUND">
<constructor-arg>
@@ -205,55 +200,25 @@
</bean>
</constructor-arg>
<property name="activationCondition">
- <bean parent="shibboleth.Conditions.OR">
- <constructor-arg>
- <ref bean="RequestObjectSignedCondition" />
- </constructor-arg>
- <constructor-arg>
- <bean parent="shibboleth.Conditions.OR">
- <constructor-arg>
- <bean parent="shibboleth.Conditions.AND">
- <constructor-arg>
- <ref bean="UseRequestObjectPredicate" />
- </constructor-arg>
- <constructor-arg>
- <ref bean="SignRequestObjectPredicate" />
- </constructor-arg>
- </bean>
- </constructor-arg>
- <constructor-arg>
- <bean parent="shibboleth.Conditions.AND">
- <constructor-arg>
- <bean id="RequestObjectInvolved" parent="shibboleth.Conditions.Expression"
- c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getRequestObject() != null" />
- </constructor-arg>
- <constructor-arg>
- <bean parent="shibboleth.Conditions.NOT">
- <constructor-arg>
- <bean id="ScopeContainsOpenid" parent="shibboleth.Conditions.Expression" p:customObject-ref="shibboleth.HttpServletRequestSupplier"
- c:expression="#custom.get().getParameter('scope') != null and #custom.get().getParameter('scope').contains('openid')" />
- </constructor-arg>
- </bean>
- </constructor-arg>
- </bean>
- </constructor-arg>
- </bean>
- </constructor-arg>
- </bean>
+ <bean id="RequestObjectInvolved" parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getRequestObject() != null" />
</property>
</bean>
<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:plainClaimsValidator="#{getObject('shibboleth.oidc.par.DefaultPlainRequestObjectClaimsValidation')}"
+ p:signedClaimsValidator="#{getObject('shibboleth.oidc.par.SignedRequestObjectClaimsValidation') ?: getObject('shibboleth.oidc.par.DefaultSignedRequestObjectClaimsValidation')}"
+ p:parEndpointLogic="true"/>
+
+ <bean id="shibboleth.oidc.par.DefaultPlainRequestObjectClaimsValidation"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator"/>
+ </property>
</bean>
- <bean id="shibboleth.oidc.DefaultPlainRequestObjectClaimsValidation"
- class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
- p:claimValidators-ref="PlainClaimsValidators" />
-
- <bean id="shibboleth.oidc.DefaultSignedRequestObjectClaimsValidation"
+ <bean id="shibboleth.oidc.par.DefaultSignedRequestObjectClaimsValidation"
class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
p:claimValidators-ref="SignedClaimsValidators" />
@@ -275,6 +240,16 @@
</property>
</bean>
+ <bean id="ClientIdClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+ p:claimName="client_id">
+ <property name="valueToMatchLookupStrategy">
+ <bean parent="shibboleth.BiFunctions.Expression"
+ c:expression="#custom.apply(#input1.getInboundMessageContext()) == null ? null : #custom.apply(#input1.getInboundMessageContext()).toString()"
+ p:customObject-ref="shibboleth.ClientIDLookupStrategy" />
+ </property>
+ </bean>
+
<bean id="AudienceClaimsValidator"
class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
<property name="audienceLookupStrategy">
@@ -284,35 +259,62 @@
</property>
</bean>
- <util:list id="PlainClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
- <ref bean="ExpiryClaimsValidator" />
- <ref bean="NotBeforeClaimsValidator" />
- </util:list>
-
<util:list id="SignedClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
<ref bean="ExpiryClaimsValidator" />
<ref bean="NotBeforeClaimsValidator" />
<ref bean="IssuerClaimsValidator" />
+ <ref bean="ClientIdClaimsValidator" />
<ref bean="AudienceClaimsValidator" />
</util:list>
+ <bean id="UseOnlyRequestObjectCondition" parent="shibboleth.BiConditions.Expression" c:expression="#input1 != null and #input1.specifiesRequestObject()" />
+
<bean id="ValidateRedirectURI" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateRedirectURI"
scope="prototype"
p:requireRequestedValue="true"
- p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
+ p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}">
+ <property name="redirectURILookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestRedirectURILookupFunction"
+ p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+ </property>
+ </bean>
<bean id="ValidateResponseType" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateResponseType"
scope="prototype"
- p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
+ p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}">
+ <property name="requestedResponseTypeLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestResponseTypeLookupFunction"
+ p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+ </property>
+ </bean>
<bean id="ValidateResponseMode" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateResponseMode"
- scope="prototype" />
+ scope="prototype">
+ <property name="requestedResponseModeLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestResponseModeLookupFunction"
+ p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+ </property>
+ </bean>
<bean id="ValidateCodeChallenge" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateCodeChallenge"
- scope="prototype" />
+ scope="prototype">
+ <property name="codeChallengeLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestCodeChallengeLookupFunction"
+ p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+ </property>
+ <property name="codeChallengeMethodLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestCodeChallengeMethodLookupFunction"
+ p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+ </property>
+ </bean>
<bean id="StoreDPoPProofKeyThumbprint" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.StoreDPoPProofKeyThumbprint"
- scope="prototype" />
+ scope="prototype">
+ <property name="dpopJktLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestDPoPJktLookupFunction"
+ p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+ </property>
+ </bean>
<bean id="FormOutboundMessage"
class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.FormOutbounPushedAuthorizationResponseMessage"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
index 0258494d..82714ccf 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
@@ -18,20 +18,25 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
+import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.opensaml.storage.RevocationCache;
import org.opensaml.storage.StorageService;
+import org.opensaml.storage.impl.MemoryStorageService;
+import org.opensaml.storage.impl.StorageServiceReplayCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.webflow.executor.FlowExecutionResult;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jwt.JWT;
@@ -43,6 +48,7 @@ import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultPushedAuthorizationRequestUriDeserializationFunction;
import net.shibboleth.idp.session.SessionException;
import net.shibboleth.oidc.security.credential.JWKCredential;
import net.shibboleth.shared.collection.Pair;
@@ -72,10 +78,25 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
@Qualifier("shibboleth.oidc.RevocationCache")
private RevocationCache revocationCache;
+ private DefaultPushedAuthorizationRequestUriDeserializationFunction statelessDeserializer;
+
public PushedAuthorizeFlowTest() {
super(FLOW_ID);
}
+ @BeforeMethod
+ public void initDeserializer() throws ComponentInitializationException {
+ final StorageServiceReplayCache replayCache = new StorageServiceReplayCache();
+ final MemoryStorageService storageService = new MemoryStorageService();
+ storageService.setId("mockId");
+ storageService.initialize();
+ replayCache.setStorage(storageService);
+ statelessDeserializer = new DefaultPushedAuthorizationRequestUriDeserializationFunction();
+ statelessDeserializer.setDataSealer(getDataSealer());
+ statelessDeserializer.setObjectMapper(new ObjectMapper());
+ statelessDeserializer.setReplayCache(replayCache);
+ }
+
@AfterMethod
public void tearDown() throws IOException {
removeMetadata(storageService, clientId);
@@ -102,7 +123,6 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
storeMetadata(storageService, clientId, clientSecret, scope);
setBasicAuth(clientId, clientSecret + "X");
final Map<String, String> requestParams = createRequestParameters(clientId);
-
setHttpFormRequest("POST", requestParams);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertErrorCode(result, OAuth2Error.INVALID_CLIENT_CODE);
@@ -279,8 +299,31 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
setBasicAuth(clientId, clientSecret);
final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "profile")
+ .build();
+ final PlainJWT requestObject = new PlainJWT(ro);
+ setHttpFormRequest("POST", createRequestParameters(clientId, "profile", null, requestObject.serialize()));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCPlainRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
.claim("redirect_uri", "https://example.org/cb")
.claim("response_type", "code")
+ .claim("scope", "openid profile")
.build();
final PlainJWT requestObject = new PlainJWT(ro);
setHttpFormRequest("POST", createRequestParameters(clientId, "profile", null, requestObject.serialize()));
@@ -300,32 +343,63 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
@Test
public void testOIDCNoResponseTypeRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
- DataSealerException, ComponentInitializationException {
+ DataSealerException, ComponentInitializationException, JOSEException {
storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
setBasicAuth(clientId, clientSecret);
final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
.claim("redirect_uri", "https://example.org/cb")
+ .claim("scope", "openid profile")
.build();
- final PlainJWT requestObject = new PlainJWT(ro);
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
setHttpFormRequest("POST", createRequestParameters(clientId, "openid profile", null,
requestObject.serialize()));
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
}
+ @Test
+ public void testOIDCResponseTypeInParamsWithRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ setHttpFormRequest("POST", createRequestParameters(clientId, "openid profile", "code",
+ requestObject.serialize()));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
@Test
public void testOIDCResponseTypeInRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
- DataSealerException, ComponentInitializationException {
+ DataSealerException, ComponentInitializationException, JOSEException {
storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
setBasicAuth(clientId, clientSecret);
final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
.claim("response_type", "code")
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("scope", "openid profile")
.build();
- final PlainJWT requestObject = new PlainJWT(ro);
- setHttpFormRequest("POST", createRequestParameters(clientId, "openid profile", null,
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ setHttpFormRequest("POST", createRequestParameters(clientId, null, null,
requestObject.serialize()));
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
- assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ assertSuccessResponse(result, clientId);
+ final PushedAuthorizationSuccessResponse response =
+ parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+ verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
}
@Test
@@ -345,6 +419,7 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
setBasicAuth(clientId, clientSecret);
final JWTClaimsSet ro = new JWTClaimsSet.Builder()
.claim("iss", clientId)
+ .claim("client_id", clientId)
.claim("aud", issuer)
.claim("redirect_uri", "https://example.org/cb")
.build();
@@ -354,6 +429,24 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
}
+ @Test
+ public void testOAuth2ResponseTypeInParamsWithRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ setHttpFormRequest("POST", createRequestParameters(clientId, "profile", "code",
+ requestObject.serialize()));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
@Test
public void testOAuth2ResponseTypeInRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
DataSealerException, ComponentInitializationException, JOSEException {
@@ -361,29 +454,328 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
setBasicAuth(clientId, clientSecret);
final JWTClaimsSet ro = new JWTClaimsSet.Builder()
.claim("iss", clientId)
+ .claim("client_id", clientId)
.claim("aud", issuer)
.claim("redirect_uri", "https://example.org/cb")
.claim("response_type", "code")
.build();
final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
- setHttpFormRequest("POST", createRequestParameters(clientId, "profile", null,
+ setHttpFormRequest("POST", createRequestParameters(clientId, "profile", "code",
requestObject.serialize()));
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
- assertSuccessResponse(result, clientId);
final PushedAuthorizationSuccessResponse response =
parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
}
+ @SuppressWarnings("null")
+ @Test
+ public void testOAuth2ROClaimsSetContents() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("custom2", "custom2Value")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertSuccessResponse(result, clientId);
+ final PushedAuthorizationSuccessResponse response =
+ parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+ final Map<String,Object> object = statelessDeserializer.apply(null, response.getRequestURI());
+ Assert.assertNotNull(object);
+ Assert.assertEquals(object.get("redirect_uri"), "https://example.org/cb");
+ Assert.assertEquals(object.get("response_type"), "code");
+ Assert.assertNull(object.get("scope"));
+ Assert.assertNull(object.get("custom1"));
+ Assert.assertEquals(object.get("custom2"), "custom2Value");
+ }
+
+ @SuppressWarnings("null")
+ @Test
+ public void testOIDCROClaimsSetContents() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .claim("custom2", "custom2Value")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertSuccessResponse(result, clientId);
+ final PushedAuthorizationSuccessResponse response =
+ parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+ final Map<String,Object> object = statelessDeserializer.apply(null, response.getRequestURI());
+ Assert.assertNotNull(object);
+ Assert.assertEquals(object.get("redirect_uri"), "https://example.org/cb");
+ Assert.assertEquals(object.get("response_type"), "code");
+ Assert.assertEquals(object.get("scope"), "openid profile");
+ Assert.assertNull(object.get("custom1"));
+ Assert.assertEquals(object.get("custom2"), "custom2Value");
+ }
+
+ @Test
+ public void testOAuth2ROClientIdMatch() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId + "2", "profile", null,
+ requestObject.serialize());
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOAuth2ROClientIdMatchRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId + "2")
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCROClientIdMatch() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId + "2", "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCROClientIdMatchRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId + "2")
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCROClientIdMissing() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCROIssMatch() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId + "2")
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCROAudMatch() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer + "2")
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCROIssMissing() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCROAudMissing() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCROExpired() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("exp", Instant.now().minusSeconds(300).toEpochMilli() / 1000)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testOIDCRONotYetValid() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+ DataSealerException, ComponentInitializationException, JOSEException {
+ storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+ setBasicAuth(clientId, clientSecret);
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("iss", clientId)
+ .claim("client_id", clientId)
+ .claim("aud", issuer)
+ .claim("nbf", Instant.now().plusSeconds(300).toEpochMilli() / 1000)
+ .claim("redirect_uri", "https://example.org/cb")
+ .claim("response_type", "code")
+ .claim("scope", "openid profile")
+ .build();
+ final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+ final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+ requestObject.serialize());
+ requestParams.put("custom1", "custom1Value");
+ setHttpFormRequest("POST", requestParams);
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
@Factory
public Object[] createRequestObjectSecurityTests() {
return new Object[] {
- new PushedAuthorizeRequestObjectJWSTest(true),
new PushedAuthorizeRequestObjectJWSTest(false),
- new PushedAuthorizeRequestObjectJWETest(false, false),
- new PushedAuthorizeRequestObjectJWETest(false, true),
- new PushedAuthorizeRequestObjectJWETest(true, false),
- new PushedAuthorizeRequestObjectJWETest(true, true)
+ new PushedAuthorizeRequestObjectJWETest(false),
+ new PushedAuthorizeRequestObjectJWETest(true)
};
}
@@ -444,7 +836,9 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
if (responseType != null) {
result.put("response_type", responseType);
}
- result.put("scope", scope);
+ if (scope != null) {
+ result.put("scope", scope);
+ }
result.put("redirect_uri", "https://example.org/cb");
if (requestObject != null) {
result.put("request", requestObject);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeRequestObjectJWETest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeRequestObjectJWETest.java
index a2c0840e..a6b1de98 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeRequestObjectJWETest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeRequestObjectJWETest.java
@@ -49,19 +49,15 @@ public class PushedAuthorizeRequestObjectJWETest extends IssuedEncryptedJWTTest
String defaultClientIdEncryptionEnforced = "mockClientIdRequestObjectEncryptionEnforced";
- public PushedAuthorizeRequestObjectJWETest(final boolean testSigned, final boolean encryptionOptional) {
- super(JWT_FETCHING_TYPE.REQUEST_OBJECT, PushedAuthorizeFlowTest.FLOW_ID, testSigned, encryptionOptional);
+ public PushedAuthorizeRequestObjectJWETest(final boolean encryptionOptional) {
+ super(JWT_FETCHING_TYPE.REQUEST_OBJECT, PushedAuthorizeFlowTest.FLOW_ID, true, encryptionOptional);
}
@Override @Test
public void testJwtEncryption_noSigAlgNorEncSpecified() throws Exception {
// use plain request object
final JWT jwt = obtainRequestObject(null, null, null, null, null, null);
- if (encryptionOptional) {
- assertSuccessRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B, null);
- } else {
- assertErrorRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B, null);
- }
+ assertErrorRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B, null);
}
@Test
@@ -85,23 +81,13 @@ public class PushedAuthorizeRequestObjectJWETest extends IssuedEncryptedJWTTest
@Test
public void testJwtEncryption_noSigAlgNorEncSpecified_encryptedRequestObject() throws Exception {
- if (testSignedJwt) {
- for (final JWSAlgorithm jwsAlgorithm : JWS_ALGORITHMS) {
- for (final JWEAlgorithm jwe : JWE_ALGORITHMS) {
- for (final EncryptionMethod method : ENCRYPTION_METHODS) {
- final JWT jwt = obtainRequestObject(defaultClientSecret64B, getProviderEncryptionKey(jwe),
- getSigningKey(jwsAlgorithm), jwsAlgorithm, jwe, method);
- assertSuccessRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B,
- getSignatureVerificationKey(jwsAlgorithm));
- }
- }
- }
- } else {
+ for (final JWSAlgorithm jwsAlgorithm : JWS_ALGORITHMS) {
for (final JWEAlgorithm jwe : JWE_ALGORITHMS) {
for (final EncryptionMethod method : ENCRYPTION_METHODS) {
- final JWT jwt = obtainRequestObject(defaultClientSecret64B, getProviderEncryptionKey(jwe), null,
- null, jwe, method);
- assertSuccessRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B, null);
+ final JWT jwt = obtainRequestObject(defaultClientSecret64B, getProviderEncryptionKey(jwe),
+ getSigningKey(jwsAlgorithm), jwsAlgorithm, jwe, method);
+ assertSuccessRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B,
+ getSignatureVerificationKey(jwsAlgorithm));
}
}
}
@@ -118,16 +104,11 @@ public class PushedAuthorizeRequestObjectJWETest extends IssuedEncryptedJWTTest
}
protected void assertSecretBasedEncryption(final JWEAlgorithm jweAlgorithm, final EncryptionMethod method) {
- if (testSignedJwt) {
- for (final JWSAlgorithm jwsAlgorithm : JWS_ALGORITHMS) {
- final JWT jwt = obtainRequestObject(defaultClientSecret64B, null, getSigningKey(jwsAlgorithm),
- jwsAlgorithm, jweAlgorithm, method);
- assertSuccessRequestObjectResponse(jwt.serialize(), jwsAlgorithm, jweAlgorithm, method,
- defaultClientSecret64B, getSignatureVerificationKey(jwsAlgorithm));
- }
- } else {
- final JWT jwt = obtainRequestObject(defaultClientSecret, null, null, null, jweAlgorithm, method);
- assertSuccessRequestObjectResponse(jwt.serialize(), null, jweAlgorithm, method, defaultClientSecret, null);
+ for (final JWSAlgorithm jwsAlgorithm : JWS_ALGORITHMS) {
+ final JWT jwt = obtainRequestObject(defaultClientSecret64B, null, getSigningKey(jwsAlgorithm),
+ jwsAlgorithm, jweAlgorithm, method);
+ assertSuccessRequestObjectResponse(jwt.serialize(), jwsAlgorithm, jweAlgorithm, method,
+ defaultClientSecret64B, getSignatureVerificationKey(jwsAlgorithm));
}
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list