[java-plugin-shibd-oidc] branch main updated: Update initiator/oidc flow
Phil Smart
philip.smart at jisc.ac.uk
Fri Sep 5 13:46:10 UTC 2025
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-plugin-shibd-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-plugin-shibd-oidc.git;a=commit;h=bc6635c597c574862166b53f477870681663e0c2
The following commit(s) were added to refs/heads/main by this push:
new bc6635c Update initiator/oidc flow
bc6635c is described below
commit bc6635c597c574862166b53f477870681663e0c2
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Sep 5 14:46:08 2025 +0100
Update initiator/oidc flow
- Build flow to the end, encoding an appropriate
authorization/authentication request
- Copy in more RP classes (needs to be reviewed)
- Improve flow tests, making the DDF input more appropriate for OIDC,
add checks the DDF output is correct.
- Configuration of the request is only via profile properties at the
moment, needs input from the DDF.
---
.../logic/RequestObjectRequiredAndSupported.java | 100 ++++++
...WTClaimsSetFromRequestObjectLookupFunction.java | 86 +++++
.../PayloadFromRequestObjectLookupFunction.java | 88 ++++++
.../navigate/RequestObjectTokenUpdateStrategy.java | 76 +++++
.../META-INF/net.shibboleth.idp/postconfig.xml | 22 ++
.../idp/flows/sp/initiator/oidc/oidc-beans.xml | 161 ++++++++++
.../idp/flows/sp/initiator/oidc/oidc-flow.xml | 44 ++-
.../shibboleth/idp/module/conf/sp/oidc.properties | 6 +-
.../sp/oidc/flows/OIDCAuthenticationFlowTest.java | 127 +++++++-
...DCEnvironmentApplicationContextInitializer.java | 2 +-
...roviderMetadataFileBasedCredentialResolver.java | 105 +++++++
.../test/resources/logback-webauthn-flow-test.xml | 25 --
.../resources/metadata/openid-configuration.json | 4 +
.../idp/module/conf/sp/oidc-test-agents.xml | 55 +++-
.../idp/module/conf/sp/oidc-test.properties} | 7 +-
.../credentials/op/global-provider-test-jwks.json | 14 +
.../net/shibboleth/sp/oidc-test-beans.xml | 7 +
.../messaging/impl/AddRequestedClaimsHandler.java | 2 +-
...tObjectSupportedSignatureSigningAlgorithms.java | 40 +++
.../sp/oidc/profile/impl/BuildRequestObject.java | 349 +++++++++++++++++++++
20 files changed, 1256 insertions(+), 64 deletions(-)
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/config/logic/RequestObjectRequiredAndSupported.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/config/logic/RequestObjectRequiredAndSupported.java
new file mode 100644
index 0000000..9ea19de
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/config/logic/RequestObjectRequiredAndSupported.java
@@ -0,0 +1,100 @@
+/*
+ * 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.sp.oidc.config.logic;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.config.OIDCAuthenticationRelyingPartyProfileConfiguration;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.logic.AbstractRelyingPartyPredicate;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A predicate that tests whether a request object should be built based on what is configured in the profile
+ * configuration and whether the OP supports it.
+ */
+public class RequestObjectRequiredAndSupported extends AbstractRelyingPartyPredicate {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(RequestObjectRequiredAndSupported.class);
+
+ /** Strategy that will return {@link OIDCProviderMetadata}. */
+ @Nonnull private
+ Function<ProfileRequestContext, OIDCProviderMetadataContext> oidcProviderMetadataContextLookupStrategy;
+
+ /** Constructor.*/
+ public RequestObjectRequiredAndSupported() {
+ oidcProviderMetadataContextLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class)
+ .compose(new ChildContextLookup<>(OIDCPeerEntityContext.class))
+ .compose(new InboundMessageContextLookup());
+ }
+
+ /**
+ * Set the lookup strategy to use to locate the {@link OIDCProviderMetadataContext}.
+ *
+ * @param strategy lookup function to use
+ */
+ public void setOidcProviderMetadataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,OIDCProviderMetadataContext> strategy) {
+
+ oidcProviderMetadataContextLookupStrategy =
+ Constraint.isNotNull(strategy,
+ "OidcProviderMetadataContextLookupStrategy lookup strategy cannot be null");
+ }
+
+ @Override
+ public boolean test(final ProfileRequestContext input) {
+
+ boolean requestObjectRequestedFromConfig = false;
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null && rpc.getProfileConfig() instanceof final OIDCAuthenticationRelyingPartyProfileConfiguration rpConfig) {
+ requestObjectRequestedFromConfig = rpConfig.isUseRequestObject(input);
+ }
+
+ final OIDCProviderMetadataContext metadata = oidcProviderMetadataContextLookupStrategy.apply(input);
+ final OIDCProviderMetadata providerInformation =
+ metadata != null ? metadata.getProviderInformation() : null;
+ if (providerInformation == null) {
+ // Should not happen at the time this predicate is called.
+ log.warn("OIDC Provider Metadata is not available, can not determine if request object is supported, "
+ + "will not build request object");
+ return false;
+ }
+
+ final boolean isSupportedByOP = providerInformation.supportsRequestParam();
+
+ final boolean requestedAndSupport =
+ requestObjectRequestedFromConfig && isSupportedByOP;
+
+ log.debug("Authentication RequestObject was enabled '{}', is supported by the OP '{}', "
+ + "will be used '{}'", requestObjectRequestedFromConfig, isSupportedByOP, requestedAndSupport);
+
+ return true;//requestedAndSupport;
+
+ }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/JWTClaimsSetFromRequestObjectLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/JWTClaimsSetFromRequestObjectLookupFunction.java
new file mode 100644
index 0000000..6a81d43
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/JWTClaimsSetFromRequestObjectLookupFunction.java
@@ -0,0 +1,86 @@
+/*
+ * 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.sp.oidc.context.navigate;
+
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Extract the {@link Payload} from the Request Object inside the {@link OIDCAuthenticationRequest}.
+ * The Payload must either be signed, or plain. The claims will not be available if the payload is still encrypted.
+ */
+public class JWTClaimsSetFromRequestObjectLookupFunction implements Function<MessageContext, JWTClaimsSet>{
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(JWTClaimsSetFromRequestObjectLookupFunction.class);
+
+ /** Strategy used to locate the {@link OIDCAuthenticationRequest} to sign. */
+ @Nonnull private Function<MessageContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
+
+ /** Constructor.*/
+ public JWTClaimsSetFromRequestObjectLookupFunction() {
+ authenticationRequestLookupStrategy = mc -> {
+ if (mc.getMessage() instanceof final OIDCAuthenticationRequest request) {
+ return request;
+ }
+ return null;
+ };
+ }
+
+ /**
+ * Set the strategy used to locate the {@link OIDCAuthenticationRequest} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthenticationRequestLookupStrategy(
+ @Nonnull final Function<MessageContext, OIDCAuthenticationRequest> strategy) {
+ authenticationRequestLookupStrategy =
+ Constraint.isNotNull(strategy, "AuthenticationRequestLookupStrategy lookup strategy cannot be null");
+ }
+
+ @Override
+ @Nullable public JWTClaimsSet apply(@Nullable final MessageContext messageContext) {
+ if (messageContext == null) {
+ return null;
+ }
+ final OIDCAuthenticationRequest authnRequest = authenticationRequestLookupStrategy.apply(messageContext);
+ final JWT requestObject = authnRequest != null ? authnRequest.getRequestObject() : null;
+ if (requestObject == null) {
+ return null;
+ }
+
+ try {
+ return requestObject.getJWTClaimsSet();
+ } catch (final ParseException e) {
+ log.debug("Error parsing JWT Claims Set of the RequestObject", e);
+ return null;
+ }
+ }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/PayloadFromRequestObjectLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/PayloadFromRequestObjectLookupFunction.java
new file mode 100644
index 0000000..bd5e1ca
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/PayloadFromRequestObjectLookupFunction.java
@@ -0,0 +1,88 @@
+/*
+ * 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.sp.oidc.context.navigate;
+
+import java.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jose.Payload;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Extract the {@link Payload} from the Request Object inside the {@link OIDCAuthenticationRequest}.
+ * The Payload must either be signed, or plain. The payload will not be available if still encrypted.
+ */
+public class PayloadFromRequestObjectLookupFunction implements Function<MessageContext, Payload>{
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(PayloadFromRequestObjectLookupFunction.class);
+
+ /** Strategy used to locate the {@link OIDCAuthenticationRequest} to sign. */
+ @Nonnull private Function<MessageContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
+
+ /** Constructor.*/
+ public PayloadFromRequestObjectLookupFunction() {
+ authenticationRequestLookupStrategy = mc -> {
+ if (mc.getMessage() instanceof final OIDCAuthenticationRequest request) {
+ return request;
+ }
+ return null;
+ };
+ }
+
+ /**
+ * Set the strategy used to locate the {@link OIDCAuthenticationRequest} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthenticationRequestLookupStrategy(
+ @Nonnull final Function<MessageContext, OIDCAuthenticationRequest> strategy) {
+ authenticationRequestLookupStrategy =
+ Constraint.isNotNull(strategy, "AuthenticationRequestLookupStrategy lookup strategy cannot be null");
+ }
+
+ @Override
+ @Nullable public Payload apply(@Nullable final MessageContext messageContext) {
+ if (messageContext == null) {
+ return null;
+ }
+ final OIDCAuthenticationRequest authnRequest = authenticationRequestLookupStrategy.apply(messageContext);
+ final JWT requestObject = authnRequest != null ? authnRequest.getRequestObject() : null;
+ if (requestObject instanceof final SignedJWT signedJwt) {
+ return new Payload(signedJwt);
+ } else if (requestObject instanceof PlainJWT) {
+ try {
+ return new Payload(requestObject.getJWTClaimsSet().getClaims());
+ } catch (final ParseException e) {
+ log.error("Unable to convert plaintext JWT to claims set", e);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/RequestObjectTokenUpdateStrategy.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/RequestObjectTokenUpdateStrategy.java
new file mode 100644
index 0000000..902e612
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/RequestObjectTokenUpdateStrategy.java
@@ -0,0 +1,76 @@
+/*
+ * 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.sp.oidc.context.navigate;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWT;
+
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** Consumer that adds the {@link JWT} back to the Request Object in the {@link OIDCAuthenticationRequest}.*/
+public class RequestObjectTokenUpdateStrategy implements BiConsumer<JWT, MessageContext> {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(RequestObjectTokenUpdateStrategy.class);
+
+ /** Strategy used to locate the {@link OIDCAuthenticationRequest} to sign. */
+ @Nonnull private Function<MessageContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
+
+ /** Constructor.*/
+ public RequestObjectTokenUpdateStrategy() {
+ authenticationRequestLookupStrategy = mc -> {
+ if (mc.getMessage() instanceof final OIDCAuthenticationRequest request) {
+ return request;
+ }
+ return null;
+ };
+ }
+
+ /**
+ * Set the strategy used to locate the {@link OIDCAuthenticationRequest} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthenticationRequestLookupStrategy(
+ @Nonnull final Function<MessageContext, OIDCAuthenticationRequest> strategy) {
+ authenticationRequestLookupStrategy =
+ Constraint.isNotNull(strategy, "AuthenticationRequestLookupStrategy lookup strategy cannot be null");
+ }
+
+ @Override
+ public void accept(@Nullable final JWT jwt, @Nullable final MessageContext messageContext) {
+ if (messageContext == null) {
+ return;
+ }
+ final OIDCAuthenticationRequest authnRequest = authenticationRequestLookupStrategy.apply(messageContext);
+ if (authnRequest != null) {
+ authnRequest.setRequestObject(jwt);
+ } else {
+ log.warn("Unable to set JWT on OIDC authentication request, request was null");
+ }
+ }
+
+}
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index a6eb653..83b75b0 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -12,6 +12,28 @@
http://www.springframework.org/schema/integration/ip https://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd"
default-init-method="initialize" default-destroy-method="destroy">
+
+ <!-- Functions use by the flow and global beans TODO: IS there a better place for these -->
+
+ <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContext"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext) }" />
+
+ <bean id="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound" parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <ref bean="shibboleth.ChildLookup.OIDCProviderMetadataContext" />
+ </constructor-arg>
+ <constructor-arg name="f">
+ <ref bean="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.ChildLookup.OIDCPeerEntityFromOutbound" parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookup.OIDCPeerEntityContext" c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+
+ <bean id="shibboleth.ChildLookup.OIDCPeerEntityContext"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext) }" />
<!-- OpenID Provider information resolver service beans. -->
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
index 7fae032..60891e9 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml
@@ -126,6 +126,167 @@
p:callbackServletPath="sp/callback"
p:allowedOrigins="%{sp.oidc.redirecturl.allowedOrigins:}"
class="net.shibboleth.sp.oidc.profile.impl.DefaultRedirectUriCreationFunction" />
+
+ <bean id="RequestObjectRequiredAndSupportedPredicate" scope="prototype"
+ class="net.shibboleth.sp.oidc.config.logic.RequestObjectRequiredAndSupported" />
+
+
+ <bean id="PopulateRequestObjectSignatureSigningParameters" scope="prototype"
+ class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters"
+ c:strategy-ref="shibboleth.MessageContextLookup.Inbound" p:noResultIsError="true"
+ p:configurationLookupStrategy-ref="RequestObjectSignatureSigningConfigurationLookup"
+ p:providerMetadataContextLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound"
+ p:signatureSigningParametersResolver-ref="RequestObjectSignatureSigningParametersResolver">
+ <property name="activationCondition">
+ <bean id="SignRequestObjectProxyCondition"
+ class="net.shibboleth.oidc.profile.config.logic.SignRequestObjectPredicate"
+ p:relyingPartyContextLookupStrategy-ref="shibboleth.ChildLookup.RelyingParty" />
+ </property>
+ </bean>
+
+ <bean id="RequestObjectSignatureSigningParametersResolver" scope="prototype"
+ class="net.shibboleth.oidc.security.jose.impl.RelyingPartySigningParametersResolver"
+ p:providerMetadataAlgorithmLookupStrategy-ref="RequestObjectSupportedSignatureSigningAlgorithms" />
+
+ <bean id="RequestObjectSupportedSignatureSigningAlgorithms" scope="prototype"
+ class="net.shibboleth.sp.oidc.metadata.impl.RequestObjectSupportedSignatureSigningAlgorithms" />
+
+
+ <bean id="RequestObjectSignatureSigningConfigurationLookup" lazy-init="true" scope="prototype"
+ class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction" />
+
+
+ <!-- if the activation condition succeeds, encryption is not optional -->
+ <bean id="PopulateRequestObjectEncryptionParameters"
+ class="net.shibboleth.oidc.profile.impl.PopulateJWTEncryptionParameters" scope="prototype"
+ p:encryptionOptional="false"
+ p:forFriendlyName="Request Object"
+ p:configurationLookupStrategy-ref="RequestObjectEncryptionConfigurationLookup"
+ p:providerMetadataContextLookupStrategy-ref="shibboleth.ChildLookup.OIDCProviderMetadataContextFromOutbound"
+ p:encryptionParametersResolver-ref="EncryptionParametersResolver">
+ <property name="activationCondition">
+ <bean id="EncryptRequestObjectCondition"
+ class="net.shibboleth.oidc.profile.config.logic.EncryptRequestObjectPredicate"
+ p:relyingPartyContextLookupStrategy-ref="shibboleth.ChildLookup.RelyingParty" />
+ </property>
+
+ </bean>
+
+ <bean id="EncryptionParametersResolver" scope="prototype"
+ class="net.shibboleth.oidc.security.jose.impl.DefaultEncryptionParametersResolver">
+ <property name="keyTransportEncryptionAlgorithmsLookupStrategy">
+ <bean
+ class="net.shibboleth.oidc.security.jose.impl.ProviderMetadataKeyTransportEncryptionAlgorithmsLookupStrategy">
+ <constructor-arg>
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ProviderMetadataStringValuesLookupFunction"
+ c:keyName="request_object_encryption_alg_values_supported"/>
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="dataEncryptionAlgorithmsLookupStrategy">
+ <bean class="net.shibboleth.oidc.security.jose.impl.ProviderMetadataDataEncryptionAlgorithmsLookupStrategy">
+ <constructor-arg>
+ <bean
+ class="net.shibboleth.oidc.profile.config.navigate.ProviderMetadataStringValuesLookupFunction"
+ c:keyName="request_object_encryption_enc_values_supported"/>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="RequestObjectEncryptionConfigurationLookup" lazy-init="true" scope="prototype"
+ class="net.shibboleth.oidc.profile.config.navigate.JWTEncryptionConfigurationLookupFunction" />
+
+ <bean id="BuildRequestObject" class="net.shibboleth.sp.oidc.profile.impl.BuildRequestObject"
+ scope="prototype"
+ p:claimsSetIsValidPredicate="#{getObject('shibboleth.oidc.RequestObjectClaimsSetIsValidPredicate')}"
+ p:requestObjectToBeSignedPredicate-ref="SignRequestObjectCondition"
+ p:customClaimsStrategy="#{getObject('shibboleth.oidc.CustomRequestObjectClaimsStrategy')}"/>
+
+ <bean id="SignRequestObjectCondition" scope="prototype"
+ class="net.shibboleth.oidc.profile.config.logic.SignRequestObjectPredicate"
+ p:relyingPartyContextLookupStrategy-ref="shibboleth.ChildLookup.RelyingParty" />
+
+ <bean id="HandleOutboundMessage"
+ class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor" scope="prototype"
+ c:messageHandler-ref="PreEncodeMessageHandler"
+ c:executionDirection="OUTBOUND">
+ <property name="errorEvent">
+ <util:constant static-field="org.opensaml.profile.action.EventIds.MESSAGE_PROC_ERROR" />
+ </property>
+ </bean>
+
+ <!-- TODO Might not need all of these in the preencode step if we can add the state before...but signing and encrypting the RO still might work here -->
+ <bean id="PreEncodeMessageHandler" class="org.opensaml.messaging.handler.impl.BasicMessageHandlerChain"
+ scope="prototype">
+ <property name="handlers">
+ <list>
+ <bean id="AddState" class="net.shibboleth.sp.oidc.messaging.impl.AddStateHandler"
+ scope="prototype"
+ p:stateGenerationStrategy="#{getObject('shibboleth.authn.oidc.rp.StateGenerationStrategy')}" />
+
+ <bean id="BuildPlainRequestObjectJWT"
+ class="net.shibboleth.sp.oidc.messaging.impl.BuildPlainRequestObjectJWT"
+ scope="prototype" />
+
+ <bean id="SignRequestObject" class="net.shibboleth.oidc.security.impl.SignJWTHandler"
+ scope="prototype" p:logName="RequestObject">
+ <property name="claimsToSignLookupStrategy">
+ <bean
+ class="net.shibboleth.sp.oidc.context.navigate.JWTClaimsSetFromRequestObjectLookupFunction" />
+ </property>
+ <property name="jwtUpdateConsumer">
+ <bean
+ class="net.shibboleth.sp.oidc.context.navigate.RequestObjectTokenUpdateStrategy" />
+ </property>
+ </bean>
+
+ <bean id="EncryptRequestObject"
+ class="net.shibboleth.oidc.security.impl.EncryptJWTHandler" scope="prototype"
+ p:logName="RequestObject">
+ <property name="payloadToEncryptLookupStrategy">
+ <bean
+ class="net.shibboleth.sp.oidc.context.navigate.PayloadFromRequestObjectLookupFunction" />
+ </property>
+ <property name="jwtUpdateConsumer">
+ <bean
+ class="net.shibboleth.sp.oidc.context.navigate.RequestObjectTokenUpdateStrategy" />
+ </property>
+ </bean>
+ <bean id="SetAuthenticationRequestTime"
+ class="net.shibboleth.sp.oidc.messaging.impl.SetAuthenticationRequestTimeHandler" scope="prototype"/>
+ </list>
+ </property>
+ </bean>
+
+ <bean id="EncodeMessage" class="net.shibboleth.sp.profile.impl.EncodeMessage" scope="prototype"
+ p:createOutputObjects="true"
+ p:messageEncoderFactory-ref="messageEncoderFactory" />
+
+ <!-- Message Encoder factory is a prototype to allow reuse of the encoders -->
+ <bean id="messageEncoderFactory"
+ class="net.shibboleth.oidc.profile.impl.AuthenticationRequestMessageEncoderFactory" scope="prototype"
+ c:encoders-ref="shibboleth.oidc.AuthenticationRequestEncoders" />
+
+ <!-- List must itself be a prototype so new encoders are created per request -->
+ <util:list id="shibboleth.oidc.AuthenticationRequestEncoders" scope="prototype">
+ <ref bean="HTTPRedirectAuthnRequestEncoder" />
+ <ref bean="HTTPPostAuthnRequestEncoder" />
+ </util:list>
+
+ <bean id="HTTPRedirectAuthnRequestEncoder"
+ class="net.shibboleth.oidc.profile.encoding.impl.HTTPRedirectAuthnRequestEncoder" init-method=""
+ scope="prototype" p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier"
+ p:authorizationParamsAreValidPredicate="#{getObject('%{sp.oidc.AuthzParamsValidPredicate:}'.trim())}" />
+
+ <bean id="HTTPPostAuthnRequestEncoder"
+ class="net.shibboleth.oidc.profile.encoding.impl.HTTPPostAuthnRequestEncoder" init-method="" scope="prototype"
+ p:velocityEngine-ref="shibboleth.VelocityEngine" p:httpServletResponseSupplier-ref="shibboleth.RemotedHttpServletResponseSupplier"
+ p:authorizationParamsAreValidPredicate="#{getObject('%{sp.oidc.AuthzParamsValidPredicate:}'.trim())}"
+ p:cSPDigester="#{%{idp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPDigester') : null}"
+ p:cSPNonceGenerator="#{%{idp.encoders.cspEnabled:true} ? getObject('shibboleth.CSPNonce') : null}"/>
+
</beans>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml
index 90fc4fd..6385fa7 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-flow.xml
@@ -19,25 +19,51 @@
<evaluate expression="InitializeOAuth2ClientContext" />
<evaluate expression="InitializeAuthorizationRequest" />
- <!-- <evaluate expression="PopulateRequestSignatureSigningParameters" />
- <evaluate expression="PopulateEncryptionParameters" />
+ <!-- SAML<evaluate expression="PopulateRequestSignatureSigningParameters" />
+ SAML<evaluate expression="PopulateEncryptionParameters" />
- <evaluate expression="AddAuthnRequest" />
<evaluate expression="EncryptNameIDs" /> -->
<evaluate expression="BuildAuthenticationRequest"/>
-
- <!--
- <evaluate expression="HandleOutboundMessage" />
- <evaluate expression="IssueCorrelationCookie" />
- <evaluate expression="EncodeMessage" /> -->
<evaluate expression="'proceed'" />
- <transition on="proceed" to="proceed" />
+ <transition on="proceed" to="RequestObjectRequiredAndSupported" />
<!-- Remap any other events into a fall-through to the next flow. -->
<transition to="ReselectFlow" />
</action-state>
+ <!-- Is a request object required by the configuration, and does the OP support it? -->
+ <decision-state id="RequestObjectRequiredAndSupported">
+ <if test="RequestObjectRequiredAndSupportedPredicate.test(opensamlProfileRequestContext)"
+ then="GenerateRequestObject"
+ else="BuildOutboundMessage" />
+ </decision-state>
+
+ <action-state id="GenerateRequestObject">
+ <evaluate expression="PopulateRequestObjectSignatureSigningParameters" />
+ <evaluate expression="PopulateRequestObjectEncryptionParameters" />
+ <evaluate expression="BuildRequestObject" />
+ <!--
+ We can not sign and encrypt the RO here (we need state from WF execution key).
+ That is left to the preEncodeMessageHandlers.
+ -->
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="BuildOutboundMessage" />
+ <!-- Remap any other events into a fall-through to the next flow. -->
+ <transition to="ReselectFlow" />
+ </action-state>
+
+ <action-state id="BuildOutboundMessage">
+ <evaluate expression="HandleOutboundMessage" />
+ <!-- <evaluate expression="IssueCorrelationCookie" /> -->
+ <evaluate expression="EncodeMessage" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="proceed" />
+ <!-- Remap any other events into a fall-through to the next flow. -->
+ <transition to="ReselectFlow" />
+ </action-state>
+
<!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
<bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/initiator/oidc/oidc-beans.xml" />
diff --git a/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties b/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
index a044e23..ec57c50 100644
--- a/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
+++ b/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
@@ -17,5 +17,9 @@ sp.oidc.signing.rsa.enc.key = %{idp.home}/credentials/sp/sp-encryption-rsa.jwk
#sp.oidc.encryption.key.2 = %{idp.home}/credentials/sp/sp-encryption-old.key
#sp.oidc.encryption.cert.2 = %{idp.home}/credentials/sp/sp-encryption-old.crt
-sp.oidc.redirecturl.allowedOrigins = http://localhost
+#sp.oidc.redirecturl.allowedOrigins = http://localhost
+
+## TEST ENC FOR NOW from profile config
+#idp.oidc.requestobject.encrypted = true
+
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
index 17f5bf3..6b69dc0 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
@@ -15,20 +15,34 @@
package net.shibboleth.sp.oidc.flows;
import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opensaml.messaging.decoder.MessageDecodingException;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.saml.common.binding.SAMLBindingSupport;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
import org.testng.annotations.Test;
+import com.nimbusds.oauth2.sdk.AuthorizationRequest;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+
+import net.shibboleth.sp.context.AgentRequestContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.flows.AbstractSPFlowTest;
import net.shibboleth.sp.messaging.RemotedHttpServletRequest;
+import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
+import net.shibboleth.sp.profile.impl.IssueCorrelationCookie;
/**
*
@@ -48,15 +62,10 @@ public class OIDCAuthenticationFlowTest extends AbstractSPFlowTest {
/** Resource URL. */
@Nonnull public static final byte[] RESOURCE_URL = "https://sp.example.org/secure".getBytes(StandardCharsets.UTF_8);
+
+ /** REDIRECT URI. */
+ @Nonnull public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/callback";
- /** ACS URL. */
- @Nonnull public static final String RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/ACS";
-
- /** POST ACS URL. */
- @Nonnull public static final String POST_RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/SAML2/POST";
-
- /** Artifact ACS URL. */
- @Nonnull public static final String ARTIFACT_RESPONSE_URL = "https://sp.example.org/Shibboleth.sso/SAML2/Artifact";
/** Constructor. */
protected OIDCAuthenticationFlowTest() {
@@ -67,9 +76,10 @@ public class OIDCAuthenticationFlowTest extends AbstractSPFlowTest {
* Basic flow test, mainly for TDD.
*
* @throws IOException on error
+ * @throws MessageDecodingException
*/
@Test
- public void testFlow() throws IOException {
+ public void testFlow() throws IOException, MessageDecodingException {
setDefaultAuth();
final DDF input = new DDF(null).structure();
@@ -81,7 +91,104 @@ public class OIDCAuthenticationFlowTest extends AbstractSPFlowTest {
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
assertFlowExecutionOutcome(result.getOutcome());
- //assertOutputMessageEvent(result, AuthnEventIds.NO_POTENTIAL_FLOW);
+
+ final AuthorizationRequest req = validateOutputMessage(result);
+ }
+
+ /**
+ * Decode an encoded response and run sanity checks against it.
+ *
+ * @param result flow execution result
+ *
+ * @return the authentication/authorization request object
+ *
+ * @throws MessageDecodingException
+ */
+ @Nonnull private AuthorizationRequest validateOutputMessage(@Nonnull final FlowExecutionResult result)
+ throws MessageDecodingException {
+ final ProfileRequestContext prc = retrieveProfileRequestContext(result);
+ assert prc != null;
+ final AgentRequestContext arc = prc.ensureSubcontext(AgentRequestContext.class);
+ final DDF input = arc.getInput();
+ final DDF output = arc.getOutput();
+
+ assert output != null;
+ Assert.assertTrue(output.isstruct());
+ final DDF http = output.getmember(RemotedHttpServletResponse.STRUCTURE_NAME);
+ Assert.assertTrue(http.isstruct());
+
+ final AuthorizationRequest authnRequest;
+ final byte[] redirect = http.getmember(RemotedHttpServletResponse.REDIRECT).unsafe_string();
+ if (redirect != null) {
+ final String redirectURL = new String(redirect, StandardCharsets.UTF_8);
+ authnRequest = decodeRedirect(redirectURL,
+ input != null ? input.getmember(SPConstants.STATE).string() : null);
+ } else {
+ //TODO what to pull out if in the POST body
+ final byte[] body = http.getmember("response.data").unsafe_string();
+ Assert.assertNotNull(body);
+ // Not trivial to consider parsing the form, so just bypass that step.
+ final Object oidc = prc.ensureOutboundMessageContext().ensureMessage();
+ assert oidc instanceof AuthenticationRequest;
+ authnRequest = (AuthenticationRequest) oidc;
+ Assert.assertEquals(SAMLBindingSupport.getRelayState(prc.ensureOutboundMessageContext()),
+ input != null ? input.getmember(SPConstants.STATE).string() : null);
+ }
+
+ assert authnRequest != null;
+ Assert.assertNotNull(authnRequest.getClientID());
+ Assert.assertNotNull(authnRequest.getScope());
+ Assert.assertTrue(authnRequest.getScope().contains("openid"));
+
+ if (input != null) {
+ boolean foundCorrelationCookie = false;
+ for (final DDF header : http.getmember(RemotedHttpServletResponse.HEADERS)) {
+ if ("Set-Cookie".equals(header.name())) {
+ final String cookie = header.string();
+ assert cookie != null;
+ if (cookie.startsWith("__Host-" + IssueCorrelationCookie.DEFAULT_COOKIE_PREFIX)) {
+ //TODO our use of the correlation cookie
+
+// final Boolean passive = authnRequest.isPassive();
+// final String passiveDelim = passive ? "=T:" : "=F:";
+// Assert.assertEquals(cookie,
+// "__Host-_shibsp_req_" + input.getmember(SPConstants.STATE).string() + passiveDelim + authnRequest.getID()
+// + "; HttpOnly=true; Path=/; SameSite=None; Secure=true");
+ foundCorrelationCookie = true;
+ }
+ }
+ }
+ //TODO Not Set yet: Assert.assertTrue(foundCorrelationCookie);
+ }
+
+// final NameIDPolicy pol = authnRequest.getNameIDPolicy();
+// assert pol != null;
+// assertTrue(pol.getAllowCreate());
+// Assert.assertEquals(pol.getFormat(), format);
+
+ return authnRequest;
+ }
+
+ /**
+ * Decodes an OAuth authorization message encoded via HTTP-Redirect binding.
+ *
+ * @param url the encoded redirect
+ * @param relayState RelayState to check for TODO state?
+ *
+ * @return decoded message
+ * @throws MessageDecodingException
+ */
+ @Nonnull protected AuthorizationRequest decodeRedirect(@Nullable final String url, @Nullable final String relayState)
+ throws MessageDecodingException {
+ if (url == null) {
+ throw new MessageDecodingException("URl is null");
+ }
+ try {
+ return AuthorizationRequest.parse(new URI(url));
+ } catch (final ParseException | URISyntaxException e) {
+ throw new MessageDecodingException("No message, or incorrect type.", e);
+ }
+
}
}
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
index c4a67a2..0c5c9fc 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
@@ -47,7 +47,7 @@ public class TestSPOIDCEnvironmentApplicationContextInitializer
mock.setProperty("sp.stateToken.Manager","shibboleth.CookieStateTokenManager");
//mock.setProperty("idp.service.logging.resource", "/logback-webauthn-flow-test.xml");
mock.setProperty("idp.additionalProperties",
- "/conf/ldap.properties, /conf/saml-nameid.properties, /conf/services.properties, /conf/admin/admin.properties, /conf/authn/authn.properties, /conf/c14n/subject-c14n.properties, /credentials/secrets.properties, /conf/sp/sp.properties, /conf/sp/oidc.properties");
+ "/conf/ldap.properties, /conf/saml-nameid.properties, /conf/services.properties, /conf/admin/admin.properties, /conf/authn/authn.properties, /conf/c14n/subject-c14n.properties, /credentials/secrets.properties, /conf/sp/sp.properties, /conf/sp/oidc.properties, /conf/sp/oidc-test.properties");
applicationContext.getEnvironment().getPropertySources().addFirst(mock);
log.info("Prepending properties '{}'", mock.getSource());
}
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/metadata/ProviderMetadataFileBasedCredentialResolver.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/metadata/ProviderMetadataFileBasedCredentialResolver.java
new file mode 100644
index 0000000..badaec2
--- /dev/null
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/metadata/ProviderMetadataFileBasedCredentialResolver.java
@@ -0,0 +1,105 @@
+/*
+ * 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.sp.oidc.metadata;
+
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashSet;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+import org.springframework.util.StreamUtils;
+
+import com.nimbusds.jose.jwk.JWKSet;
+
+import net.shibboleth.oidc.security.credential.impl.BasicJOSEObjectCredentialResolver;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.component.InitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+import net.shibboleth.shared.resolver.ResolverException;
+import net.shibboleth.shared.resource.Resource;
+
+/**
+ * A credential resolver used for testing that returns the same collection of credentials no matter what metadata
+ * is provided.
+ */
+public class ProviderMetadataFileBasedCredentialResolver extends BasicJOSEObjectCredentialResolver
+ implements InitializableComponent {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(ProviderMetadataFileBasedCredentialResolver.class);
+
+ /** Initialization flag. */
+ private boolean isInitialized;
+
+ /** The resource that holds the JWKS. */
+ @Nonnull
+ private final Resource jwkSetResource;
+
+ /** The contents of the JWKS.*/
+ @Nonnull private final JWKSet cachedSet;
+
+
+ /**
+ * Constructor.
+ *
+ * @param jwkSet the resource holding the JWK key sets.
+ */
+ public ProviderMetadataFileBasedCredentialResolver(
+ @Nonnull @ParameterName(name="jwkSet") final Resource jwkSet) {
+ jwkSetResource = Constraint.isNotNull(jwkSet, "JWKS can not be null");
+
+ if (!jwkSetResource.isFile()) {
+ throw new IllegalArgumentException("JWKS must be a file");
+ }
+ try (var inputStream = jwkSet.getInputStream()) {
+ final String jwkSetAsString = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
+ cachedSet = JWKSet.parse(jwkSetAsString);
+ assert cachedSet != null;
+ } catch (final Exception e) {
+ throw new IllegalArgumentException("JWKS resource can not be read", e);
+ }
+
+ }
+
+
+ /** {@inheritDoc} */
+ public boolean isInitialized() {
+ return isInitialized;
+ }
+
+ /** {@inheritDoc} */
+ public void initialize() throws ComponentInitializationException {
+ isInitialized = true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nonnull
+ protected Iterable<Credential> resolveFromSource(@Nullable final CriteriaSet criteriaSet) throws ResolverException {
+ log.debug("Getting JWKs from cached set in '{}'",jwkSetResource.getDescription());
+ final LinkedHashSet<Credential> credentials = new LinkedHashSet<>(1);
+ populateCredentialsFromKeySet(cachedSet, credentials);
+ return credentials;
+ }
+
+
+
+}
diff --git a/sp-oidc-conf-impl/src/test/resources/logback-webauthn-flow-test.xml b/sp-oidc-conf-impl/src/test/resources/logback-webauthn-flow-test.xml
deleted file mode 100644
index d71e445..0000000
--- a/sp-oidc-conf-impl/src/test/resources/logback-webauthn-flow-test.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<configuration>
-
- <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>%level [%logger:%line] - %msg%n</pattern>
- <charset>UTF-8</charset>
- </encoder>
- </appender>
-
- <root>
- <level value="WARN" />
- <appender-ref ref="STDOUT" />
- </root>
-
- <logger name="net.shibboleth.idp.plugin.authn" level="TRACE" additivity="false">
- <appender-ref ref="STDOUT" />
- </logger>
-
- <logger name="org.springframework.webflow" level="INFO" additivity="false">
- <appender-ref ref="STDOUT" />
- </logger>
-
-</configuration>
\ No newline at end of file
diff --git a/sp-oidc-conf-impl/src/test/resources/metadata/openid-configuration.json b/sp-oidc-conf-impl/src/test/resources/metadata/openid-configuration.json
index 8f2f888..d04036c 100644
--- a/sp-oidc-conf-impl/src/test/resources/metadata/openid-configuration.json
+++ b/sp-oidc-conf-impl/src/test/resources/metadata/openid-configuration.json
@@ -5,6 +5,8 @@
"userinfo_endpoint": "https://openidconnect.op.example.org/v1/userinfo",
"revocation_endpoint": "https://oauth2.op.example.org/revoke",
"jwks_uri": "https://op.example.org/oauth2/v3/certs",
+"request_parameter_supported" : true,
+"request_object_signing_alg_values_supported" : ["RS256"],
"response_types_supported": [
"code",
"token",
@@ -15,6 +17,8 @@
"code token id_token",
"none"
],
+"request_object_encryption_enc_values_supported" : ["A128CBC-HS256"],
+"request_object_encryption_alg_values_supported" : ["RSA-OAEP-256"],
"subject_types_supported": [
"public"
],
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
index c485d43..e0d1b85 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
@@ -65,24 +65,47 @@
</util:list>
<util:list id="test.ProfileConfigurations">
- <ref bean="OIDC.SSO" />
-<!-- <ref bean="SAML2.ECP" />
- <ref bean="SAML2.Logout" /> -->
- </util:list>
-
- <util:list id="test.responseBindingProfileConfigurations">
- <!-- <bean parent="OIDC.SSO" p:responseBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" /> -->
- <bean parent="OIDC.SSO"/>
-<!-- <ref bean="SAML2.ECP" />
- <ref bean="SAML2.Logout" /> -->
+ <bean parent="OIDC.SSO" p:securityConfiguration-ref="testSecConfig"/>
</util:list>
+
+ <!--
+ Overrides the default security configuration to provide an encryption credential resolver
+ that loads JWKs from a local file instead of retrieving them from the issuer’s JWKS URI.
+ This simplifies testing by avoiding reliance on an external JWKS endpoint. We also constraint
+ the algorithms used, to simplify baseline tests.
+ -->
+ <bean id="testSecConfig" parent="shibboleth.oidc.DefaultSecurityConfiguration">
+ <property name="jwtEncryptionConfiguration">
+ <bean id="dummy.oidc.EncryptionConfiguration" parent="dummy.oidc.BasicEncryptionConfiguration"
+ p:contentEncryptionKeyCredentialResolver-ref="defaultOIDCContentEncryptionKeyCredentialResolver">
+ <property name="keyTransportEncryptionAlgorithms">
+ <list>
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.KeyManagementConstants.ALGO_ID_ALG_RSA_OAEP_256" />
+ </list>
+ </property>
+ <property name="dataEncryptionAlgorithms">
+ <list>
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.EncryptionConstants.ALGO_ID_ENC_ALG_A128CBC_HS256" />
+ </list>
+ </property>
+ <property name="KEKCredentialResolver">
+ <bean id="defaultOIDCKeyEncryptionCredentialResolver"
+ class="net.shibboleth.oidc.security.credential.impl.ChainingJOSEObjectCredentialResolver">
+ <constructor-arg>
+ <list>
+ <bean id="ProviderMetadataFileBasedCredentialResolver"
+ class="net.shibboleth.sp.oidc.metadata.ProviderMetadataFileBasedCredentialResolver"
+ c:jwkSet="classpath:/net/shibboleth/idp/module/credentials/op/global-provider-test-jwks.json" />
+ </list>
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
- <util:list id="test.featureBlockingProfileConfigurations">
- <!-- <bean parent="OIDC.SSO" p:disallowedFeatures="0x1F" /> -->
- <bean parent="OIDC.SSO" />
-<!-- <ref bean="SAML2.ECP" />
- <ref bean="SAML2.Logout" /> -->
- </util:list>
<!-- ============ Profile defaults ============ -->
diff --git a/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test.properties
similarity index 85%
copy from sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
copy to sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test.properties
index a044e23..bff09e5 100644
--- a/sp-oidc-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/oidc.properties
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test.properties
@@ -1,4 +1,4 @@
-# OIDC-specific RP settings
+# OIDC-specific RP test settings
# Settings for RP public/private signing and encryption key(s)
# During decryption key rollover, point the ".2" properties at a second
@@ -19,3 +19,8 @@ sp.oidc.signing.rsa.enc.key = %{idp.home}/credentials/sp/sp-encryption-rsa.jwk
sp.oidc.redirecturl.allowedOrigins = http://localhost
+## TEST ENC FOR NOW from profile config
+idp.oidc.requestobject.encrypted = true
+
+#idp.oidc.encryption.config = dummy.oidc.EncryptionConfiguration
+
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/credentials/op/global-provider-test-jwks.json b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/credentials/op/global-provider-test-jwks.json
new file mode 100644
index 0000000..a2b5201
--- /dev/null
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/credentials/op/global-provider-test-jwks.json
@@ -0,0 +1,14 @@
+{
+ "keys": [
+ {
+ "kty": "RSA",
+ "kid": "test-rsa",
+ "use": "enc",
+ "alg": "RSA-OAEP-256",
+ "n": "sXchX0m6hD-0WWLHH7CBmUdZqcmULVqfSTQO1Gp6Tw1BaZs6w3Q7Y61EuyyQyRvnYp9TF9A36X8D6kZVjs8QyRCPXJ1z4CWe5Vd4VqSczNhbtNmoqHmbf_tnk3K4KcfHdOiG0fKtTe1sVHZp-srOjApXso2MXMCCrbvw8p9l0j8zUy2S1yrTtT6O4nD_f8RDN2y0x3Xkqf0J1Ae0uPCZ_P8qZf-ryjYlgHu41jTUGDhz9aEuZeq_0UZZKxPoJWh8T_Gsl_sEm9qMPjG-d5rORjQZ-4qv0FvAk3Q3FksznGd7eh1olPYCDnZzBjO0xk4LWhP2XBBij5zUgcqAnYKw",
+ "e": "AQAB"
+ }
+ ]
+}
+
+
\ No newline at end of file
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/sp/oidc-test-beans.xml b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/sp/oidc-test-beans.xml
index 2ee0182..2ec55a8 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/sp/oidc-test-beans.xml
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/sp/oidc-test-beans.xml
@@ -30,5 +30,12 @@
c:entityCertificate-ref="dummy.idp.X509Certificate"
c:privateKey-ref="dummy.idp.PrivateKey"
p:entityId="https://idp.example.org" />
+
+ <bean id="dummy.oidc.BasicEncryptionConfiguration" abstract="true"
+ class="net.shibboleth.oidc.security.jose.impl.BasicEncryptionConfiguration"
+ p:includedAlgorithms="#{getObject('shibboleth.oidc.IncludedEncryptionAlgorithms')}"
+ p:excludedAlgorithms="#{getObject('shibboleth.oidc.ExcludedEncryptionAlgorithms')}" />
+
+
</beans>
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AddRequestedClaimsHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AddRequestedClaimsHandler.java
index 8e65bde..9cd6a3e 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AddRequestedClaimsHandler.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AddRequestedClaimsHandler.java
@@ -37,7 +37,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
* <p>The claims are added from a customizable strategy/hook. No additional claims are provided by default.</p>
*
* <p>Also records in the request whether the upstream OP supports the claims parameter, for later inspection by
- * downstream components that only access to the request e.g. an encoder.</p>
+ * downstream components that only has access to the request e.g. an encoder.</p>
*/
public class AddRequestedClaimsHandler extends AbstractOIDCAuthenticationRequestActionMessageHandler {
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/RequestObjectSupportedSignatureSigningAlgorithms.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/RequestObjectSupportedSignatureSigningAlgorithms.java
new file mode 100644
index 0000000..37c7a4d
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/RequestObjectSupportedSignatureSigningAlgorithms.java
@@ -0,0 +1,40 @@
+/*
+ * 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.sp.oidc.metadata.impl;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+/**
+ * Pull out the request object supported signature signing algorithms from the metadata.
+ */
+public class RequestObjectSupportedSignatureSigningAlgorithms implements Function<OIDCProviderMetadata, List<String>>{
+
+ @Override
+ @Nullable public List<String> apply(@Nullable final OIDCProviderMetadata metadata) {
+
+ if (metadata == null || metadata.getRequestObjectJWSAlgs() == null) {
+ return null;
+ }
+ return metadata.getRequestObjectJWSAlgs()
+ .stream().map(JWSAlgorithm::getName).toList();
+ }
+
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObject.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObject.java
new file mode 100644
index 0000000..5fc353b
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/BuildRequestObject.java
@@ -0,0 +1,349 @@
+/*
+ * 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.sp.oidc.profile.impl;
+
+import java.time.Duration;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.id.Audience;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.openid.connect.sdk.claims.ACR;
+import com.nimbusds.openid.connect.sdk.claims.ClaimsSet;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext;
+import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
+import net.shibboleth.oidc.profile.encoding.AuthenticationContextClassReferenceSupport;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+
+/**
+ * Action that creates a Request Object {@link JWT}, and sets it to the work context
+ * {@link OIDCAuthenticationRequest} located under {@link ProfileRequestContext#getOutboundMessageContext()}.
+ *
+ * <p>Note, some parameters are set downstream in the flow before the request object is signed and or encrypted.
+ * These parameters are only available to the HTTP Controller e.g. state, and must be set during the external
+ * authentication redirect.</p>
+ *
+ * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link AuthnEventIds#INVALID_AUTHN_CTX}
+ * @pre <pre>ProfileRequestContext.getOutboundMessageContext().getMessage()
+ * instance of OIDCAuthenticationRequest.class</pre>
+ * @post Add a JWT request object to the in-flight authentication request
+ */
+public class BuildRequestObject extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(BuildRequestObject.class);
+
+ /** Lookup strategy to locate the OpenID Provider metadata to use.*/
+ @Nonnull private Function<ProfileRequestContext, OIDCProviderMetadataContext> providerMetadataLookupStrategy;
+
+ /** Lookup function for relying party context. */
+ @Nonnull private Function<ProfileRequestContext,RelyingPartyContext> relyingPartyContextLookupStrategy;
+
+ /** A hook to allow additional checking of the request object claims after it is built.*/
+ @Nonnull private Predicate<ClaimsSet> claimsSetIsValidPredicate;
+
+ /** A strategy hook to add custom claims to the claims set.*/
+ @Nonnull private BiConsumer<ProfileRequestContext, ClaimsSet> customClaimsStrategy;
+
+ /**
+ * Is the request object going to be signed? if so the 'iss' and 'aud' claims will be set.
+ * Defaults to always true, as it is permissible that both 'iss' and 'aud' claim can exist in
+ * plain request objects.
+ */
+ @Nonnull private Predicate<ProfileRequestContext> requestObjectToBeSignedPredicate;
+
+ /** OIDC authentication request built by the IdP. */
+ @NonnullBeforeExec private OIDCAuthenticationRequest authnRequest;
+
+ /** OpenID Provider metadata .*/
+ @NonnullBeforeExec private OIDCProviderMetadata providerMetadata;
+
+ /** Constructor.*/
+ public BuildRequestObject() {
+ claimsSetIsValidPredicate = PredicateSupport.alwaysTrue();
+ requestObjectToBeSignedPredicate = PredicateSupport.alwaysTrue();
+
+ providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
+ new ChildContextLookup<>(OIDCPeerEntityContext.class).compose(
+ new OutboundMessageContextLookup()));
+
+ relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
+ // Create a no-op consumer
+ customClaimsStrategy = (prc, set) -> {};
+ }
+
+ /**
+ * Set a bi-consumer hook to add custom claims to the request object.
+ *
+ * @param strategy The custom claims strategy to set.
+ *
+ * @since 2.1.0
+ */
+ public void setCustomClaimsStrategy(@Nullable final BiConsumer<ProfileRequestContext, ClaimsSet> strategy) {
+ checkSetterPreconditions();
+ if (strategy != null) {
+ customClaimsStrategy = strategy;
+ }
+ }
+
+
+ /**
+ * Set the strategy used to locate the {@link RelyingPartyContext} associated with a given
+ * {@link ProfileRequestContext}.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setRelyingPartyContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,RelyingPartyContext> strategy) {
+ checkSetterPreconditions();
+
+ relyingPartyContextLookupStrategy =
+ Constraint.isNotNull(strategy, "RelyingPartyContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the lookup strategy to locate the OpenID providers metadata.
+ *
+ * @param strategy the strategy.
+ */
+ public void setProviderMetadataLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, OIDCProviderMetadataContext> strategy) {
+ checkSetterPreconditions();
+
+ providerMetadataLookupStrategy =
+ Constraint.isNotNull(strategy,"Provider metadata lookup strategy can not be null");
+ }
+
+ /**
+ * Set a hook that allows the built request object to be validated before it is used.
+ * This is run in addition too, but before, the built in validation taken from the specification.
+ * If this returns false, the built in validation is not run, and validation fails.
+ *
+ * @param predicate the hook to run
+ */
+ public void setClaimsSetIsValidPredicate(@Nullable final Predicate<ClaimsSet> predicate) {
+ checkSetterPreconditions();
+
+ if (predicate != null) {
+ claimsSetIsValidPredicate = predicate;
+ }
+ }
+
+ /**
+ * Set a predicate to determine if the request object will be 'eventually' signed. If so,
+ * the 'iss' and 'aud' claims will be set into the request object.
+ *
+ * @param predicate the predicate
+ */
+ public void setRequestObjectToBeSignedPredicate(@Nullable final Predicate<ProfileRequestContext> predicate) {
+ checkSetterPreconditions();
+
+ if (predicate != null) {
+ requestObjectToBeSignedPredicate = predicate;
+ }
+ }
+
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ final MessageContext outboundMsgContext = profileRequestContext.getOutboundMessageContext();
+ if (outboundMsgContext == null) {
+ log.error("{} Outbound message context was null", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ if (!(outboundMsgContext.getMessage() instanceof OIDCAuthenticationRequest)) {
+ log.error("{} Outbound message was not an authentication request", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ authnRequest = (OIDCAuthenticationRequest) outboundMsgContext.getMessage();
+ if (authnRequest == null) {
+ log.error("{} No authentication request found", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ final OIDCProviderMetadataContext providerMetadataContext =
+ providerMetadataLookupStrategy.apply(profileRequestContext);
+ if (providerMetadataContext == null) {
+ log.error("{} No provider metadata context found for peer", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ providerMetadata = providerMetadataContext.getProviderInformation();
+ if (providerMetadata == null) {
+ log.error("{} No provider metadata found for peer", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ //TODO maybe we could share building of a request object or query params (from AbstractOIDCMessageEncoder)
+ // in some way
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ log.debug("{} Building a plain RequestObject JWT", getLogPrefix());
+
+ final ClaimsSet requestObjectClaims = new ClaimsSet();
+
+ if (requestObjectToBeSignedPredicate.test(profileRequestContext)) {
+ if (providerMetadata.getIssuer() != null) {
+ requestObjectClaims.setAudience(
+ new Audience(providerMetadata.getIssuer().getValue()));
+ } else {
+ // Should never happen
+ log.error("{} Signed RequestObject requires 'iss' claim, which is currently null",
+ getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+ return;
+ }
+ requestObjectClaims.setIssuer(new Issuer(authnRequest.getClientID().getValue()));
+ }
+
+ requestObjectClaims.setClaim("client_id", authnRequest.getClientID().toString());
+
+ setClaimIfPresent(requestObjectClaims, "nonce", authnRequest.getNonce());
+ setClaimIfPresent(requestObjectClaims, "response_type", authnRequest.getResponseType());
+
+ // Only set the response_mode if not equal to the default for that response_type
+ final ResponseMode responseMode = authnRequest.getDefaultResponseMode();
+ if (responseMode != null && !responseMode.equals(authnRequest.getResponseMode())){
+ requestObjectClaims.setClaim("response_mode", authnRequest.getResponseMode());
+ }
+
+ setClaimIfPresent(requestObjectClaims, "redirect_uri", authnRequest.getRedirectURI());
+ setClaimIfPresent(requestObjectClaims,"scope", authnRequest.getScope());
+ setClaimIfPresent(requestObjectClaims, "max_age", authnRequest.getMaxAge());
+ setClaimIfPresent(requestObjectClaims, "login_hint", authnRequest.getLoginHint());
+ setClaimIfPresent(requestObjectClaims, "prompt", authnRequest.getPrompt());
+ setClaimIfPresent(requestObjectClaims, "display", authnRequest.getDisplay());
+
+ if (authnRequest.providerSupportsClaimsParameter()) {
+ // Build the ACRs if set before adding the 'claims' claim
+ AuthenticationContextClassReferenceSupport.buildACRClaimsRequest(authnRequest);
+ if (authnRequest.getRequestedClaims() != null) {
+ requestObjectClaims.setClaim("claims", authnRequest.getRequestedClaims());
+ }
+ } else if (!authnRequest.getAcrs().isEmpty()) {
+ // Only add ACR values as acr_values if the provider does not support the 'claims' claim.
+ final String acrString = String.join(" ", authnRequest.getAcrs()
+ .stream()
+ .map(ACR::getValue).toList());
+ requestObjectClaims.setClaim("acr_values", acrString);
+ }
+
+ // Add custom claims from the claims hook
+ customClaimsStrategy.accept(profileRequestContext, requestObjectClaims);
+
+ // Validate the request object
+ if (!validateRequestObject(profileRequestContext, requestObjectClaims)) {
+ log.error("{} RequestObject claims are not valid", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+ return;
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("{} Setting the RequestObject claims: {}", getLogPrefix(),
+ requestObjectClaims.toJSONString());
+ }
+
+ // Create the claim first, can be signed and encrypted as a JWT later
+ authnRequest.setRequestObjectClaimsSet(requestObjectClaims);
+
+ }
+
+ /**
+ * Set the claim onto the claims set if not {@code null}. Calls toString on each value, assuming it
+ * will produce the correct value.
+ *
+ * @param claims the claims set
+ * @param claimName the claim name
+ * @param claim the claim
+ */
+ private void setClaimIfPresent(
+ @Nonnull final ClaimsSet claims, @Nonnull final String claimName, @Nullable final Object claim) {
+ if (claim instanceof final Duration duration) {
+ // Convert to seconds
+ claims.setClaim(claimName, duration.toSeconds());
+ } else if (claim != null) {
+ claims.setClaim(claimName, claim.toString());
+ }
+ }
+
+ /**
+ * Ensure the request object is valid by assessing the claims are correct.
+ *
+ * @param profileRequestContext the profile request context
+ * @param requestObjectClaims the claims of the request object
+ *
+ * @return true if the request object claims are valid, false otherwise
+ */
+ private boolean validateRequestObject(@Nonnull final ProfileRequestContext profileRequestContext,
+ @Nonnull final ClaimsSet requestObjectClaims) {
+
+ if (!claimsSetIsValidPredicate.test(requestObjectClaims)) {
+ return false;
+ }
+
+ if (requestObjectToBeSignedPredicate.test(profileRequestContext)) {
+ if (requestObjectClaims.getClaim("iss") == null) {
+ return false;
+ }
+ if (requestObjectClaims.getClaim("aud") == null) {
+ return false;
+ }
+
+ }
+ return true;
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list