[java-idp-plugin-oidc-op-oidfed] 02/02: Wired new properties to control automatic registration to token and userinfo
Henri Mikkonen
henri.mikkonen at iki.fi
Thu Oct 9 10:36:43 UTC 2025
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch main
in repository java-idp-plugin-oidc-op-oidfed.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-op-oidfed.git;a=commit;h=b6ebb6dedc66183e7e62beafb036e2073582c6a3
commit b6ebb6dedc66183e7e62beafb036e2073582c6a3
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Oct 9 13:36:21 2025 +0300
Wired new properties to control automatic registration to token and userinfo
- idp.oidfed.token.automaticRegistrationCondition and idp.oidfed.userinfo.automaticRegistrationCondition
- Both defaults to shibboleth.Conditions.FALSE
- If condition returns true, the unwrapped claims set (authorization code or access token) must contain the trustchain-claim
- ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN
- Improved testing
---
.../META-INF/net.shibboleth.idp/postconfig.xml | 42 ++++
.../oidfed/metadata-lookup-ext-oidfed-flow.xml | 2 +-
.../flow/oidfed/AbstractFederationFlowTest.java | 18 ++
.../AuthorizeFlowAutomaticRegistrationTest.java | 6 +-
...shedAuthorizeFlowAutomaticRegistrationTest.java | 26 +--
.../oidfed/TokenFlowAutomaticRegistrationTest.java | 211 +++++++++++++++++++++
.../UserInfoFlowAutomaticRegistrationTest.java | 168 ++++++++++++++++
.../net/shibboleth/idp/module/conf/oidc.properties | 2 +
.../shibboleth/idp/module/conf/relying-party.xml | 1 +
9 files changed, 450 insertions(+), 26 deletions(-)
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 7291558..2bae86d 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -457,6 +457,12 @@
<entry key="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2PushedAuthorizationRequestConfiguration).PROFILE_ID}">
<ref bean="shibboleth.oidfed.par.DefaultAutomaticRegistrationCondition"/>
</entry>
+ <entry key="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2TokenConfiguration).PROFILE_ID}">
+ <ref bean="shibboleth.oidfed.token.DefaultAutomaticRegistrationCondition"/>
+ </entry>
+ <entry key="#{T(net.shibboleth.oidc.profile.config.OIDCUserInfoConfiguration).PROFILE_ID}">
+ <ref bean="shibboleth.oidfed.userinfo.DefaultAutomaticRegistrationCondition"/>
+ </entry>
</util:map>
</property>
</bean>
@@ -483,6 +489,24 @@
</constructor-arg>
</bean>
+ <bean id="shibboleth.oidfed.token.DefaultAutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <list>
+ <ref bean="%{idp.oidfed.token.automaticRegistrationCondition:shibboleth.Conditions.FALSE}"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage() instanceof T(com.nimbusds.oauth2.sdk.TokenRequest)" />
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.getOutboundMessageContext() != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)) != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAuthorizationGrantClaimsSet() != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAuthorizationGrantClaimsSet().getClaimsSet().getClaim(T(net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport).KEY_AUTO_REGISTERED_TRUST_CHAIN) != null"/>
+ </list>
+ </constructor-arg>
+ </bean>
+
<bean id="shibboleth.oidfed.par.DefaultAutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
<constructor-arg>
<list>
@@ -515,6 +539,24 @@
</constructor-arg>
</bean>
+ <bean id="shibboleth.oidfed.userinfo.DefaultAutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
+ <constructor-arg>
+ <list>
+ <ref bean="%{idp.oidfed.userinfo.automaticRegistrationCondition:shibboleth.Conditions.FALSE}"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().getMessage() instanceof T(com.nimbusds.openid.connect.sdk.UserInfoRequest)" />
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.getOutboundMessageContext() != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)) != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAuthorizationGrantClaimsSet() != null"/>
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getAuthorizationGrantClaimsSet().getClaimsSet().getClaim(T(net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport).KEY_AUTO_REGISTERED_TRUST_CHAIN) != null"/>
+ </list>
+ </constructor-arg>
+ </bean>
+
<bean class="net.shibboleth.idp.plugin.oidc.op.security.jwt.claims.RequestObjectClaimsValidator">
<constructor-arg>
<bean id="shibboleth.oidfed.DefaultRequestObjectClaimsValidation"
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml
index bbe8d1a..8c5cb3e 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml
@@ -26,7 +26,7 @@
<decision-state id="CheckIfFallbackToLocalResolution">
<if test="FallbackToLocalResolutionCondition.test(opensamlProfileRequestContext)"
- then="ResolveTrustChains" else="proceed" />
+ then="ResolveTrustChains" else="NoTrustChainsResolved" />
</decision-state>
<action-state id="ResolveTrustChains">
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
index baab2b2..fe61a3d 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
@@ -105,6 +105,8 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
JWK trustedAnchorKey;
JWK intermediateKey;
+ String subject = "jdoe";
+
@Autowired
@Qualifier("shibboleth.oidfed.HttpClient")
HttpClient federationHttpClient;
@@ -614,6 +616,22 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
Assert.assertTrue(errorResponse.getErrorObject().getDescription().contains(errorDescription), message);
}
+ protected void populateClientAssertionParams(final Map<String, String> requestParameters,
+ final JWT jwt) {
+ requestParameters.put("client_assertion", jwt.serialize());
+ requestParameters.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
+ }
+
+ protected JWTClaimsSet validJwtAuthenticationClaimsSet(final String clientId, final String audience) {
+ return new JWTClaimsSet.Builder()
+ .subject(clientId)
+ .issuer(clientId)
+ .audience(audience)
+ .expirationTime(Date.from(Instant.now().plusSeconds(600)))
+ .jwtID(idGenerator.generateIdentifier())
+ .build();
+ }
+
protected class RequestUriMatcher implements ArgumentMatcher<ClassicHttpRequest> {
@Nonnull private final String uri;
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
index 12d8aae..8e7de38 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
@@ -543,7 +543,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
setQueryParameters(request, requestParameters);
storeMetadata(storageService, clientId, clientSecret, Scope.parse("openid profile"), redirectUri);
- setBasicAuth("jdoe", "changeit");
+ setBasicAuth(subject, "changeit");
initializeThreadLocals();
final FlowExecutionResult result =
@@ -590,7 +590,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
new Pair<>("request", requestObject == null ? "" : requestObject.serialize())));
request.setMethod("GET");
- setBasicAuth("jdoe", "changeit");
+ setBasicAuth(subject, "changeit");
initializeThreadLocals();
return flowExecutor.launchExecution(AuthorizeFlowTest.FLOW_ID, null, externalContext);
@@ -604,7 +604,7 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
new Pair<>("request_uri", "" + requestUri)));
request.setMethod("GET");
- setBasicAuth("jdoe", "changeit");
+ setBasicAuth(subject, "changeit");
initializeThreadLocals();
return flowExecutor.launchExecution(AuthorizeFlowTest.FLOW_ID, null, externalContext);
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
index 94b6016..2bea9e1 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
@@ -21,7 +21,6 @@ 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;
@@ -40,7 +39,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jwt.JWT;
-import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.OAuth2Error;
import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
@@ -81,7 +79,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
public void testSuccess() throws Exception {
final String clientId = uniqueClientId();
rpConfigureMockHttpClient(clientId);
- final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId, issuer),
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
ClientAuthenticationMethod.PRIVATE_KEY_JWT, rpKey.toRSAKey().toPublicKey());
@@ -98,7 +96,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
metadata.setRedirectionURI(new URI(redirectUri));
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
rpConfigureMockHttpClient(clientId, rpEntityConfigurationUnmatchingKey(clientId, metadata));
- final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId, issuer),
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
ClientAuthenticationMethod.PRIVATE_KEY_JWT, rpKey.toRSAKey().toPublicKey());
@@ -358,7 +356,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
initializeThreadLocals();
final String clientId = uniqueClientId();
rpConfigureMockHttpClient(clientId, vector);
- final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId, issuer),
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS256);
final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
ClientAuthenticationMethod.PRIVATE_KEY_JWT, rpKey.toRSAKey().toPublicKey());
@@ -384,7 +382,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri, final String jkt) {
initializeMocks();
initializeThreadLocals();
- setBasicAuth("jdoe", "changeit");
+ setBasicAuth(subject, "changeit");
request.setMethod("GET");
setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
@@ -417,22 +415,6 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
return flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
}
- protected void populateClientAssertionParams(final Map<String, String> requestParameters,
- final JWT jwt) {
- requestParameters.put("client_assertion", jwt.serialize());
- requestParameters.put("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer");
- }
-
- protected JWTClaimsSet validClaimsSet(final String clientId, final String audience) {
- return new JWTClaimsSet.Builder()
- .subject(clientId)
- .issuer(clientId)
- .audience(audience)
- .expirationTime(Date.from(Instant.now().plusSeconds(600)))
- .jwtID(idGenerator.generateIdentifier())
- .build();
- }
-
protected Map<String,String> createRequestParameters(final String id) {
return createRequestParameters(id, "openid", "code", null);
}
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
new file mode 100644
index 0000000..0c5928b
--- /dev/null
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed;
+
+import java.net.URI;
+import java.security.PublicKey;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.opensaml.storage.StorageService;
+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.Test;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.OAuth2Error;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport;
+import net.shibboleth.idp.plugin.oidc.op.profile.flow.TokenFlowTest;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
+import net.shibboleth.shared.security.impl.SecureRandomIdentifierGenerationStrategy;
+
+public class TokenFlowAutomaticRegistrationTest extends AbstractFederationFlowTest {
+
+ @Autowired
+ @Qualifier("shibboleth.StorageService")
+ StorageService storageService;
+
+ public TokenFlowAutomaticRegistrationTest() {
+ super(TokenFlowTest.FLOW_ID);
+ }
+
+ @Test
+ public void testSuccess() throws Exception {
+ final String clientId = uniqueClientId();
+ rpConfigureMockHttpClient(clientId);
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
+ rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+ final Map<String,String> requestParameters = new HashMap<>(Map.of(
+ "redirect_uri", redirectUri,
+ "grant_type", "authorization_code",
+ "code", buildAuthorizationCode(clientId, List.of(clientId, anchorId)),
+ "client_id", clientId));
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, JWSAlgorithm.RS512,
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT, null, requestParameters);
+ final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
+ Assert.assertNotNull(response.getTokens().getAccessToken());
+ Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+ Assert.assertNotNull(response.getOIDCTokens().getIDToken().getJWTClaimsSet().getClaim("at_hash"));
+ }
+
+ @Test
+ public void testSuccess_resolveApi() throws Exception {
+ final String clientId = uniqueClientId();
+ rpResolveEntityConfigureMockHttpClient(clientId);
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
+ rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+ final Map<String,String> requestParameters = new HashMap<>(Map.of(
+ "redirect_uri", redirectUri,
+ "grant_type", "authorization_code",
+ "code", buildAuthorizationCode(clientId, List.of(clientId, anchorId)),
+ "client_id", clientId));
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, JWSAlgorithm.RS512,
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT, null, requestParameters);
+ final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
+ Assert.assertNotNull(response.getTokens().getAccessToken());
+ Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+ Assert.assertNotNull(response.getOIDCTokens().getIDToken().getJWTClaimsSet().getClaim("at_hash"));
+ }
+
+ @Test
+ public void testFails_resolveApiFails_noFallback() throws Exception {
+ final String clientId = uniqueClientId();
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
+ rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+ final Map<String,String> requestParameters = new HashMap<>(Map.of(
+ "redirect_uri", redirectUri,
+ "grant_type", "authorization_code",
+ "code", buildAuthorizationCode(clientId, List.of(clientId, anchorId)),
+ "client_id", clientId));
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, JWSAlgorithm.RS512,
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT, null, requestParameters);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ assertErrorDescriptionContains(result, "NoTrustChainsResolved");
+ }
+
+ @Test
+ public void testFails_resolveApiFails_fallbackFails() throws Exception {
+ final String clientId = uniqueClientId();
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ request.addHeader(USE_CUSTOM_FAILBACK_TO_LOCAL_CONDITION, "true");
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
+ rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+ final Map<String,String> requestParameters = new HashMap<>(Map.of(
+ "redirect_uri", redirectUri,
+ "grant_type", "authorization_code",
+ "code", buildAuthorizationCode(clientId, List.of(clientId, anchorId)),
+ "client_id", clientId));
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, JWSAlgorithm.RS512,
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT, null, requestParameters);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ assertErrorDescriptionContains(result, "NoTrustChainsResolved");
+ }
+
+ @Test
+ public void testSuccess_resolveApiFails_fallbackSuccess() throws Exception {
+ final String clientId = uniqueClientId();
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ request.addHeader(USE_CUSTOM_FAILBACK_TO_LOCAL_CONDITION, "true");
+ rpConfigureMockHttpClient(clientId);
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
+ rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+ final Map<String,String> requestParameters = new HashMap<>(Map.of(
+ "redirect_uri", redirectUri,
+ "grant_type", "authorization_code",
+ "code", buildAuthorizationCode(clientId, List.of(clientId, anchorId)),
+ "client_id", clientId));
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, JWSAlgorithm.RS512,
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT, null, requestParameters);
+ final OIDCTokenResponse response = parseSuccessResponse(result, OIDCTokenResponse.class);
+ Assert.assertNotNull(response.getTokens().getAccessToken());
+ Assert.assertNotNull(response.getOIDCTokens().getIDToken());
+ Assert.assertNotNull(response.getOIDCTokens().getIDToken().getJWTClaimsSet().getClaim("at_hash"));
+ }
+
+ @Test
+ public void testFails_NonMatchingTrustChain() throws Exception {
+ final String clientId = uniqueClientId();
+ rpConfigureMockHttpClient(clientId);
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
+ rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+ final Map<String,String> requestParameters = new HashMap<>(Map.of(
+ "redirect_uri", redirectUri,
+ "grant_type", "authorization_code",
+ "code", buildAuthorizationCode(clientId, List.of(clientId, uniqueIntermediateId())),
+ "client_id", clientId));
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, JWSAlgorithm.RS512,
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT, null, requestParameters);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ assertErrorDescriptionContains(result, "NoTrustChainsResolved");
+ }
+
+ @Test
+ public void testFails_resolveApi_NonMatchingTrustChain() throws Exception {
+ final String clientId = uniqueClientId();
+ rpResolveEntityConfigureMockHttpClient(clientId);
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
+ rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+ final Map<String,String> requestParameters = new HashMap<>(Map.of(
+ "redirect_uri", redirectUri,
+ "grant_type", "authorization_code",
+ "code", buildAuthorizationCode(clientId, List.of(clientId, uniqueIntermediateId())),
+ "client_id", clientId));
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, JWSAlgorithm.RS512,
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT, null, requestParameters);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ assertErrorDescriptionContains(result, "NoTrustChainsResolved");
+ }
+
+ protected FlowExecutionResult launchWithJwtAuthentication(final JWT jwt, final JWSAlgorithm algorithm,
+ final ClientAuthenticationMethod method, final PublicKey publicKey,
+ final Map<String, String> requestParameters) throws Exception {
+ // use 'iss' claim from JWT as clientId if set, 'sub' otherwise
+ populateClientAssertionParams(requestParameters, jwt);
+ setHttpFormRequest("POST", requestParameters);
+ return flowExecutor.launchExecution(TokenFlowTest.FLOW_ID, null, externalContext);
+ }
+
+ protected String buildAuthorizationCode(final String clientId, final Object trustChain) throws Exception {
+ final AuthorizeCodeClaimsSet.Builder builder = new AuthorizeCodeClaimsSet.Builder();
+ builder.setJWTID(new SecureRandomIdentifierGenerationStrategy())
+ .setClientID(new ClientID(clientId))
+ .setIssuer("https://op.example.org")
+ .setPrincipal(subject)
+ .setSubject("mock")
+ .setIssuedAt(Instant.now())
+ .setExpiresAt(Instant.now().plusSeconds(100))
+ .setAuthenticationTime(Instant.now())
+ .setRedirectURI(new URI(redirectUri))
+ .setScope(Scope.parse("openid profile"))
+ .addCustomClaim(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN, trustChain);
+
+ return builder.build().serialize(getDataSealer());
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
new file mode 100644
index 0000000..c692231
--- /dev/null
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/UserInfoFlowAutomaticRegistrationTest.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.NoSuchAlgorithmException;
+import java.time.Instant;
+import java.util.List;
+
+import org.opensaml.storage.StorageService;
+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.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.OAuth2Error;
+import com.nimbusds.oauth2.sdk.Scope;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.token.BearerTokenError;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport;
+import net.shibboleth.idp.plugin.oidc.op.profile.flow.UserInfoTest;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AccessTokenClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.security.DataSealerException;
+
+public class UserInfoFlowAutomaticRegistrationTest extends AbstractFederationFlowTest {
+
+ @Autowired
+ @Qualifier("shibboleth.StorageService")
+ StorageService storageService;
+
+ public UserInfoFlowAutomaticRegistrationTest() {
+ super(UserInfoTest.FLOW_ID);
+ }
+
+ @BeforeMethod
+ public void removeAuthorizationHeader() throws IOException {
+ request.setMethod("GET");
+ request.removeHeader("Authorization");
+ }
+
+ @Test
+ public void testSuccess() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+ ComponentInitializationException, IOException {
+ final String clientId = uniqueClientId();
+ rpConfigureMockHttpClient(clientId);
+ final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+ final UserInfoSuccessResponse response = parseSuccessResponse(result, UserInfoSuccessResponse.class);
+ Assert.assertEquals(response.getUserInfo().getSubject().getValue(), subject);
+ final UserInfo userInfo = response.getUserInfo();
+ Assert.assertNotNull(userInfo);
+ Assert.assertNull(userInfo.getEmailAddress());
+ Assert.assertNull(userInfo.getNickname());
+ Assert.assertNull(response.getUserInfoJWT());
+ }
+
+ @Test
+ public void testSuccess_resolveApi() throws Exception {
+ final String clientId = uniqueClientId();
+ rpResolveEntityConfigureMockHttpClient(clientId);
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+ final UserInfoSuccessResponse response = parseSuccessResponse(result, UserInfoSuccessResponse.class);
+ Assert.assertEquals(response.getUserInfo().getSubject().getValue(), subject);
+ final UserInfo userInfo = response.getUserInfo();
+ Assert.assertNotNull(userInfo);
+ Assert.assertNull(userInfo.getEmailAddress());
+ Assert.assertNull(userInfo.getNickname());
+ Assert.assertNull(response.getUserInfoJWT());
+ }
+
+ @Test
+ public void testFails_resolveApiFails_noFallback() throws Exception {
+ final String clientId = uniqueClientId();
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ assertErrorDescriptionContains(result, "NoTrustChainsResolved");
+ }
+
+ @Test
+ public void testFails_resolveApiFails_fallbackFails() throws Exception {
+ final String clientId = uniqueClientId();
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ request.addHeader(USE_CUSTOM_FAILBACK_TO_LOCAL_CONDITION, "true");
+ final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ assertErrorDescriptionContains(result, "NoTrustChainsResolved");
+ }
+
+ @Test
+ public void testSuccess_resolveApiFails_fallbackSuccess() throws Exception {
+ final String clientId = uniqueClientId();
+ request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
+ request.addHeader(USE_CUSTOM_FAILBACK_TO_LOCAL_CONDITION, "true");
+ rpConfigureMockHttpClient(clientId);
+ final BearerAccessToken token = buildToken(clientId, List.of(clientId, anchorId));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+ final UserInfoSuccessResponse response = parseSuccessResponse(result, UserInfoSuccessResponse.class);
+ Assert.assertEquals(response.getUserInfo().getSubject().getValue(), subject);
+ final UserInfo userInfo = response.getUserInfo();
+ Assert.assertNotNull(userInfo);
+ Assert.assertNull(userInfo.getEmailAddress());
+ Assert.assertNull(userInfo.getNickname());
+ Assert.assertNull(response.getUserInfoJWT());
+ }
+
+ @Test
+ public void testFaoös_nonMatchingTrustChain() throws Exception {
+ final String clientId = uniqueClientId();
+ rpConfigureMockHttpClient(clientId);
+ final BearerAccessToken token = buildToken(clientId, List.of(clientId, uniqueIntermediateId()));
+ request.addHeader("Authorization", token.toAuthorizationHeader());
+ final FlowExecutionResult result = flowExecutor.launchExecution(UserInfoTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, BearerTokenError.INVALID_REQUEST.getCode());
+ assertErrorDescriptionContains(result, "NoTrustChainsResolved");
+ }
+
+ protected BearerAccessToken buildToken(final String clientId, final Object trustChain)
+ throws URISyntaxException, NoSuchAlgorithmException, DataSealerException, ComponentInitializationException {
+ final TokenClaimsSet claims = new AccessTokenClaimsSet.Builder()
+ .setJWTID(idGenerator.generateIdentifier())
+ .setClientID(new ClientID(clientId))
+ .setIssuer("https://op.example.org")
+ .setPrincipal(subject)
+ .setSubject(subject)
+ .setIssuedAt(Instant.now())
+ .setExpiresAt(Instant.now().plusSeconds(30))
+ .setAuthenticationTime(Instant.now())
+ .setRedirectURI(new URI("https://example.org/cb"))
+ .setScope(Scope.parse("openid profile"))
+ .setRootTokenIdentifier(idGenerator.generateIdentifier())
+ .addCustomClaim(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN, trustChain)
+ .build();
+ return new BearerAccessToken(claims.serialize(getDataSealer()));
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
index d663f4f..1ac53f3 100644
--- a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
+++ b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
@@ -35,7 +35,9 @@ idp.authn.Password.supportedPrincipals = \
idp.authn.OAuth2Client.audit.format = %a|%T|%SP|%I|%s|%AF|%CV|%u|%tu|%AR|%UA|%iss|%sub|%exp|%iat|%typ
idp.oidfed.authorize.automaticRegistrationCondition = shibboleth.Conditions.TRUE
+idp.oidfed.token.automaticRegistrationCondition = shibboleth.Conditions.TRUE
idp.oidfed.par.automaticRegistrationCondition = shibboleth.Conditions.TRUE
+idp.oidfed.userinfo.automaticRegistrationCondition = shibboleth.Conditions.TRUE
idp.oidfed.configuration.resolver.values = CustomEntityConfigurationValues
idp.oidfed.configuration.MetadataSkaletonFile = src/test/resources/net/shibboleth/idp/module/conf/oidfed-entity-configuration-metadata.json
idp.oidfed.trustchain.resolver.useResolverApiCondition = HeaderUseResolveApiCondition
diff --git a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index 339ce76..a6e7cd7 100644
--- a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -92,6 +92,7 @@
<ref bean="OIDC.SSO.MDDriven" />
<bean parent="OAUTH2.Token.MDDriven" p:tokenEndpointAuthMethods="client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt,none"/>
<bean parent="OAUTH2.PAR.MDDriven" p:tokenEndpointAuthMethods="client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt,none"/>
+ <ref bean="OIDC.UserInfo.MDDriven" />
</list>
</property>
</bean>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list