[java-idp-oidc] branch main updated: JOIDC-200 - Support for OAuth2 Pushed Authorization Requests (PAR)
Henri Mikkonen
henri.mikkonen at iki.fi
Thu May 30 12:52:01 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=edbaddcead4a9e4c9abad556bc18817a7196c247
The following commit(s) were added to refs/heads/main by this push:
new edbaddce JOIDC-200 - Support for OAuth2 Pushed Authorization Requests (PAR)
edbaddce is described below
commit edbaddcead4a9e4c9abad556bc18817a7196c247
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu May 30 15:51:45 2024 +0300
JOIDC-200 - Support for OAuth2 Pushed Authorization Requests (PAR)
https://shibboleth.atlassian.net/browse/JOIDC-200
- Included support for the metadata flag to require pushed authorization request
- In both static and dynamic client registration
- Extended SetRequestObjectToResponseContext to support requiring PAR via profile and metadata
- Tested via flow tests
---
.../impl/SetRequestObjectToResponseContext.java | 40 ++++-
.../impl/AddRemainingClaimsToClientMetadata.java | 7 +-
.../pushed-authorization-beans.xml | 1 +
.../idp/flows/oidc/authorize/authorize-beans.xml | 27 ++-
.../SetRequestObjectToResponseContextTest.java | 1 +
.../oidc/op/profile/flow/AuthorizeFlowTest.java | 187 +++++++++++++++++++++
.../oidc/op/profile/flow/RegistrationFlowTest.java | 30 ++++
.../shibboleth/idp/module/conf/relying-party.xml | 7 +
8 files changed, 293 insertions(+), 7 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
index 5ff7d2b5..f3fece49 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
@@ -84,6 +84,9 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
/** Predicate for enforcing the use of request objects. */
@NonnullAfterInit private Predicate<ProfileRequestContext> requestObjectEnforcedPredicate;
+ /** Predicate for enforcing the use of pushed authorization requests. */
+ @NonnullAfterInit private Predicate<ProfileRequestContext> pushedAuthorizationRequestEnforcedPredicate;
+
/** Object mapper used for pretty-printing JWT contents. */
@NonnullAfterInit private ObjectMapper objectMapper;
@@ -91,6 +94,9 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
@Nonnull private List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>>
pushedAuthorizationRequestUriDeserializers;
+ /** Whether to require pushed authorization request to be used. */
+ private boolean requirePushedAuthorization = false;
+
/**
* Constructor.
*/
@@ -128,6 +134,19 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
"Request object enforced predicate annot be null");
}
+ /**
+ * Set the predicate for enforcing the use of pushed authorization requests.
+ *
+ * @param predicate the predicate for enforcing the use of pushed authorization requests
+ *
+ * @since 4.2.0
+ */
+ public void setPushedAuthorizationRequestEnforcedPredicate(
+ @Nonnull final Predicate<ProfileRequestContext> predicate) {
+ pushedAuthorizationRequestEnforcedPredicate = Constraint.isNotNull(predicate,
+ "Pushed authorization request enforced predicate annot be null");
+ }
+
/**
* Set the object mapper used for pretty-printing JWT contents.
*
@@ -180,6 +199,10 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
if (requestObjectEnforcedPredicate == null) {
throw new ComponentInitializationException("Request object enforced predicate cannot be null");
}
+ if (pushedAuthorizationRequestEnforcedPredicate == null) {
+ throw new ComponentInitializationException(
+ "Pushed authorization request enforced predicate cannot be null");
+ }
if (objectMapper == null) {
throw new ComponentInitializationException("Object mapper cannot be null");
}
@@ -199,11 +222,16 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
return false;
}
+ requirePushedAuthorization = pushedAuthorizationRequestEnforcedPredicate.test(profileRequestContext);
if (!authorizationRequest.specifiesRequestObject()) {
if (requestObjectEnforcedPredicate.test(profileRequestContext)) {
log.warn("{} No request_uri or request by value, even though it's enforced for {}", getLogPrefix(),
authorizationRequest.getClientID().getValue());
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.MISSING_MANDATORY_REQUEST_OBJECT);
+ if (requirePushedAuthorization) {
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.MISSING_MANDATORY_PAR_REQUEST_URI);
+ } else {
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.MISSING_MANDATORY_REQUEST_OBJECT);
+ }
} else {
log.debug("{} No request_uri or request by value, nothing to do", getLogPrefix());
}
@@ -228,7 +256,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
final AuthorizationRequest authorizationRequest = getAuthorizationRequest();
assert authorizationRequest != null;
final JWT requestObject = authorizationRequest.getRequestObject();
- if (requestObject != null) {
+ if (requestObject != null && !requirePushedAuthorization) {
oidcResponseContext.setRequestObject(requestObject);
log.debug("{} Request object {} by value stored to oidc response context", getLogPrefix(),
requestObject.serialize());
@@ -275,7 +303,13 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REQUEST_URI);
return;
}
-
+
+ if (requirePushedAuthorization) {
+ log.warn("{} Pushed authorization request required but not used", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.MISSING_MANDATORY_PAR_REQUEST_URI);
+ return;
+ }
+
final HttpGet httpRequest = new HttpGet(authorizationRequest.getRequestURI());
final HttpClientContext httpContext = buildHttpContext(httpRequest);
try (final ClassicHttpResponse response = httpClient.executeOpen(null, httpRequest, httpContext)) {
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddRemainingClaimsToClientMetadata.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddRemainingClaimsToClientMetadata.java
index 2ef21a9f..d27356c1 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddRemainingClaimsToClientMetadata.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/AddRemainingClaimsToClientMetadata.java
@@ -35,8 +35,9 @@ import net.shibboleth.shared.primitive.LoggerFactory;
/**
* Adds the remaining recognized claims from the input metadata to the output {@link OIDCClientMetadata}. The static
- * set of remaining recognized claims contain only 'dpop_bound_access_tokens'. The metadata policy offers a dynamic
- * way to add any other claims to be recognized and thus included in the output metadata.
+ * set of remaining recognized claims contain 'dpop_bound_access_tokens' and 'require_pushed_authorization_requests'.
+ * The metadata policy offers a dynamic way to add any other claims to be recognized and thus included in the output
+ * metadata.
*/
public class AddRemainingClaimsToClientMetadata extends AbstractOIDCClientMetadataPopulationAction {
@@ -103,6 +104,8 @@ public class AddRemainingClaimsToClientMetadata extends AbstractOIDCClientMetada
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
getOutputMetadata().setDPoPBoundAccessTokens(getInputMetadata().getDPoPBoundAccessTokens());
+ getOutputMetadata().requiresPushedAuthorizationRequests(
+ getInputMetadata().requiresPushedAuthorizationRequests());
final JSONObject input = getInputMetadata().toJSONObject();
final JSONObject initialOutput = getOutputMetadata().toJSONObject();
for (final String claim : input.keySet()) {
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 d9e43abd..4d9c4461 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
@@ -83,6 +83,7 @@
p:httpClient="#{getObject('shibboleth.oidc.NonBrowser.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
p:httpClientSecurityParameters="#{getObject('shibboleth.oidc.NonBrowser.HttpClientSecurityParameters')}"
p:requestObjectEnforcedPredicate-ref="UseRequestObjectPredicate"
+ p:pushedAuthorizationRequestEnforcedPredicate-ref="shibboleth.Conditions.FALSE"
p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}" />
<bean id="RequestObjectEncryptedCondition" parent="shibboleth.Conditions.Expression"
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 8cb6f71c..66bf520b 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
@@ -123,9 +123,20 @@
class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.SetRequestObjectToResponseContext" scope="prototype"
p:httpClient="#{getObject('shibboleth.oidc.NonBrowser.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
p:httpClientSecurityParameters="#{getObject('shibboleth.oidc.NonBrowser.HttpClientSecurityParameters')}"
- p:requestObjectEnforcedPredicate-ref="UseRequestObjectPredicate"
+ p:pushedAuthorizationRequestEnforcedPredicate-ref="RequirePushedAuthorizationRequestPredicate"
p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}"
- p:pushedAuthorizationRequestUriDeserializers-ref="#{'%{idp.oauth2.par.deserializationStrategies:shibboleth.oidc.DefaultPushedAuthorizationRequestUriDeserializers}'.trim()}" />
+ p:pushedAuthorizationRequestUriDeserializers-ref="#{'%{idp.oauth2.par.deserializationStrategies:shibboleth.oidc.DefaultPushedAuthorizationRequestUriDeserializers}'.trim()}">
+ <property name="requestObjectEnforcedPredicate">
+ <bean parent="shibboleth.Conditions.OR">
+ <constructor-arg>
+ <ref bean="UseRequestObjectPredicate" />
+ </constructor-arg>
+ <constructor-arg>
+ <ref bean="RequirePushedAuthorizationRequestPredicate"/>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
<util:list id="shibboleth.oidc.DefaultPushedAuthorizationRequestUriDeserializers">
<bean factory-bean="DefaultStatelessPushedAuthorizationRequestUriDeserializerFactory" factory-method="getBean" />
@@ -222,6 +233,18 @@
<bean id="UseRequestObjectPredicate" class="net.shibboleth.oidc.profile.config.logic.UseRequestObjectPredicate" />
+ <bean id="RequirePushedAuthorizationRequestPredicate" parent="shibboleth.Conditions.OR">
+ <constructor-arg>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.MetadataValueEqualsCondition"
+ p:key="require_pushed_authorization_requests"
+ p:value="#{true}"
+ p:defaultValue="#{false}"/>
+ </constructor-arg>
+ <constructor-arg>
+ <bean class="net.shibboleth.oidc.profile.config.logic.RequirePushedAuthorizationRequestPredicate" />
+ </constructor-arg>
+ </bean>
+
<bean id="SignRequestObjectPredicate" class="net.shibboleth.oidc.profile.config.logic.SignRequestObjectPredicate" />
<bean id="ValidateRequestObjectSignature" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContextTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContextTest.java
index 0270f784..14768c4c 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContextTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContextTest.java
@@ -63,6 +63,7 @@ public class SetRequestObjectToResponseContextTest extends BaseOIDCResponseActio
assert enforceRequestObjects != null;
action.setRequestObjectEnforcedPredicate(enforceRequestObjects);
action.setObjectMapper(new ObjectMapper());
+ action.setPushedAuthorizationRequestEnforcedPredicate(prc -> false);
action.initialize();
}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
index 120aa96f..aff3da2d 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
@@ -16,6 +16,8 @@ package net.shibboleth.idp.plugin.oidc.op.profile.flow;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
@@ -26,6 +28,8 @@ import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import org.opensaml.storage.StorageService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -38,6 +42,7 @@ 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.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWEAlgorithm;
@@ -58,13 +63,17 @@ import com.nimbusds.openid.connect.sdk.OIDCClaimsRequest;
import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSetRequest;
import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultPushedAuthorizationRequestUriSerializationFunction;
import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
import net.shibboleth.idp.session.SessionException;
import net.shibboleth.oidc.profile.core.OidcError;
import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.security.DataSealerException;
+import net.shibboleth.shared.testing.RepositorySupport;
/**
* Tests for the authorize-flow.
@@ -84,6 +93,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
String clientIdQueryResponseMode = "mockClientIdQueryResponseMode";
String clientIdFragmentResponseMode = "mockClientIdFragmentResponseMode";
String clientIdQueryFragmentResponseMode = "mockClientIdQueryFragmentResponseMode";
+ String clientIdRequirePAR = "mockClientIdRequirePAR";
String clientSecret = "mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret";
Scope scope = Scope.parse("openid profile email");
@@ -161,6 +171,182 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
Assert.assertNull(successResponse.getIssuer());
}
+ @Test
+ public void testWithAuthorizationCodeFlow_parRequiredByProfileNotProvided() throws IOException, SessionException {
+ setRequestParameters(List.of(new Pair<>("client_id", clientIdRequirePAR),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("redirect_uri", redirectUri)));
+ request.setMethod("GET");
+ storeMetadata(storageService, clientIdRequirePAR, clientSecret, scope, redirectUri);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ Assert.assertEquals("ErrorView", result.getOutcome().getId());
+ }
+
+ @Test
+ public void testWithAuthorizationCodeFlow_parRequiredByProfileROProvided() throws IOException, SessionException {
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("redirect_uri", redirectUri)
+ .build();
+ final PlainJWT requestObject = new PlainJWT(ro);
+ setRequestParameters(List.of(new Pair<>("client_id", clientIdRequirePAR),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("request", requestObject.serialize()),
+ new Pair<>("redirect_uri", redirectUri)));
+ request.setMethod("GET");
+ storeMetadata(storageService, clientIdRequirePAR, clientSecret, scope, redirectUri);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ Assert.assertEquals("ErrorView", result.getOutcome().getId());
+ }
+
+ @Test
+ public void testWithAuthorizationCodeFlow_parRequiredByProfileUriProvided() throws IOException, SessionException,
+ URISyntaxException {
+ final String clientId = "s6BhdRkqt3";
+ final String uri = RepositorySupport.buildHTTPSResourceURL("java-idp-oidc",
+ "idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/oidc/profile/impl/oidc-authreq.json");
+ setRequestParameters(List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("request_uri", uri)));
+ request.setMethod("GET");
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setRedirectionURIs(Set.of(new URI("https://client.example.org/cb")));
+ metadata.setScope(scope);
+ metadata.setRequestObjectURIs(Set.of(new URI(uri)));
+ storeMetadataObject(storageService, clientId, clientSecret, metadata);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ Assert.assertEquals("ErrorView", result.getOutcome().getId());
+ removeMetadata(storageService, clientId);
+ }
+
+ @Test
+ public void testWithAuthorizationCodeFlow_parRequiredByProfile() throws IOException, SessionException,
+ URISyntaxException {
+ final String clientId = clientIdRequirePAR;
+ setRequestParameters(List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("request_uri", createParGeneratedRequestUri(clientId).toString()),
+ new Pair<>("redirect_uri", redirectUri)));
+ request.setMethod("GET");
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setRedirectionURIs(Set.of(new URI(redirectUri)));
+ metadata.setScope(scope);
+ metadata.requiresPushedAuthorizationRequests(true);
+ storeMetadataObject(storageService, clientId, clientSecret, metadata);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+ final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNull(successResponse.getIDToken());
+ Assert.assertNull(successResponse.getAccessToken());
+ Assert.assertNotNull(successResponse.getAuthorizationCode());
+ Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
+ Assert.assertNull(successResponse.getIssuer());
+ }
+
+ @Test
+ public void testWithAuthorizationCodeFlow_parRequiredByMetadataNotProvided() throws IOException, SessionException,
+ URISyntaxException {
+ setRequestParameters(List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("redirect_uri", redirectUri)));
+ request.setMethod("GET");
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setRedirectionURIs(Set.of(new URI(redirectUri)));
+ metadata.setScope(scope);
+ metadata.requiresPushedAuthorizationRequests(true);
+ storeMetadataObject(storageService, clientId, clientSecret, metadata);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ Assert.assertEquals("ErrorView", result.getOutcome().getId());
+ }
+
+ @Test
+ public void testWithAuthorizationCodeFlow_parRequiredByMetadataROProvided() throws IOException, SessionException,
+ URISyntaxException {
+ final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+ .claim("redirect_uri", redirectUri)
+ .build();
+ final PlainJWT requestObject = new PlainJWT(ro);
+ setRequestParameters(List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("request", requestObject.serialize()),
+ new Pair<>("redirect_uri", redirectUri)));
+ request.setMethod("GET");
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setRedirectionURIs(Set.of(new URI(redirectUri)));
+ metadata.setScope(scope);
+ metadata.requiresPushedAuthorizationRequests(true);
+ storeMetadataObject(storageService, clientId, clientSecret, metadata);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ Assert.assertEquals("ErrorView", result.getOutcome().getId());
+ }
+
+ protected URI createParGeneratedRequestUri(final String clientId) {
+ final DefaultPushedAuthorizationRequestUriSerializationFunction parGenerator =
+ new DefaultPushedAuthorizationRequestUriSerializationFunction();
+ parGenerator.setObjectMapper(new ObjectMapper());
+ parGenerator.setDataSealer(getDataSealer());
+ parGenerator.setIdentifierGeneratorLookupStrategy(prc -> idGenerator);
+ parGenerator.setId("mockPar");
+ try {
+ parGenerator.initialize();
+ } catch (ComponentInitializationException e) {
+ Assert.fail("Could not initialize PAR generator", e);
+ }
+ return parGenerator.apply(null, Map.of("client_id", clientId, "redirect_uri", redirectUri));
+ }
+
+ @Test
+ public void testWithAuthorizationCodeFlow_parRequiredByMetadata() throws IOException, SessionException,
+ URISyntaxException {
+ setRequestParameters(List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", "openid profile"),
+ new Pair<>("request_uri", createParGeneratedRequestUri(clientId).toString()),
+ new Pair<>("redirect_uri", redirectUri)));
+ request.setMethod("GET");
+ final OIDCClientMetadata metadata = buildMetadataSkeleton();
+ metadata.setRedirectionURIs(Set.of(new URI(redirectUri)));
+ metadata.setScope(scope);
+ metadata.requiresPushedAuthorizationRequests(true);
+ storeMetadataObject(storageService, clientId, clientSecret, metadata);
+
+ initializeThreadLocals();
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+ final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNull(successResponse.getIDToken());
+ Assert.assertNull(successResponse.getAccessToken());
+ Assert.assertNotNull(successResponse.getAuthorizationCode());
+ Assert.assertNotNull(getSidFromAuthorizeCodeClaimsSet(successResponse));
+ Assert.assertNull(successResponse.getIssuer());
+ }
+
@Test
public void testWithAuthorizationCodeFlow_defaultResponseModeNotAllowed() throws IOException, SessionException {
setRequestParameters(List.of(new Pair<>("client_id", clientIdFragmentResponseMode),
@@ -2139,6 +2325,7 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
removeMetadata(storageService, clientIdQueryResponseMode);
removeMetadata(storageService, clientIdQueryFragmentResponseMode);
removeMetadata(storageService, clientIdFragmentResponseMode);
+ removeMetadata(storageService, clientIdRequirePAR);
}
}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
index b1aeacd4..1de268fa 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RegistrationFlowTest.java
@@ -198,6 +198,36 @@ public class RegistrationFlowTest extends AbstractOidcFlowTest {
Assert.assertEquals(storedInfo.getOIDCMetadata().getRequestObjectURIs(), Set.of(new URI(requestUri)));
Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
Assert.assertTrue(metadata.getDPoPBoundAccessTokens());
+ Assert.assertFalse(metadata.requiresPushedAuthorizationRequests());
+ }
+
+ @Test
+ public void testUnauthenticated_successWithPARRequired() throws Exception {
+ final String requestUri = "https://client.example.org/rf.txt#qpXaRLh_n93TTR9F252ValdatUQvQiJi5BDub2BeznA";
+ setJsonRequest("POST", "{ \"redirect_uris\":[\"" + redirectUri + "\"], \"request_uris\":[\"" + requestUri + "\"],"
+ + "\"require_pushed_authorization_requests\":true }");
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final OIDCClientInformationResponse parsedResponse =
+ parseSuccessResponse(result, OIDCClientInformationResponse.class);
+ final OIDCClientInformation clientInfo = parsedResponse.getOIDCClientInformation();
+ final OIDCClientMetadata metadata = clientInfo.getOIDCMetadata();
+ final String clientId = clientInfo.getID().getValue();
+ assert clientId != null;
+ assert storageService != null;
+ final StorageRecord<String> storageRecord = storageService.read(BaseStorageServiceClientInformationComponent.CONTEXT_NAME,
+ clientId);
+ assert storageRecord != null;
+ final String record = storageRecord.getValue();
+ Assert.assertNotNull(record);
+ final JSONParser parser = new JSONParser(JSONParser.DEFAULT_PERMISSIVE_MODE);
+ final OIDCClientInformation storedInfo = OIDCClientInformation.parse((JSONObject) parser.parse(record));
+ Assert.assertEquals(storedInfo.getID(), clientInfo.getID());
+ Assert.assertEquals(storedInfo.getSecret(), clientInfo.getSecret());
+ Assert.assertEquals(storedInfo.getOIDCMetadata().getRedirectionURIStrings(), metadata.getRedirectionURIStrings());
+ Assert.assertEquals(storedInfo.getOIDCMetadata().getRequestObjectURIs(), Set.of(new URI(requestUri)));
+ Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
+ Assert.assertTrue(metadata.requiresPushedAuthorizationRequests());
+ Assert.assertFalse(metadata.getDPoPBoundAccessTokens());
}
@Test
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index 94eb2130..c4c44acc 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -83,6 +83,13 @@
</list>
</property>
</bean>
+ <bean parent="RelyingPartyByName" c:relyingPartyIds="#{{'mockClientIdRequirePAR','s6BhdRkqt3'}}">
+ <property name="profileConfigurations">
+ <list>
+ <bean parent="OIDC.SSO" p:requirePushedAuthorizationRequest="true" p:encryptionOptional="true"/>
+ </list>
+ </property>
+ </bean>
<bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdQueryFragmentResponseMode">
<property name="profileConfigurations">
<list>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list