[java-idp-oidc] 03/03: JOIDC-222 - Support for OpenID Federation
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Feb 21 14:24:18 UTC 2025
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=822bb80a79bf71f4504009930101323a9faf491b
commit 822bb80a79bf71f4504009930101323a9faf491b
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Feb 21 16:23:55 2025 +0200
JOIDC-222 - Support for OpenID Federation
https://shibboleth.atlassian.net/browse/JOIDC-222
Initial flow tests for automatic registration via PAR and authorize-flows
- The global bean 'shibboleth.oidfed.HttpClient' is mocked so that metadata caches get populated correctly
---
.../plugin/oidc/op/oidfed/TrustChainTestUtil.java | 124 ++++++++++
.../flow/oidfed/AbstractFederationFlowTest.java | 268 +++++++++++++++++++++
.../AuthorizeFlowAutomaticRegistrationTest.java | 227 +++++++++++++++++
...shedAuthorizeFlowAutomaticRegistrationTest.java | 231 ++++++++++++++++++
.../resources/credentials/fed-local-anchor.jwk | 13 +
.../net/shibboleth/idp/module/conf/global.xml | 8 +
.../net/shibboleth/idp/module/conf/oidc.properties | 5 +-
.../idp/module/conf/oidfed-trust-anchors.json | 13 +
.../shibboleth/idp/module/conf/relying-party.xml | 16 ++
9 files changed, 904 insertions(+), 1 deletion(-)
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
new file mode 100644
index 00000000..a76a63e3
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
@@ -0,0 +1,124 @@
+/*
+ * 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.oidfed;
+
+import java.time.Instant;
+import java.util.Date;
+import java.util.List;
+
+import org.testng.Assert;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.ECDSASigner;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+/**
+ * Various utility methods for testing trust chains.
+ */
+public class TrustChainTestUtil {
+
+ public static List<EntityStatement> chainWithIntermediate(final EntityStatement leaf, final String anchorId,
+ final String intermediateId) {
+ try {
+ final RSAKey anchorKey = new RSAKeyGenerator(2048)
+ .keyID("mockTrustAnchorKey")
+ .keyUse(KeyUse.SIGNATURE)
+ .generate();
+ final RSAKey intermediateKey = new RSAKeyGenerator(2048)
+ .keyID("mockIntermediateKey")
+ .keyUse(KeyUse.SIGNATURE)
+ .generate();
+ final EntityStatement trustAnchor = trustAnchor(JWSAlgorithm.RS256, anchorKey, anchorId);
+ final EntityStatement intermediateStatement = entityStatement(JWSAlgorithm.RS256, anchorKey,
+ new JWTClaimsSet.Builder()
+ .subject(intermediateId)
+ .issueTime(new Date())
+ .issuer(anchorId)
+ .build());
+ final EntityStatement leafSubordinateStatement = entityStatement(JWSAlgorithm.RS256, intermediateKey,
+ new JWTClaimsSet.Builder(leaf.getClaimsSet().toJWTClaimsSet())
+ .subject(leaf.getEntityID().getValue())
+ .issueTime(new Date())
+ .issuer(intermediateId)
+ .build());
+ return List.of(leaf, leafSubordinateStatement, intermediateStatement, trustAnchor);
+ } catch (final ParseException | JOSEException e) {
+ Assert.fail("Could not construct trust chain", e);
+ }
+ return null;
+ }
+
+ public static EntityStatement trustAnchor(final JWSAlgorithm algorithm, final JWK jwk, final String entityId) {
+ return entityStatement(algorithm, jwk,
+ new JWTClaimsSet.Builder()
+ .subject(entityId)
+ .issueTime(new Date())
+ .issuer(entityId)
+ .build());
+ }
+
+ public static EntityStatement entityStatement(final JWSAlgorithm algorithm, final JWK jwk,
+ final JWTClaimsSet claimsSet) {
+ try {
+ return EntityStatement.parse(signedJwt(algorithm, jwk, "entity-statement+jwt", claimsSet));
+ } catch (final ParseException e) {
+ Assert.fail("Could not construct entity configuration", e);
+ }
+ return null;
+ }
+
+ public static SignedJWT trustMark(final JWSAlgorithm algorithm, final JWK jwk, final String iss, final String sub,
+ final String id, final Instant exp) {
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder()
+ .subject(sub)
+ .issuer(iss)
+ .claim("id", id)
+ .issueTime(new Date())
+ .expirationTime(exp == null ? null : Date.from(exp));
+ return signedJwt(algorithm, jwk, "trust-mark+jwt", builder.build());
+ }
+
+ public static SignedJWT signedJwt(final JWSAlgorithm algorithm, final JWK jwk, final String type,
+ final JWTClaimsSet claimsSet) {
+ final SignedJWT signedJwt = new SignedJWT(
+ new JWSHeader.Builder(algorithm)
+ .type(new JOSEObjectType(type))
+ .keyID(jwk.getKeyID())
+ .build(),
+ claimsSet);
+ try {
+ if (JWSAlgorithm.Family.RSA.contains(algorithm)) {
+ signedJwt.sign(new RSASSASigner(jwk.toRSAKey()));
+ } else {
+ signedJwt.sign(new ECDSASigner(jwk.toECKey()));
+ }
+ return signedJwt;
+ } catch (final JOSEException e) {
+ Assert.fail("Could not construct signed JWT", e);
+ }
+ return null;
+ }
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
new file mode 100644
index 00000000..ee5a9464
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyPair;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.RSAPublicKey;
+import java.time.Instant;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.annotation.Nonnull;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.mockito.ArgumentMatcher;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.TrustChainTestUtil;
+import net.shibboleth.idp.plugin.oidc.op.profile.flow.AbstractOidcFlowTest;
+import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Abstract unit test for the flows supporting OpenID federation.
+ */
+public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
+
+ final static AtomicInteger clientIndex = new AtomicInteger();
+ final String redirectUri = "https://rp.federation.local/cb";
+ final String clientIdPattern = "https://testrp%s.federation.local";
+ final String anchorId = "https://trust-anchor.federation.local";
+ final String anchorFetchEndpoint = anchorId + "/fetch";
+ String issuer = "https://op.example.org";
+
+ JWK rpKey;
+ JWK leafKey;
+ JWK anchorKey;
+ JWK trustedAnchorKey;
+
+ @Autowired
+ @Qualifier("shibboleth.oidfed.HttpClient")
+ HttpClient federationHttpClient;
+
+ protected AbstractFederationFlowTest(final String flowId) {
+ super(flowId);
+ }
+
+ @BeforeClass
+ public void initKeys() throws NoSuchAlgorithmException {
+ rpKey = initializeNewJwk("RSA", 2048, "mockRpKey");
+ leafKey = initializeNewJwk("RSA", 2048, "mockLeafKey");
+ anchorKey = initializeNewJwk("RSA", 2048, "mockAnchorKey");
+ final BasicJWKCredential localAnchor = loadCredential("/credentials/fed-local-anchor.jwk");
+ trustedAnchorKey = new RSAKey.Builder((RSAPublicKey) localAnchor.getPublicKey())
+ .privateKey(localAnchor.getPrivateKey())
+ .keyID("locallyTrustedAnchorKey")
+ .build();
+ }
+
+ protected JWK initializeNewJwk(final String algorithm, final int size, final String kid)
+ throws NoSuchAlgorithmException {
+ if ("RSA".equals(algorithm)) {
+ final KeyPair keyPair = generateNewKeyPair(algorithm, size);
+ return new RSAKey.Builder((RSAPublicKey) keyPair.getPublic())
+ .privateKey(keyPair.getPrivate())
+ .keyID(kid)
+ .build();
+ }
+ throw new NoSuchAlgorithmException(algorithm);
+ }
+
+ protected JWT plainRequestObject(final Map<String,Object> claims) {
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
+ for (final String claim : claims.keySet()) {
+ builder.claim(claim, claims.get(claim));
+ }
+ return new PlainJWT(builder.build());
+ }
+
+ protected JWT signedRequestObject(final Map<String,Object> claims) {
+ return signedRequestObject(claims, rpKey);
+ }
+
+ protected JWT signedRequestObject(final Map<String,Object> claims, final JWK jwk) {
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
+ for (final String claim : claims.keySet()) {
+ builder.claim(claim, claims.get(claim));
+ }
+ try {
+ final SignedJWT jwt =
+ new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(jwk.getKeyID()).build(),
+ builder.build());
+ final RSASSASigner signer = new RSASSASigner(jwk.toRSAKey());
+ jwt.sign(signer);
+ return jwt;
+ } catch (JOSEException e) {
+ Assert.fail(e.getMessage(), e);
+ }
+ return null;
+ }
+
+ protected String entityConfigurationUrl(final String entityId) {
+ return entityId + "/.well-known/openid-federation";
+ }
+
+ protected String subordinateStatementUrl(final String fetchEndpoint, final String issuer, final String subject) {
+ return fetchEndpoint + "?iss=" + issuer + "&sub=" + subject;
+ }
+
+ protected void mapResponse(final String requestUri, final ClassicHttpResponse classicResponse) throws IOException {
+ when(federationHttpClient.executeOpen(any(), argThat(new RequestUriMatcher(requestUri)), any()))
+ .thenReturn(classicResponse);
+ }
+
+ protected ClassicHttpResponse mockResponse(final String contents)
+ throws UnsupportedOperationException, IOException {
+ return mockResponse(200, "application/entity-statement+jwt", contents);
+ }
+
+ protected ClassicHttpResponse mockResponse(final int code, final String contentType, final String contents)
+ throws UnsupportedOperationException, IOException {
+ final ClassicHttpResponse classicResponse = mock(ClassicHttpResponse.class);
+ when(classicResponse.getCode()).thenReturn(code);
+ final HttpEntity responseEntity = mock(HttpEntity.class);
+ when(responseEntity.getContentType()).thenReturn(contentType);
+ when(responseEntity.getContent()).thenReturn(new ByteArrayInputStream(contents.getBytes()));
+ when(classicResponse.getEntity()).thenReturn(responseEntity);
+ return classicResponse;
+ }
+
+ protected String rpEntityConfiguration(final String clientId) throws URISyntaxException {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ return rpEntityConfiguration(clientId, metadata);
+ }
+
+ protected String rpEntityConfiguration(final String clientId, final OIDCClientMetadata metadata)
+ throws URISyntaxException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(clientId).subject(clientId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("openid_relying_party", metadata.toJSONObject()))
+ .claim("authority_hints", new String[] { anchorId })
+ .build();
+ final EntityStatement rpConfiguration =
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, claimsSet);
+ return rpConfiguration.getSignedStatement().serialize();
+ }
+
+ protected String trustedAnchorConfiguration() {
+ final String anchorId = "https://trust-anchor.federation.local";
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(anchorId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(trustedAnchorKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Map.of("federation_fetch_endpoint",
+ anchorFetchEndpoint)))
+ .build();
+ final EntityStatement anchorConfiguration =
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, claimsSet);
+ return anchorConfiguration.getSignedStatement().serialize();
+ }
+
+ protected String subordinateStatement(final String clientId) {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().issuer(anchorId).subject(clientId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("openid_relying_party", new OIDCClientMetadata().toJSONObject()))
+ .claim("authority_hints", new String[] { anchorId })
+ .build();
+ final EntityStatement rpConfiguration =
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, trustedAnchorKey, claimsSet);
+ return rpConfiguration.getSignedStatement().serialize();
+ }
+
+ protected String uniqueClientId() {
+ return String.format(clientIdPattern, clientIndex.getAndIncrement());
+ }
+
+ protected void configureMockHttpClient(final String clientId) {
+ try {
+ mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration(clientId)));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, anchorId, clientId),
+ mockResponse(subordinateStatement(clientId)));
+ } catch (UnsupportedOperationException | IOException | URISyntaxException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+ }
+
+ protected void configureMockHttpClient(final String clientId, final OIDCClientMetadata metadata) {
+ try {
+ mapResponse(entityConfigurationUrl(clientId), mockResponse(rpEntityConfiguration(clientId, metadata)));
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, anchorId, clientId),
+ mockResponse(subordinateStatement(clientId)));
+ } catch (UnsupportedOperationException | IOException | URISyntaxException e) {
+ Assert.fail("Could not initialize mock HTTP client", e);
+ }
+ }
+
+ protected class RequestUriMatcher implements ArgumentMatcher<ClassicHttpRequest> {
+
+ @Nonnull private final String uri;
+
+ public RequestUriMatcher(final String value) {
+ uri = Constraint.isNotEmpty(value, "URI value cannot be null");
+ }
+
+ @Override
+ public boolean matches(final ClassicHttpRequest match) {
+ try {
+ return match != null && uri.equals(match.getUri().toString());
+ } catch (URISyntaxException e) {
+ }
+ return false;
+ }
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
new file mode 100644
index 00000000..4cdb852f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.flow.AuthorizeFlowTest;
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultPushedAuthorizationRequestUriSerializationFunction;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.security.DataSealerException;
+
+public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFlowTest {
+
+ public AuthorizeFlowAutomaticRegistrationTest() {
+ super(AuthorizeFlowTest.FLOW_ID);
+ }
+
+ @Test
+ public void testWithValidTrustChain_noRequestObject()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final FlowExecutionResult result = launchAuthenticationRequest(clientId, "openid profile");
+ Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+ }
+
+ @Test
+ public void testWithValidTrustChain_plainRequestObject()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", plainRequestObject(Map.of(
+ "iss", clientId,
+ "aud", issuer,
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri)));
+ Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+ }
+
+ @Test
+ public void testWithValidTrustChain_signedRequestObject()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+ "iss", clientId,
+ "aud", issuer,
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri)));
+ 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.assertEquals(unwrapTrustChainFromAuthorizeCode(successResponse), List.of(clientId, anchorId));
+ }
+
+ @Test
+ public void testWithValidTrustChain_leafKeySignedRequestObject()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+ "iss", clientId,
+ "aud", issuer,
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri), leafKey));
+ Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+ }
+
+ @Test
+ public void testWithPar_unresolvableTrustChain()
+ throws Exception {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final URI uri = createParGeneratedRequestUri(Map.of(
+ "client_id", clientId,
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri), List.of(clientId, anchorId + "/notExisting"));
+ final FlowExecutionResult result = launchAuthenticationRequest(clientId, "openid profile", uri);
+ Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+ }
+
+ @Test
+ public void testWithPar_matchingAutoRegisteredTrustChain()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", createParGeneratedRequestUri(Map.of(
+ "client_id", clientId,
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri), List.of(clientId, anchorId)));
+ 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.assertEquals(unwrapTrustChainFromAuthorizeCode(successResponse), List.of(clientId, anchorId));
+ }
+
+ protected URI createParGeneratedRequestUri(final Map<String, Object> parameters,
+ final List<String> trustChain) {
+ 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);
+ }
+ final Map<String, Object> map = new HashMap<>(parameters);
+ map.put(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN, trustChain);
+ return parGenerator.apply(null, map);
+ }
+
+ protected FlowExecutionResult launchAuthenticationRequest(final String clientId, final String scope) {
+ return launchAuthenticationRequest(clientId, scope, (JWT) null);
+ }
+
+ protected FlowExecutionResult launchAuthenticationRequest(final String clientId, final String scope,
+ final JWT requestObject) {
+ setQueryParameters(request, List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", scope),
+ new Pair<>("redirect_uri", redirectUri),
+ new Pair<>("request", requestObject == null ? "" : requestObject.serialize())));
+ request.setMethod("GET");
+
+ setBasicAuth("jdoe", "changeit");
+ initializeThreadLocals();
+
+ return flowExecutor.launchExecution(AuthorizeFlowTest.FLOW_ID, null, externalContext);
+ }
+
+ protected FlowExecutionResult launchAuthenticationRequest(final String clientId, final String scope,
+ final URI requestUri) {
+ setQueryParameters(request, List.of(new Pair<>("client_id", clientId),
+ new Pair<>("response_type", "code"),
+ new Pair<>("scope", scope),
+ new Pair<>("request_uri", "" + requestUri)));
+ request.setMethod("GET");
+
+ setBasicAuth("jdoe", "changeit");
+ initializeThreadLocals();
+
+ return flowExecutor.launchExecution(AuthorizeFlowTest.FLOW_ID, null, externalContext);
+ }
+
+ protected List<String> unwrapTrustChainFromAuthorizeCode(final AuthenticationSuccessResponse successResponse) {
+ Assert.assertNotNull(successResponse.getAuthorizationCode());
+ final String code = successResponse.getAuthorizationCode().getValue();
+ assert code != null;
+ final AuthorizeCodeClaimsSet claims;
+ try {
+ claims = AuthorizeCodeClaimsSet.parse(code, getDataSealer());
+ Assert.assertNotNull(claims.getAutomaticallyRegisteredTrustChain());
+ return claims.getAutomaticallyRegisteredTrustChain();
+ } catch (ParseException | DataSealerException e) {
+ return null;
+ }
+ }
+
+ protected static void setQueryParameters(final MockHttpServletRequest request,
+ final List<Pair<String, String>> pairs) {
+ final StringBuffer query = new StringBuffer();
+ request.removeAllParameters();
+ for (final Pair<String, String> pair : pairs) {
+ final String first = pair.getFirst();
+ assert first != null;
+ request.addParameter(first, pair.getSecond());
+ try {
+ query.append(pair.getFirst() + "=" + URLEncoder.encode(pair.getSecond(), "UTF-8") + "&");
+ } catch (UnsupportedEncodingException e) {
+ Assert.fail(e.getMessage());
+ }
+ }
+ request.setQueryString(query.toString());
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
new file mode 100644
index 00000000..b84a33f8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.security.PublicKey;
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+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;
+import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.flow.PushedAuthorizeFlowTest;
+import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.security.DataSealerException;
+
+public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFlowTest {
+
+ public PushedAuthorizeFlowAutomaticRegistrationTest() {
+ super(PushedAuthorizeFlowTest.FLOW_ID);
+ }
+
+ @Test
+ public void testSuccess() throws Exception {
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId);
+ final SignedJWT jwt = createPrivateKeyJWT(validClaimsSet(clientId, issuer),
+ rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+ final FlowExecutionResult result = launchWithJwtAuthentication(jwt, null,
+ ClientAuthenticationMethod.PRIVATE_KEY_JWT, rpKey.toRSAKey().toPublicKey());
+ assertSuccessResponse(result, clientId);
+ final PushedAuthorizationSuccessResponse response =
+ parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+ verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
+ }
+
+ @Test
+ public void testWithPublicClientWithoutRequestObject() throws Exception {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId, metadata);
+ setHttpFormRequest("POST", createRequestParameters(clientId));
+ final FlowExecutionResult result =
+ flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ @Test
+ public void testWithPublicClientWithSignedRequestObject() throws Exception {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId, metadata);
+ setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
+ "client_id", clientId,
+ "iss", clientId,
+ "aud", issuer,
+ "response_type", "code",
+ "scope", "openid",
+ "redirect_uri", redirectUri)).serialize()));
+ final FlowExecutionResult result =
+ flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+ assertSuccessResponse(result, clientId);
+ final PushedAuthorizationSuccessResponse response =
+ parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+ verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
+ }
+
+ @Test
+ public void testWithPublicClientWithPlainRequestObject() throws Exception {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+ final String clientId = uniqueClientId();
+ configureMockHttpClient(clientId, metadata);
+ setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", plainRequestObject(Map.of(
+ "client_id", clientId,
+ "iss", clientId,
+ "aud", issuer,
+ "response_type", "code",
+ "scope", "openid",
+ "redirect_uri", redirectUri)).serialize()));
+ final FlowExecutionResult result =
+ flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
+ protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri) {
+ verifyAuthorizeEndpoint(clientId, requestUri, null);
+ }
+
+ protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri, final String jkt) {
+ initializeMocks();
+ initializeThreadLocals();
+ setBasicAuth("jdoe", "changeit");
+ request.setMethod("GET");
+
+ setRequestParameters(request, List.of(new Pair<>("client_id", clientId),
+ new Pair<>("request_uri", requestUri)));
+
+ final FlowExecutionResult result = flowExecutor.launchExecution("oidc/authorize", null, externalContext);
+ Assert.assertEquals(result.getOutcome().getId(), END_STATE_ID);
+ if (jkt != null) {
+ final String url = response.getRedirectedUrl();
+ assert url != null;
+ final String code = url.substring(url.indexOf("code=") + 5);
+ assert code != null;
+ try {
+ final AuthorizeCodeClaimsSet claimsSet = AuthorizeCodeClaimsSet.parse(code, getDataSealer());
+ assert claimsSet != null;
+ Assert.assertEquals(claimsSet.getDpopProofJwkThumbprint(), jkt);
+ } catch (ParseException | DataSealerException e) {
+ Assert.fail(e.getMessage());
+ }
+ }
+ }
+ protected FlowExecutionResult launchWithJwtAuthentication(final JWT jwt, final JWSAlgorithm algorithm,
+ final ClientAuthenticationMethod method, final PublicKey publicKey) throws Exception {
+ // use 'iss' claim from JWT as clientId if set, 'sub' otherwise
+ final String iss = jwt.getJWTClaimsSet().getStringClaim("iss");
+ final String clientId = iss == null ? jwt.getJWTClaimsSet().getStringClaim("sub") : iss;
+ final Map<String, String> requestParameters = createRequestParameters(clientId);
+ populateClientAssertionParams(requestParameters, jwt);
+ setHttpFormRequest("POST", requestParameters);
+ 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);
+ }
+
+ protected Map<String,String> createRequestParameters(final String id, final String scope,
+ final String responseType, final String requestObject) {
+ final Map<String,String> result = new HashMap<>();
+ result.put("client_id", id);
+ if (responseType != null) {
+ result.put("response_type", responseType);
+ }
+ if (scope != null) {
+ result.put("scope", scope);
+ }
+ result.put("redirect_uri", redirectUri);
+ if (requestObject != null) {
+ result.put("request", requestObject);
+ }
+ return result;
+ }
+
+ protected static void setRequestParameters(final MockHttpServletRequest request,
+ final List<Pair<String, String>> pairs) {
+ final StringBuffer query = new StringBuffer();
+ for (final Pair<String, String> pair : pairs) {
+ final String first = pair.getFirst();
+ assert first != null;
+ request.addParameter(first, pair.getSecond());
+ try {
+ query.append(pair.getFirst() + "=" + URLEncoder.encode(pair.getSecond(), "UTF-8") + "&");
+ } catch (UnsupportedEncodingException e) {
+ Assert.fail(e.getMessage());
+ }
+ }
+ request.setQueryString(query.toString());
+ }
+
+ protected void assertSuccessResponse(final FlowExecutionResult result, final String id) {
+ final PushedAuthorizationSuccessResponse resp =
+ parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+ Assert.assertNotNull(resp);
+ Assert.assertNotNull(resp.getRequestURI());
+ Assert.assertNotNull(resp.getLifetime());
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/resources/credentials/fed-local-anchor.jwk b/idp-oidc-extension-impl/src/test/resources/credentials/fed-local-anchor.jwk
new file mode 100644
index 00000000..8601a78c
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/credentials/fed-local-anchor.jwk
@@ -0,0 +1,13 @@
+{
+ "alg": "RS256",
+ "kid": "locallyTrustedAnchorKey",
+ "kty": "RSA",
+ "p": "y_ul6_wF0dnPzfK_BW0q5dM2aKRFOekotB6mFmWXE4VY_EVRpPAcHQppBbOUOBMkg_NSmjRK3x8wZYhLeavqfsFNcpGMAMsL1LA_FtPFo1R2B8n-FpcA9uRimjDxlzIQKrbEYM1OGPA7jLCE57fNzta2YM4e1sjP3W7-TfOa7v0",
+ "q": "x--fmMMzN9CRakq-pi6I0ClQq4PXJnJROGzVSjMGKs-YbaFXaWPnF_rHqJunmFavTiiSwCv36kiseEAA8fTl-Ms75MXp7LGo30e0F7JJ3vXDD5MwvapswwoiWJWCgJxCOnWJdWvm_iZApK9xcaM_ngTWQuQEq1JhPq6rEB4wck0",
+ "d": "AnBEysAXveM6Lkt2U4RaRoq30upD1EwX7GBE0xCEyzXHESoZIhriDICL9DcCnH7wz0GHittysTrnzEcOWA37mMRKtMJ9ZX3P4QXhfcxGPVvJoDXuH3hR1rbXsu-b9-aYsGNeO6fZSafqhXBeXYU3q1CJFFjoe72drw3DJ3xwCRdYv3y94YBw09kAQYOA4zAtxrUF3ZhlLH9K4sfFLAEzztJX-OuY3a2UFeNC1FkpNDKovbXU0yrkP_y5jjSPrjI2lBQg4yFN5hM3YM7bvG001E796CEhqRYsnLazNDpRXAIQc_jbs_3YrdrlaWR-abMj1zr0zw44a8JZ5Tvd7imZaQ",
+ "e": "AQAB",
+ "qi": "OlB4Yfvc6MBNsFUPpFGXmuYjVDkgTtcAEG60n1TDsrh4C2ir0-ZyaQg1gV3D3EabD96lnp7IiO3uX6X6e6AJgC2AW052bc1EuRQkhyyXeEYN23u3elF1y2F4eQUbNgfc2ghZfUqx5xhZDWFXbrMC7NaOCn1UOIfBe2kuvqSL8Es",
+ "dp": "ebkddgjaYDOd8cPdgZt3cdXsLd15AenExFdVvR-6W4fDZibnZYly_VFtAl37IMsriyH0NNjnpOWzt6LxhxWzxRgM40U_SmngEXdq7nBJDAImvNcorMpHZQ08Wc7DG_pf811FKo7Y_8C7iGT9qljgk4FFK9dUR89lWzoUvueTmPE",
+ "dq": "UBHQ8pbJ_kJS2iSQ8XCVbff9zJKCKW2CxXwgdxS0FZUJ0G3a2eQeemX-a7HajpG4py5shvWU1YjBOW84ca3II7kQhXAVXKtRnAnVP-Aw4U-_DI-_51VHNVzroFpP5z2s8Eh-Aj5yRboADXQNlJryMVBylltG222kcDv3Wf8dG8k",
+ "n": "n0-NFV06ZDKLo1v8KrSJsQ8bbLEffVJw1F5jGXqrKh_4PpBt9FmyWY3gIA9aK1p1WneMaWRNlM1EObierCr0EdXCQbgpKorrPqxiwyl6cOMIH4fN_9uWGqD2HlyGcjcESrNjZz75tNr_9oegh6fWSMgrxyySpU38ALWUX1ZuNS8A4tj8XdJSbSHqftf7qOdgzuy0yaD5h7NwoBCRPOIY88vOLHkcQ4nYdkk8GLSIf5GgGb7JFiPuFHN7pK---LNnFBifag2wbEZ9nnAcAol4jc2gF7zq2mqhMSlbIVmTRj4Y9wxh3DPbmC8xZ-8nbhPmgi4vlij9JWJGEvfLuXaMGQ"
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
index 707f855f..54624fdf 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
@@ -106,4 +106,12 @@
<bean id="alwaysFalsePolicyOperator" class="net.shibboleth.idp.plugin.oidc.op.profile.flow.AlwaysFalseCustomMetadataPolicyOperator" />
+ <bean id="MockitoMockFactory" class="org.mockito.Mockito" />
+
+ <bean id="shibboleth.oidfed.HttpClient"
+ factory-bean="MockitoMockFactory"
+ factory-method="mock">
+ <constructor-arg value="#{T(org.apache.hc.client5.http.classic.HttpClient)}" />
+ </bean>
+
</beans>
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
index fca79757..dd370e35 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
@@ -20,4 +20,7 @@ idp.oauth2.defaultAllowedAudience = https://rp.example.org
idp.oidc.discovery.resolver.values = CustomConfigurationValues
-idp.oidc.DefaultUnregisteredClientPolicyFile = src/test/resources/net/shibboleth/idp/module/conf/unregistered-policy.json
\ No newline at end of file
+idp.oidc.DefaultUnregisteredClientPolicyFile = src/test/resources/net/shibboleth/idp/module/conf/unregistered-policy.json
+
+idp.oidfed.authorize.automaticRegistrationCondition = shibboleth.Conditions.TRUE
+idp.oidfed.par.automaticRegistrationCondition = shibboleth.Conditions.TRUE
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidfed-trust-anchors.json b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidfed-trust-anchors.json
new file mode 100644
index 00000000..5464750f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidfed-trust-anchors.json
@@ -0,0 +1,13 @@
+{
+ "https://trust-anchor.federation.local": {
+ "keys": [
+ {
+ "alg": "RS256",
+ "kty":"RSA",
+ "e":"AQAB",
+ "kid":"locallyTrustedAnchorKey",
+ "n":"n0-NFV06ZDKLo1v8KrSJsQ8bbLEffVJw1F5jGXqrKh_4PpBt9FmyWY3gIA9aK1p1WneMaWRNlM1EObierCr0EdXCQbgpKorrPqxiwyl6cOMIH4fN_9uWGqD2HlyGcjcESrNjZz75tNr_9oegh6fWSMgrxyySpU38ALWUX1ZuNS8A4tj8XdJSbSHqftf7qOdgzuy0yaD5h7NwoBCRPOIY88vOLHkcQ4nYdkk8GLSIf5GgGb7JFiPuFHN7pK---LNnFBifag2wbEZ9nnAcAol4jc2gF7zq2mqhMSlbIVmTRj4Y9wxh3DPbmC8xZ-8nbhPmgi4vlij9JWJGEvfLuXaMGQ"
+ }
+ ]
+ }
+}
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 e9db5719..83bc574e 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
@@ -77,6 +77,22 @@
</bean>
<util:list id="shibboleth.RelyingPartyOverrides">
+ <bean parent="RelyingPartyByName" c:relyingPartyIds="https://trust-anchor.federation.local">
+ <property name="profileConfigurations">
+ <list>
+ <bean parent="OIDFED.AutomaticRegistration" p:mandatoryTrustMarks=""/>
+ </list>
+ </property>
+ </bean>
+ <bean parent="RelyingPartyByTrustAnchor" c:trustAnchorIds="https://trust-anchor.federation.local">
+ <property name="profileConfigurations">
+ <list>
+ <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"/>
+ </list>
+ </property>
+ </bean>
<bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdFragmentResponseMode">
<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