[java-plugin-shibd-oidc] 01/04: Move state handling to new State Manager mechanisms
Codeberg
noreply at shibboleth.net
Wed May 6 15:47:28 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd-oidc.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/e3bb47ca52fa6465af3361b79da7151e0740badb
commit e3bb47ca52fa6465af3361b79da7151e0740badb
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue May 5 13:35:42 2026 +0100
Move state handling to new State Manager mechanisms
- Move state handling to new mechanisms in the sp-server
- Move to new State Data context
- Fix javadoc
- Fix checkstyle
- Add missing package-info
---
resources/checkstyle/checkstyle-suppressions.xml | 9 +
resources/checkstyle/checkstyle.xml | 4 +-
.../sp/oidc/BasicOIDCProtocolSupportService.java | 4 +-
.../logic/RequestObjectRequiredAndSupported.java | 3 +-
.../sp/oidc/config/logic/package-info.java | 18 +
.../sp/oidc/config/navigate/package-info.java | 18 +
.../oidc/context/AuthnRequestStateDataContext.java | 75 ---
.../sp/oidc/context/navigate/package-info.java | 18 +
.../shibboleth/sp/oidc/context/package-info.java | 18 +
.../messaging/AbstractAgentMessageHandler.java | 111 ----
.../AbstractAgentRequestMessageHandler.java | 101 ---
.../AbstractApplicationMessageHandler.java | 69 --
.../oidc/messaging/context/logic/package-info.java | 18 +
...onStateFromCorrelationCookieLookupFunction.java | 53 --
.../RFPFromCorrelationCookieLookupFunction.java | 53 --
.../navigate/RFPFromOAuthStateLookupFunction.java | 48 --
.../RequestCorrelationFromStateLookupFunction.java | 184 ------
.../navigate/StateFromJSONStateLookupFunction.java | 49 --
.../navigate/StateFromResponseLookupFunction.java | 51 ++
.../sp/oidc/messaging/navigate/package-info.java | 18 +
.../profile/AuthenticationRequestStateData.java | 257 +++-----
.../shibboleth/sp/oidc/profile/OIDCConstants.java | 12 +-
.../sp/oidc/profile/OIDCInitiatorConstants.java | 11 +-
.../shibboleth/sp/oidc/profile/package-info.java | 18 +
.../sp/consumer/oidc/code/post/post-beans.xml | 5 -
.../sp/consumer/oidc/code/query/query-beans.xml | 5 -
.../idp/flows/sp/consumer/oidc/oidc-beans.xml | 144 +----
.../idp/flows/sp/consumer/oidc/oidc-flow.xml | 11 +-
.../idp/flows/sp/initiator/oidc/oidc-beans.xml | 79 +--
.../idp/flows/sp/initiator/oidc/oidc-flow.xml | 6 +-
.../shibboleth/idp/flows/sp/oidc-common-beans.xml | 43 +-
.../net/shibboleth/sp/service/agent/postconfig.xml | 2 +-
.../shibboleth/idp/module/conf/sp/oidc.properties | 3 -
.../flows/AbstractOIDCTokenConsumerFlowTest.java | 533 ++++++++++++++++
.../oidc/flows/OIDCSessionInitiatorFlowTest.java | 46 +-
.../sp/oidc/flows/OIDCTokenConsumerFlowTest.java | 696 ++-------------------
...DCTokenConsumerFlowUsingStorageServiceTest.java | 148 +++++
.../shibboleth/sp/oidc/flows/TestConstants.java | 144 ++---
...DCEnvironmentApplicationContextInitializer.java | 14 +-
...ationContextInitializerWithStorageService.java} | 27 +-
.../shibboleth/idp/module/conf/sp/sp.properties | 2 +-
.../impl/OIDCProviderMetadataLookupHandler.java | 4 +-
.../impl/DecodeStateAsJsonObjectConsumer.java | 2 +-
.../oidc/profile/decoding/impl/package-info.java | 18 +
.../oidc/profile/impl/AbstractHttpOAuthAction.java | 3 +-
.../AbstractProviderMetadataLookupFunction.java | 2 +-
.../impl/AuthTimeRequestedActivationCondition.java | 47 +-
...thenticationRequestStateForStorageStrategy.java | 117 +---
.../AuthenticationRequestTimeLookupFunction.java | 37 +-
.../impl/CreateAuthenticationStateData.java | 121 ++++
.../profile/impl/ExchangeCodeForAccessToken.java | 4 +-
.../sp/oidc/profile/impl/ExtractOIDCClaims.java | 3 +-
.../impl/InitializeAuthorizationRequest.java | 2 +-
...izeOAuth2ClientAuthenticationMethodHandler.java | 17 +-
.../impl/InitializeOAuth2ClientContext.java | 4 +-
.../profile/impl/InitializePeerEntityContext.java | 101 +++
.../sp/oidc/profile/impl/IssueStateCookie.java | 206 ------
.../profile/impl/MapStateTokenToStateValue.java | 177 ------
.../profile/impl/MapStateValueToStateToken.java | 165 -----
...AgeFromAuthenticationRequestLookupFunction.java | 33 +-
...omAuthenticationRequestStateLookupStrategy.java | 15 +-
.../PKCECodeVerifierFromStateLookupStrategy.java | 43 +-
.../impl/PrepareOIDCInboundMessageContext.java | 2 +-
.../impl/RedirectUriFromStateLookupStrategy.java | 46 +-
.../RequestedACRClaimsFromStateLookupStrategy.java | 35 +-
.../RequestedACRValidationActivationCondition.java | 2 +-
.../sp/oidc/profile/impl/ResolveStateCookie.java | 230 -------
...AuthenticationRequestToPeerContextConsumer.java | 174 ------
.../impl/SetAuthenticationStateTokenConsumer.java | 50 --
.../profile/impl/StandardExtractionStrategy.java | 1 +
.../profile/impl/StateTokenLookupStrategy.java | 72 +++
.../impl/ValidateUserInfoJSONObjectClaims.java | 2 +-
.../AuthTimeRequestedActivationConditionTest.java | 17 +-
...ticationRequestStateForStorageStrategyTest.java | 95 +--
...uthenticationRequestTimeLookupFunctionTest.java | 16 +-
.../sp/oidc/profile/impl/IssueStateCookieTest.java | 173 -----
...romAuthenticationRequestLookupFunctionTest.java | 10 +-
...uestedACRClaimsFromStateLookupStrategyTest.java | 14 +-
...uestedACRValidationActivationConditionTest.java | 4 +-
.../oidc/profile/impl/ResolveStateCookieTest.java | 178 ------
...enticationRequestToPeerContextConsumerTest.java | 146 -----
81 files changed, 1765 insertions(+), 3751 deletions(-)
diff --git a/resources/checkstyle/checkstyle-suppressions.xml b/resources/checkstyle/checkstyle-suppressions.xml
new file mode 100644
index 0000000..5063f85
--- /dev/null
+++ b/resources/checkstyle/checkstyle-suppressions.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE suppressions PUBLIC
+ "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
+ "https://checkstyle.org/dtds/suppressions_1_2.dtd">
+
+<suppressions>
+
+</suppressions>
diff --git a/resources/checkstyle/checkstyle.xml b/resources/checkstyle/checkstyle.xml
index e3f1203..ac8b0f3 100644
--- a/resources/checkstyle/checkstyle.xml
+++ b/resources/checkstyle/checkstyle.xml
@@ -108,7 +108,7 @@
<property name="max" value="1000"/>
</module>
<module name="Header">
- <property name="header" value="/*\n * Licensed to the University Corporation for Advanced Internet Development,\n * Inc. (UCAID) under one or more contributor license agreements. See the\n * NOTICE file distributed with this work for additional information regarding\n * copyright ownership. The UCAID licenses this file to You under the Apache\n * License, Version 2.0 (the "License"); you may not use this file except in\n * compliance with the License. You may obtain a cop [...]
+ <property name="header" value="/*\n * Licensed under the Apache License, Version 2.0 (the "License");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an "AS IS" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, eit [...]
<property name="fileExtensions" value="java"/>
</module>
<module name="JavadocPackage"/>
@@ -116,7 +116,7 @@
<property name="max" value="120"/>
</module>
<module name="SuppressionFilter">
- <property name="file" value="resources/checkstyle-suppressions.xml"/>
+ <property name="file" value="resources/checkstyle/checkstyle-suppressions.xml"/>
<property name="optional" value="false"/>
</module>
</module>
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/BasicOIDCProtocolSupportService.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/BasicOIDCProtocolSupportService.java
index c3cf21e..3c48e02 100644
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/BasicOIDCProtocolSupportService.java
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/BasicOIDCProtocolSupportService.java
@@ -31,7 +31,7 @@ public class BasicOIDCProtocolSupportService extends BasicProtocolSupportService
/** Metadata source. */
@NonnullAfterInit private ReloadableService<ProviderMetadataResolver> metadataResolver;
- /** [{@inheritDoc} */
+ /** {@inheritDoc}. */
@NonnullAfterInit
public ReloadableService<ProviderMetadataResolver> getMetadataResolver() {
return metadataResolver;
@@ -49,7 +49,7 @@ public class BasicOIDCProtocolSupportService extends BasicProtocolSupportService
metadataResolver = Constraint.isNotNull(service, "ProviderMetadataResolver service cannot be null");
}
- /** {@inheritDoc} */
+ /** {@inheritDoc}. */
@Override
protected void doInitialize() throws ComponentInitializationException {
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
index 1e4bec2..68d3532 100644
--- 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
@@ -71,7 +71,8 @@ public class RequestObjectRequiredAndSupported extends AbstractRelyingPartyPredi
boolean requestObjectRequestedFromConfig = false;
final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
- if (rpc != null && rpc.getProfileConfig() instanceof final OIDCAuthenticationRelyingPartyProfileConfiguration rpConfig) {
+ if (rpc != null && rpc.getProfileConfig()
+ instanceof final OIDCAuthenticationRelyingPartyProfileConfiguration rpConfig) {
requestObjectRequestedFromConfig = rpConfig.isUseRequestObject(input);
}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/config/logic/package-info.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/config/logic/package-info.java
new file mode 100644
index 0000000..c5f560e
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/config/logic/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Predicates for RP-specific messaging configuration.
+ */
+package net.shibboleth.sp.oidc.config.logic;
\ No newline at end of file
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/config/navigate/package-info.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/config/navigate/package-info.java
new file mode 100644
index 0000000..bd6680b
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/config/navigate/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Functions to navigate into profile configuration information.
+ */
+package net.shibboleth.sp.oidc.config.navigate;
\ No newline at end of file
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/AuthnRequestStateDataContext.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/AuthnRequestStateDataContext.java
deleted file mode 100644
index 99fd52b..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/AuthnRequestStateDataContext.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.BaseContext;
-
-import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
-
-/**
- * A context to hold the token used to recover state information about the original authentication request, alongside
- * any recovered information.
- *
- * <p>On initiation requests, it is likely only the token will be populated. In consumer flows, it is likely both the
- * token and the recovered state is populated.</p>
- */
-public class AuthnRequestStateDataContext extends BaseContext {
-
- /** The token used as a key to the stored authentication request state.*/
- @Nullable private String token;
-
- /** The authentication state data. */
- @Nullable private AuthenticationRequestStateData authnState;
-
- /**
- * Set the token used as a key to the stored authentication request state.
- *
- * @param tokenIn the token to set.
- */
- @Nonnull public AuthnRequestStateDataContext setToken(@Nullable final String tokenIn) {
- token = tokenIn;
- return this;
- }
- /**
- * Return the token used as a key to the stored authentication request state.
- *
- * @return the token.
- */
- @Nullable public String getToken() {
- return token;
- }
-
- /**
- * Sets the authentication state recovered from the authentication request.
- *
- * @param state The authnState to set
- */
- @Nonnull public AuthnRequestStateDataContext setAuthnState(@Nullable final AuthenticationRequestStateData state) {
- authnState = state;
- return this;
- }
-
- /**
- * Get the authentication state recovered from the authentication request.
- *
- * @return the authentication state
- */
- @Nullable public AuthenticationRequestStateData getAuthnState() {
- return authnState;
- }
-
-}
\ No newline at end of file
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/package-info.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/package-info.java
new file mode 100644
index 0000000..518fced
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/navigate/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Functions that navigate messaging contexts.
+ */
+package net.shibboleth.sp.oidc.context.navigate;
\ No newline at end of file
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/package-info.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/package-info.java
new file mode 100644
index 0000000..8bb753a
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/context/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Context classes for RP-specific information needed by profile actions.
+ */
+package net.shibboleth.sp.oidc.context;
\ No newline at end of file
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractAgentMessageHandler.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractAgentMessageHandler.java
deleted file mode 100644
index deac1fe..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractAgentMessageHandler.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.messaging;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.MessageContext;
-import org.opensaml.messaging.handler.MessageHandlerException;
-
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.sp.Agent;
-import net.shibboleth.sp.context.AgentRequestContext;
-import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
-
-/**
- * TODO javadoc and should we use as a message handler
- */
-public abstract class AbstractAgentMessageHandler extends AbstractAgentRequestMessageHandler {
-
- /** Cached agent from context. */
- @NonnullBeforeExec private Agent agent;
-
- /** Whether to create the output objects into which the message will be encoded. */
- private boolean createOutputObjects;
-
- /**
- * Gets whether to create an output {@link DDF} and {@link RemotedHttpServletResponse}.
- *
- * @return whether to create output objects
- */
- public boolean isCreateOutputObjects() {
- return createOutputObjects;
- }
-
- /**
- * Sets whether to create an output {@link DDF} and {@link RemotedHttpServletResponse}.
- *
- * <p>Defaults to false.</p>
- *
- * @param flag flag to set
- */
- public void setCreateOutputObjects(final boolean flag) {
- checkSetterPreconditions();
-
- createOutputObjects = flag;
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
-
- if (!super.doPreInvoke(messageContext)) {
- return false;
- }
- agent = ensureAgentRequestContext().getAgent();
- if (agent == null) {
- throw new MessageHandlerException("No Agent found in context");
- }
-
- return true;
- }
-
- /**
- * Gets the {@link Agent} for this request.
- *
- * @return the agent, or null
- */
- @Nullable public Agent getAgent() {
- return agent;
- }
-
- /**
- * Gets the {@link Agent} for this request.
- *
- * @return the agent
- */
- @Nonnull public Agent ensureAgent() {
- return Constraint.isNotNull(agent, "Agent was null");
- }
-
- /**
- * If {@link #isCreateOutputObjects()} is true, then this ensures an output
- * {@link DDF} is in place and if creating one, adds the structure and installs
- * the wrapper for a {@link RemotedHttpServletResponse}.
- */
- protected void ensureOutputObjects() {
- final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
- if (isCreateOutputObjects() && agentRequestContext.getOutput() == null) {
- final DDF output = new DDF(null);
- agentRequestContext.setOutput(output);
- agentRequestContext.setRemotedHttpServletResponse(new RemotedHttpServletResponse(
- output.structure().addmember(RemotedHttpServletResponse.STRUCTURE_NAME)));
- }
- }
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractAgentRequestMessageHandler.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractAgentRequestMessageHandler.java
deleted file mode 100644
index 243ff3d..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractAgentRequestMessageHandler.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * 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.messaging;
-
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.MessageContext;
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.messaging.context.navigate.ParentContextLookup;
-import org.opensaml.messaging.handler.AbstractMessageHandler;
-import org.opensaml.messaging.handler.MessageHandlerException;
-import org.opensaml.profile.context.ProfileRequestContext;
-
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.sp.context.AgentRequestContext;
-
-/**
- * TODO javadoc and should we use as a message handler
- */
-public abstract class AbstractAgentRequestMessageHandler extends AbstractMessageHandler {
-
- /** Lookup strategy for {@link AgentRequestContext}. */
- @Nonnull private Function<MessageContext,AgentRequestContext> agentRequestContextLookupStrategy;
-
- /** Cached context to populate. */
- @NonnullBeforeExec private AgentRequestContext agentRequestContext;
-
- /**
- * Gets the cached request context located by the lookup strategy.
- *
- * @return cached request context or null
- */
- @Nullable public AgentRequestContext getAgentRequestContext() {
- return agentRequestContext;
- }
-
- /**
- * Gets the cached request context located by the lookup strategy.
- *
- * @return cached request context
- */
- @Nonnull public AgentRequestContext ensureAgentRequestContext() {
- return Constraint.isNotNull(agentRequestContext, "AgentRequestContext was null");
- }
-
- /** Constructor. */
- protected AbstractAgentRequestMessageHandler() {
- // By default msgCtx (up)-> ProfileRequestContext (down)-> AgentRequestContext
- agentRequestContextLookupStrategy = new ChildContextLookup<>(AgentRequestContext.class)
- .compose(new ParentContextLookup<>(ProfileRequestContext.class));
-
- }
-
- /**
- * Sets the lookup strategy for the {@link AgentRequestContext}.
- *
- * @param strategy lookup strategy
- */
- public void setAgentRequestContextLookupStrategy(
- @Nonnull final Function<MessageContext,AgentRequestContext> strategy) {
- checkSetterPreconditions();
-
- agentRequestContextLookupStrategy = Constraint.isNotNull(strategy,
- "AgentRequestContext lookup strategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
-
- if (!super.doPreInvoke(messageContext)) {
- return false;
- }
-
- agentRequestContext = agentRequestContextLookupStrategy.apply(messageContext);
- if (agentRequestContext == null) {
- throw new MessageHandlerException("No AgentRequestContext found");
- }
-
- return true;
- }
-
-
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractApplicationMessageHandler.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractApplicationMessageHandler.java
deleted file mode 100644
index f380043..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractApplicationMessageHandler.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.messaging;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.MessageContext;
-import org.opensaml.messaging.handler.MessageHandlerException;
-
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.sp.Application;
-
-/**
- * TODO javadoc and should we use as a message handler
- */
-public abstract class AbstractApplicationMessageHandler extends AbstractAgentMessageHandler {
-
- /** Cached Application from context. */
- @NonnullBeforeExec private Application application;
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
-
- if (!super.doPreInvoke(messageContext)) {
- return false;
- }
-
- application = ensureAgentRequestContext().getApplication();
- if (application == null) {
- throw new MessageHandlerException("No Application found in context");
- }
-
- return true;
- }
-
- /**
- * Gets the {@link Application} for this request.
- *
- * @return the application, or null
- */
- @Nullable public Application getApplication() {
- return application;
- }
-
- /**
- * Gets the {@link Application} for this request.
- *
- * @return the application
- */
- @Nonnull public Application ensureApplication() {
- return Constraint.isNotNull(application, "Application was null");
- }
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/logic/package-info.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/logic/package-info.java
new file mode 100644
index 0000000..1611dca
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/context/logic/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Predicates for RP-specific messaging configuration.
+ */
+package net.shibboleth.sp.oidc.messaging.context.logic;
\ No newline at end of file
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/AuthenticationStateFromCorrelationCookieLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/AuthenticationStateFromCorrelationCookieLookupFunction.java
deleted file mode 100644
index 115d708..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/AuthenticationStateFromCorrelationCookieLookupFunction.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.messaging.navigate;
-
-import java.util.function.Function;
-
-import javax.annotation.Nullable;
-
-import net.minidev.json.JSONObject;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.sp.oidc.context.CorrelationCookieStateContext;
-import net.shibboleth.sp.oidc.profile.OIDCConstants;
-
-/**
- * A function that extracts the authentication state token from the correlation cookie, if present, and returns it.
- */
-public class AuthenticationStateFromCorrelationCookieLookupFunction
- implements Function<CorrelationCookieStateContext,String> {
-
- /** {@inheritDoc} */
- @Nullable public String apply(@Nullable final CorrelationCookieStateContext stateContext) {
- if (stateContext == null) {
- return null;
- }
- final JSONObject correlationCookie = stateContext.getCorrelationCookieValue();
- if (correlationCookie == null) {
- return null;
- }
- final String authnStateToken = correlationCookie.getAsString(OIDCConstants.AUTHN_STATE_FIELD);
- if (StringSupport.trimOrNull(authnStateToken) != null) {
- return authnStateToken;
- }
- return null;
-
- }
-
-
-
-}
-
-
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/RFPFromCorrelationCookieLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/RFPFromCorrelationCookieLookupFunction.java
deleted file mode 100644
index 6dde325..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/RFPFromCorrelationCookieLookupFunction.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.messaging.navigate;
-
-import java.util.function.Function;
-
-import javax.annotation.Nullable;
-
-import net.minidev.json.JSONObject;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.sp.oidc.context.CorrelationCookieStateContext;
-import net.shibboleth.sp.oidc.profile.OIDCConstants;
-
-/**
- * A function that extracts the Request Forgery Protection nonce from the correlation cookie, if present, and
- * returns it.
- */
-public class RFPFromCorrelationCookieLookupFunction implements Function<CorrelationCookieStateContext,String> {
-
- /** {@inheritDoc} */
- @Nullable public String apply(@Nullable final CorrelationCookieStateContext stateContext) {
- if (stateContext == null) {
- return null;
- }
- final JSONObject correlationCookie = stateContext.getCorrelationCookieValue();
- if (correlationCookie == null) {
- return null;
- }
- final String rfpToken = correlationCookie.getAsString(OIDCConstants.RFP_FIELD);
- if (StringSupport.trimOrNull(rfpToken) != null) {
- return rfpToken;
- }
- return null;
-
- }
-
-
-
-}
-
-
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/RFPFromOAuthStateLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/RFPFromOAuthStateLookupFunction.java
deleted file mode 100644
index 2f22c2a..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/RFPFromOAuthStateLookupFunction.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.messaging.navigate;
-
-import java.util.function.Function;
-
-import javax.annotation.Nullable;
-
-import net.minidev.json.JSONObject;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.sp.oidc.context.OAuthStateContext;
-import net.shibboleth.sp.oidc.profile.OIDCConstants;
-
-/**
- * A lookup function that extracts the request forgery protection value from a Base64URL encoded JSON object.
- */
-public class RFPFromOAuthStateLookupFunction implements Function<OAuthStateContext,String> {
-
- /** {@inheritDoc} */
- @Nullable public String apply(@Nullable final OAuthStateContext stateContext) {
- if (stateContext == null) {
- return null;
- }
- final JSONObject stateJson = stateContext.getStateJson();
- if (stateJson == null) {
- return null;
- }
- final String nonceFromState = stateJson.getAsString(OIDCConstants.RFP_FIELD);
- if (StringSupport.trimOrNull(nonceFromState) != null) {
- return nonceFromState;
- }
- return null;
-
- }
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/RequestCorrelationFromStateLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/RequestCorrelationFromStateLookupFunction.java
deleted file mode 100644
index 8187070..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/RequestCorrelationFromStateLookupFunction.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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.messaging.navigate;
-
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.nimbusds.jose.util.StandardCharset;
-
-import net.minidev.json.JSONObject;
-import net.minidev.json.JSONValue;
-import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
-import net.shibboleth.shared.annotation.ParameterName;
-import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.codec.EncodingException;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.PredicateSupport;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.security.DataSealer;
-import net.shibboleth.shared.security.DataSealerException;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
-import net.shibboleth.sp.oidc.profile.OIDCConstants;
-
-/**
- * A {@link Function} that creates a request correlation cookie value with the token/key of the stored authentication
- * request state, and the request forgery protection (RFP) value that was stored in the OAuth state parameter.
- * The resulting cookie value is Base64URL encoded JSON string (noting the sealer will already base64 encode the
- * sealed String).
- */
-public class RequestCorrelationFromStateLookupFunction extends AbstractIdentifiableInitializableComponent
- implements Function<ProfileRequestContext,String> {
-
- /** Class logger. */
- @Nonnull
- private final Logger log = LoggerFactory.getLogger(RequestCorrelationFromStateLookupFunction.class);
-
- /** A strategy to locate the {@link AuthnRequestStateDataContext}.*/
- @Nonnull
- private final Function<ProfileRequestContext, AuthnRequestStateDataContext> authnRequestDataStateLookupStrategy;
-
- /** Strategy to locate the authentication request. */
- @Nonnull
- private final Function<ProfileRequestContext, OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
-
- /** Optional data sealer to use. */
- @Nullable private DataSealer dataSealer;
-
- /**
- * If the dataSealer is provided should it be used to seal the authentication request state? Defaults to true,
- * that is, if the dataSealer is provided, always seal state.
- */
- @Nonnull private Predicate<ProfileRequestContext> sealState;
-
-
- /**
- *
- * Constructor.
- *
- * @param dataLookuptrategy a strategy to locate the {@link AuthnRequestStateDataContext}
- * @param authnRequestStrategy a strategy to locate the {@link OIDCAuthenticationRequest}
- */
- public RequestCorrelationFromStateLookupFunction(@ParameterName(name="authenticationRequestDataStateLookupStrategy")
- @Nonnull final Function<ProfileRequestContext, AuthnRequestStateDataContext> dataLookuptrategy,
- @ParameterName(name="authenticationRequestLookupStrategy")
- @Nonnull final Function<ProfileRequestContext, OIDCAuthenticationRequest> authnRequestStrategy) {
-
- authnRequestDataStateLookupStrategy =
- Constraint.isNotNull(dataLookuptrategy, "AuthnRequestStateDataContext lookup strategy cannot be null");
- authenticationRequestLookupStrategy =
- Constraint.isNotNull(authnRequestStrategy, "OIDCAuthenticationRequest lookup strategy cannot be null");
- sealState = PredicateSupport.alwaysTrue();
- }
-
-
- /**
- * Sets {@link DataSealer} to use.
- *
- * @param sealer data sealer
- */
- public void setDataSealer(@Nullable final DataSealer sealer) {
- checkSetterPreconditions();
-
- dataSealer = sealer;
- }
-
- /**
- * Set the predicate to determine whether to seal the state.
- *
- * @param predicate the seal state predicate to set.
- */
- public void setSealStatePredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
- checkSetterPreconditions();
-
- sealState = Constraint.isNotNull(predicate, "Seal state predicate can not be null");
- }
-
- /**
- * Set the flag to determine whether to seal the state.
- *
- * @param flag the flag to set.
- */
- public void setSealState(final boolean flag) {
- checkSetterPreconditions();
-
- sealState = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
- }
-
- /** {@inheritDoc} */
- @Nullable public String apply(@Nullable final ProfileRequestContext prc) {
- final AuthnRequestStateDataContext stateContext = authnRequestDataStateLookupStrategy.apply(prc);
-
- if (stateContext == null || stateContext.getToken() == null) {
- log.error("No authentication request state found, can not create correlation cookie");
- return null;
- }
-
- final OIDCAuthenticationRequest authnRequest = authenticationRequestLookupStrategy.apply(prc);
-
- String csrfNonce = null;
- if (authnRequest != null) {
- final JSONObject stateJson = authnRequest.getStateJson();
- if (stateJson != null) {
- final Object nonceFromOAuthStateAsObject = stateJson.get(OIDCConstants.RFP_FIELD);
- if (nonceFromOAuthStateAsObject instanceof final String nonce) {
- csrfNonce = nonce;
- }
- }
- }
- if (csrfNonce == null) {
- log.error("Unable to generate request correlation cookie, no request forgery protection value found");
- return null;
- }
-
- final JSONObject stateObject = new JSONObject();
- stateObject.appendField(OIDCConstants.AUTHN_STATE_FIELD, stateContext.getToken());
- stateObject.appendField(OIDCConstants.RFP_FIELD, csrfNonce);
- final String stateJsonString = stateObject.toJSONString(JSONValue.COMPRESSION);
- if (stateJsonString == null || stateJsonString.isEmpty()) {
- return null;
- }
- try {
- final DataSealer localDataSealer = dataSealer;
- if (localDataSealer != null && sealState.test(prc)) {
- log.debug("{}: Request correlation cookie is sealed", getId());
- final String sealed = localDataSealer.wrap(stateJsonString);
- final byte[] sealedAsBytes = sealed.getBytes(StandardCharset.UTF_8);
- assert sealedAsBytes != null;
- return Base64Support.encodeURLSafe(sealedAsBytes);
- } else {
- log.warn("{}: Request correlation cookie was NOT sealed, either DataSealer is not configured or "
- + "sealing predicate returned false. Sealing should be enabled in production", getId());
- final byte[] stateJsonAsBytes = stateJsonString.getBytes(StandardCharset.UTF_8);
- assert stateJsonAsBytes != null;
- return Base64Support.encodeURLSafe(stateJsonAsBytes);
- }
-
- } catch (final EncodingException | DataSealerException e) {
- log.error("Unable to generate request correlation cookie", e);
- return null;
- }
-
- }
-
-}
-
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/StateFromJSONStateLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/StateFromJSONStateLookupFunction.java
deleted file mode 100644
index b79528e..0000000
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/StateFromJSONStateLookupFunction.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.messaging.navigate;
-
-import java.util.function.Function;
-
-import javax.annotation.Nullable;
-
-import net.minidev.json.JSONObject;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.sp.oidc.context.OAuthStateContext;
-import net.shibboleth.sp.oidc.profile.OIDCConstants;
-
-/**
- * Lookup strategy that extracts the 'state' parameter from the OAuth 2.0 state JSON Object in the state context. This
- * state is used to carry a reference to the resource URL.
- */
-public class StateFromJSONStateLookupFunction implements Function<OAuthStateContext,String> {
-
- /** {@inheritDoc} */
- @Nullable public String apply(@Nullable final OAuthStateContext stateContext) {
- if (stateContext == null) {
- return null;
- }
- final JSONObject stateJson = stateContext.getStateJson();
- if (stateJson == null) {
- return null;
- }
- final String stateFromState = stateJson.getAsString(OIDCConstants.STATE_FIELD);
- if (StringSupport.trimOrNull(stateFromState) != null) {
- return stateFromState;
- }
- return null;
-
- }
-
-}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/StateFromResponseLookupFunction.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/StateFromResponseLookupFunction.java
new file mode 100644
index 0000000..7c3e1da
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/StateFromResponseLookupFunction.java
@@ -0,0 +1,51 @@
+/*
+ * 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.messaging.navigate;
+
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
+
+/**
+ * Lookup strategy that extracts the OAuthn 2.0 authentication response 'state' parameter.
+ */
+public class StateFromResponseLookupFunction implements Function<ProfileRequestContext,String> {
+
+ /** {@inheritDoc} */
+ @Nullable public String apply(@Nullable final ProfileRequestContext prc) {
+ if (prc == null) {
+ return null;
+ }
+ final MessageContext inbound = prc.getInboundMessageContext();
+ if (inbound == null) {
+ return null;
+ }
+ if (inbound.getMessage() instanceof final AuthenticationResponse response) {
+ final State responseState = response.getState();
+ if (responseState != null) {
+ return responseState.getValue();
+ }
+ }
+ return null;
+
+ }
+
+}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/package-info.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/package-info.java
new file mode 100644
index 0000000..042a52e
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/navigate/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * Functions to navigate into profile messages.
+ */
+package net.shibboleth.sp.oidc.messaging.navigate;
\ No newline at end of file
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/AuthenticationRequestStateData.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/AuthenticationRequestStateData.java
index 5e11959..ff0c047 100644
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/AuthenticationRequestStateData.java
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/AuthenticationRequestStateData.java
@@ -15,10 +15,7 @@
package net.shibboleth.sp.oidc.profile;
-import java.net.URI;
import java.time.Duration;
-import java.time.Instant;
-import java.util.List;
import java.util.Objects;
import javax.annotation.Nonnull;
@@ -27,34 +24,28 @@ import javax.annotation.concurrent.NotThreadSafe;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.MoreObjects;
import com.nimbusds.openid.connect.sdk.OIDCClaimsRequest;
+import com.nimbusds.openid.connect.sdk.Prompt;
import com.nimbusds.openid.connect.sdk.claims.ACR;
import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSetRequest;
import com.nimbusds.openid.connect.sdk.claims.ClaimsSetRequest.Entry;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.sp.state.StateData;
/**
- * A DTO class that carries authentication request information that needs to be recovered to validate the authentication
- * response. This class is designed for JSON serialization and deserialization for storage e.g. in a cookie.
+ * A DTO class that carries OIDC specific authentication request information that needs to be recovered to validate the
+ * authentication response. This class is designed for JSON serialization and deserialization for storage.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@NotThreadSafe
-public class AuthenticationRequestStateData {
+public class AuthenticationRequestStateData extends StateData {
- /** The identifier of the client that is making the authentication request. */
- @Nullable private String clientId;
-
/** The id_token nonce value. */
@Nullable private String nonce;
- /** The expected issuer of the authentication response. */
- @Nullable private String authenticatingAuthority;
-
/** The optional PKCE code verifier. */
@Nullable private String pkceCodeVerifier;
@@ -66,47 +57,6 @@ public class AuthenticationRequestStateData {
* or the claims parameter requested auth_time.
*/
private boolean authTimeRequired;
-
- /** The time at which the RP made the authentication request to the OP.*/
- @Nullable private Instant authnRequestTime;
-
- /** List of authentication context class references requested in the authentication request.*/
- @Nonnull @Unmodifiable @NotLive private List<String> acrs;
-
- /**
- * The redirect_uri value of the authentication request, included here to be
- * able to include it in any further Token endpoint requests.
- */
- @Nullable private URI redirectUri;
-
- /** Constructor.*/
- public AuthenticationRequestStateData() {
- acrs = CollectionSupport.emptyList();
- }
-
- /**
- * Set the identifier of the client that is making the authentication request. Can be used to ensure the audience
- * of the response matches the client that made the request.
- *
- * @param id The client ID to set.
- *
- * @return the updated object
- */
- @Nonnull public AuthenticationRequestStateData setClientId(@Nullable final String id) {
- clientId = id;
- return this;
- }
-
- /**
- * Get the identifier of the client that is making the authentication request. Can be used to ensure the audience
- * of the response matches the client that made the request.
- *
- * @return the client ID.
- */
- @JsonProperty("client_id")
- @Nullable public String getClientId() {
- return clientId;
- }
/**
* Get the nonce expected in the id_token response.
@@ -130,27 +80,6 @@ public class AuthenticationRequestStateData {
return this;
}
- /**
- * Get the expected issuer of the authentication response.
- *
- * @return the authentication authority
- */
- @JsonProperty("authority")
- @Nullable public String getAuthenticationAuthority() {
- return authenticatingAuthority;
- }
-
- /**
- * Set the expected issuer of the authentication response.
- *
- * @param authority the authentication authority to set
- *
- * @return the updated object
- */
- @Nonnull public AuthenticationRequestStateData setAuthenticationAuthority(@Nullable final String authority) {
- authenticatingAuthority = authority;
- return this;
- }
/**
* Get the PKCE code verifier used to generate the code challenge in the authentication request.
@@ -210,8 +139,8 @@ public class AuthenticationRequestStateData {
}
/**
- * Is auth_time required inside the id_token? either because the max_age parameter was used, or the claims parameter
- * requested it.
+ * Is auth_time required inside the id_token? either because the max_age parameter was used, or the claims
+ * parameter requested it.
*
* @return true if auth_time has been requested and is required inside the id_token, false otherwise.
*/
@@ -219,74 +148,7 @@ public class AuthenticationRequestStateData {
public boolean isAuthTimeRequired() {
return authTimeRequired;
}
-
- /**
- * Set the time at which this RP sent this authentication request to the OP.
- *
- * @param time the time the request was made
- *
- * @return the updated object
- */
- @Nonnull public AuthenticationRequestStateData setAuthnRequestTime(@Nullable final Instant time) {
- authnRequestTime = time;
- return this;
- }
-
- /**
- * Get the time at which this RP sent this authentication request to the OP.
- *
- * @return the time the request was made
- */
- @JsonProperty("authn_request_time")
- @Nullable public Instant getAuthnRequestTime() {
- return authnRequestTime;
- }
-
- /**
- * Set the acrs requested in the authentication request.
- *
- * @param acrsIn The acrs to set.
- *
- * @return the updated object
- */
- public AuthenticationRequestStateData setAcrs(@Nullable final List<String> acrsIn) {
- if (acrsIn != null) {
- acrs = CollectionSupport.copyToList(acrsIn);
- }
- return this;
- }
- /**
- * Get the acrs requested in the authentication request.
- *
- * @return the acrs.
- */
- @JsonProperty("requested_acrs")
- @Nonnull @Unmodifiable @NotLive public List<String> getAcrs() {
- return CollectionSupport.copyToList(acrs);
- }
-
- /**
- * Get the redirect_uri value of the authentication request.
- *
- * @return Returns the redirectUri.
- */
- @JsonProperty("redirect_uri")
- @Nullable public URI getRedirectUri() {
- return redirectUri;
- }
-
- /**
- * Set the redirect_uri value of the authentication request, included here to be able to include it in any further
- * Token endpoint requests.
- *
- * @param uri The redirectUri to set.
- *
- * @return the updated object
- */
- public AuthenticationRequestStateData setRedirectUri(@Nullable final URI uri) {
- redirectUri = uri;
- return this;
- }
+
/**
* Create an instance of this class from the given authentication request and authentication authority.
@@ -296,24 +158,39 @@ public class AuthenticationRequestStateData {
*
* @return a populated instance of this class
*/
+ @SuppressWarnings("null")
public static AuthenticationRequestStateData from(
@Nonnull final OIDCAuthenticationRequest request, @Nonnull final String authenticationAuthority) {
- @SuppressWarnings("null")
- final AuthenticationRequestStateData state = new AuthenticationRequestStateData()
+ // Specify OIDC specific information first
+ final AuthenticationRequestStateData state = new AuthenticationRequestStateData()
.setNonce(request.getNonce() != null ? request.getNonce().getValue() : null)
- .setAuthenticationAuthority(authenticationAuthority)
.setPkceCodeVerifier(request.getCodeVerifier())
- .setMaxAge(request.getMaxAge())
- .setAuthnRequestTime(request.getAuthnRequestTime())
- .setClientId(request.getClientID().getValue())
- .setAcrs(request.getAcrs().stream().filter(Objects::nonNull).map(ACR::getValue).toList())
- .setRedirectUri(request.getRedirectURI());
+ .setMaxAge(request.getMaxAge());
+ // Now set the generic state information
+ state.setAcrs(request.getAcrs().stream().filter(Objects::nonNull).map(ACR::getValue).toList())
+ .setResponseLocation(request.getRedirectURI() != null ? request.getRedirectURI().toString() : null)
+ .setAuthenticationAuthority(authenticationAuthority)
+ .setIssuer(request.getClientID().getValue())
+ .setRequestTime(request.getAuthnRequestTime());
- state.setAuthTimeRequired(isAuthTimeRequired(request));
+ state.setAuthTimeRequired(isAuthTimeRequired(request));
+ state.setPassive(determineIsPassive(request));
+
return state;
}
+ /**
+ * Determine if the authentication request is passive by checking if the prompt parameter contains "none".
+ *
+ * @param request the authentication request
+ * @return true if the prompt parameter contains "none", false otherwise.
+ */
+ static boolean determineIsPassive(@Nonnull final OIDCAuthenticationRequest request) {
+ final Prompt localPrompt = request.getPrompt();
+ return localPrompt != null && localPrompt.contains(Prompt.Type.NONE);
+ }
+
/**
* Determine if auth_time is required inside the id_token either because the max_age parameter was used, or the
* claims parameter requested it as an ESSENTIAL claim.
@@ -347,34 +224,56 @@ public class AuthenticationRequestStateData {
@Override
public String toString() {
- final String nonceSuffix = mask(nonce);
- String pkceCodeVerifierSuffix = mask(pkceCodeVerifier);
+ final String nonceSuffix = mask(getNonce());
+ String pkceCodeVerifierSuffix = mask(getPkceCodeVerifier());
pkceCodeVerifierSuffix = pkceCodeVerifierSuffix == null ? "" : pkceCodeVerifierSuffix;
+
+ return MoreObjects.toStringHelper(this)
+ .add("clientAddress", getClientAddress())
+ .add("issuer", getIssuer())
+ .add("authenticatingAuthority", getAuthenticationAuthority())
+ .add("requestTime", getRequestTime())
+ .add("acrs", getAcrs())
+ .add("reponseLocation", getResponseLocation())
+ .add("resource", getResource())
+ .add("nonce", nonceSuffix)
+ .add("pkceCodeVerifier", pkceCodeVerifierSuffix)
+ .add("maxAge", getMaxAge())
+ .add("authTimeRequired", isAuthTimeRequired())
+ .toString();
+ }
- return "AuthenticationRequestStateData [nonce="+nonceSuffix+", authenticatingAuthority=" + authenticatingAuthority
- + ", pkceCodeVerifier="+pkceCodeVerifierSuffix+", maxAge=" + maxAge + ", authTimeRequired="
- + authTimeRequired + ", authnRequestTime=" + authnRequestTime + ", acrs=" + acrs + ", "
- + "redirect_uri="+redirectUri+"]";
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ super.hashCode(),
+ authTimeRequired,
+ maxAge,
+ nonce,
+ pkceCodeVerifier
+ );
}
-
- /**
- * A method to mask all but the last 2 characters of a string value for logging purposes.
- *
- * @param value the string value to mask
- * @return the masked string
- */
- private static String mask(final String value) {
- if (value == null) {
- return null;
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
}
- if (value.length() <= 4) {
- return "****";
+ if (!super.equals(obj)) {
+ return false;
}
- return "****" + value.substring(value.length() - 2);
- }
-
-
-
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final AuthenticationRequestStateData other = (AuthenticationRequestStateData) obj;
+ return authTimeRequired == other.authTimeRequired
+ && Objects.equals(maxAge, other.maxAge)
+ && Objects.equals(nonce, other.nonce)
+ && Objects.equals(pkceCodeVerifier, other.pkceCodeVerifier);
+ }
+
}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCConstants.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCConstants.java
index 20dc9c9..e4783d4 100644
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCConstants.java
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCConstants.java
@@ -22,12 +22,7 @@ import net.shibboleth.shared.annotation.constraint.NotEmpty;
* OIDC related constants.
*/
public final class OIDCConstants {
-
- /** Private constructor. */
- private OIDCConstants() {
- }
-
/**
* Name of the state field to add to the state JSON object. The state field represents the current 'state'
* value of the DDF.
@@ -38,6 +33,11 @@ public final class OIDCConstants {
@Nonnull @NotEmpty public static final String AUTHN_STATE_FIELD = "authnState";
/** Name of the Request Forgery Protection (nonce) field.*/
- @Nonnull @NotEmpty public static final String RFP_FIELD = "rfp";
+ @Nonnull @NotEmpty public static final String RFP_FIELD = "rfp";
+
+ /** Private constructor. */
+ private OIDCConstants() {
+
+ }
}
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java
index 167f8cc..2a607d6 100644
--- a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java
@@ -40,20 +40,21 @@ public final class OIDCInitiatorConstants {
/** IsPassive input parameter. */
@Nonnull @NotEmpty public static final String IS_PASSIVE = "isPassive";
- /** Prompt parameter */
+ /** Prompt parameter. */
@Nonnull @NotEmpty public static final String PROMPT = "prompt";
- /** Scope parameter */
+ /** Scope parameter. */
@Nonnull @NotEmpty public static final String SCOPE = "scope";
- /** Resource parameter */
+ /** Resource parameter. */
@Nonnull @NotEmpty public static final String RESOURCE = "resource";
- /** ui_locales parameter */
+ /** ui_locales parameter. */
@Nonnull @NotEmpty public static final String UI_LOCALES = "ui_locales";
/** authnContextClassRef input parameter. */
- @Nonnull @NotEmpty public static final String AUTHN_CONTEXT_CLASS_REF = AuthnContextClassRef.DEFAULT_ELEMENT_LOCAL_NAME;
+ @Nonnull @NotEmpty public static final String AUTHN_CONTEXT_CLASS_REF =
+ AuthnContextClassRef.DEFAULT_ELEMENT_LOCAL_NAME;
/** Private constructor. */
private OIDCInitiatorConstants() {
diff --git a/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/package-info.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/package-info.java
new file mode 100644
index 0000000..f05bf07
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * API classes for SP profile actions.
+ */
+package net.shibboleth.sp.oidc.profile;
\ No newline at end of file
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml
index 3f5e9b7..6f9c87d 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml
@@ -14,11 +14,6 @@
p:checkDuringInit="false"
p:postDecodeStrategyFailureIsError="true"
p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier">
- <property name="postDecodeStrategy">
- <bean class="net.shibboleth.sp.oidc.profile.decoding.impl.DecodeStateAsJsonObjectConsumer"
- p:dataSealer-ref="#{'%{sp.dataSealer:shibboleth.DataSealer}'.trim()}"
- p:unsealStatePredicate="#{getObject('%{sp.oidc.sealOAuthStatePredicate:}') ?: ((%{sp.oidc.sealOAuthState:true}) ? getObject('shibboleth.Conditions.TRUE') : getObject('shibboleth.Conditions.FALSE'))}"/>
- </property>
</bean>
</constructor-arg>
</bean>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-beans.xml
index 3983c6d..d513905 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/query/query-beans.xml
@@ -14,11 +14,6 @@
p:checkDuringInit="false"
p:postDecodeStrategyFailureIsError="true"
p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier">
- <property name="postDecodeStrategy">
- <bean class="net.shibboleth.sp.oidc.profile.decoding.impl.DecodeStateAsJsonObjectConsumer"
- p:dataSealer-ref="#{'%{sp.dataSealer:shibboleth.DataSealer}'.trim()}"
- p:unsealStatePredicate="#{getObject('%{sp.oidc.sealOAuthStatePredicate:}') ?: ((%{sp.oidc.sealOAuthState:true}) ? getObject('shibboleth.Conditions.TRUE') : getObject('shibboleth.Conditions.FALSE'))}"/>
- </property>
</bean>
</constructor-arg>
</bean>
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
index df8e5f9..bf089e1 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-beans.xml
@@ -13,119 +13,40 @@
<import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml" />
-
-
- <!--
- Map the state reference from the OAuth state parameter to the stored off resource URL.
- -->
- <bean id="MapStateTokenToResource"
- class="net.shibboleth.sp.profile.impl.MapStateTokenToResource" scope="prototype"
- p:stateTokenLookupStrategy-ref="StateFromStateLookup"
- p:createOutputObjects="true"/>
-
- <!--
- Retrieve the correlation cookie from the state token (reference) in the OAuth state parameter. The cookie is used
- to protect against request forgery, relate response to request, and contains the authn state token used to recover
- authentication state.
-
- Store the cookie value into the context for later inspection.
- -->
- <bean id="ProcessCorrelationCookie" class="net.shibboleth.sp.oidc.profile.impl.ResolveStateCookie" scope="prototype"
- p:cookieManager-ref="shibboleth.sp.RemotedCookieManager"
- p:cookiePrefix="%{sp.correlation.cookiePrefix:__Host-shibsp_req_}"
- p:createOutputObjects="true"
- p:errorFatal="true"
- p:stateTokenLookupStrategy-ref="StateFromStateLookup">
- <property name="cookieValueConsumerStrategy">
- <bean class="net.shibboleth.sp.oidc.profile.impl.SetCorrelationCookieValueToContextConsumer"
- p:correlationCookieStateContextLookupStrategy-ref="shibboleth.ChildLookupOrCreate.CorrelationCookieStateContextFromInboundOAuthStateContext"
- p:dataSealer-ref="#{'%{sp.dataSealer:shibboleth.DataSealer}'.trim()}"
- p:unsealStatePredicate="#{getObject('%{sp.oidc.sealCorrelationCookiePredicate:}') ?: ((%{sp.oidc.sealCorrelationCookie:true}) ? getObject('shibboleth.Conditions.TRUE') : getObject('shibboleth.Conditions.FALSE'))}"/>
- </property>
- </bean>
- <!--
- Retrieve the state token from the the OAuth 2.0 state parameter which is used to map to the correlation cookie
- and the resource URL. The state parameter value will be decoded into the OAuth state context by this point.
- -->
- <bean id="StateFromStateLookup" parent="shibboleth.Functions.Compose">
- <constructor-arg name="g">
- <bean parent="shibboleth.Functions.Compose">
- <constructor-arg name="g">
- <bean class="net.shibboleth.sp.oidc.messaging.navigate.StateFromJSONStateLookupFunction"/>
- </constructor-arg>
- <constructor-arg name="f">
- <bean class="org.opensaml.messaging.context.navigate.ChildContextLookup"
- c:type="#{ T(net.shibboleth.sp.oidc.context.OAuthStateContext) }" />
- </constructor-arg>
- </bean>
- </constructor-arg>
- <constructor-arg name="f" ref="shibboleth.MessageContextLookup.Inbound"/>
- </bean>
- <!--
- Retrieve the stored authentication state/value from the storage manager using the authn state token in the correlation cookie
+ <!--
+
+ Uses the OAuth 2.0 state parameter to reference a browser cookie (by name) that
+ stores the authentication state, or a pointer to that state within the storage
+ manager.
+
+ The presence of a cookie whose name matches the OAuth 2.0 state value enables
+ state verification and provides protection against cross-site request forgery (CSRF).
+ The state value must exactly match the name of the corresponding cookie.
+
+ The cookie name (and therefore the state value) is generated using a
+ cryptographically secure, high-entropy identifier that is infeasible for an attacker
+ to guess. Binding the cookie to the browser session and validating it against the
+ state value returned in the authorization response mitigates CSRF attacks.
+
-->
- <bean id="RecoverAuthenticationState" class="net.shibboleth.sp.oidc.profile.impl.MapStateTokenToStateValue" scope="prototype"
+ <bean id="RecoverStateData"
+ class="net.shibboleth.sp.profile.impl.RecoverStateData" scope="prototype"
+ p:stateTokenLookupStrategy-ref="OAuthStateLookup"
p:errorFatal="true"
- p:stateTokenLookupStrategy-ref="AuthenticationStateFromCorrelationCookieLookup">
- <property name="stateValueConsumer">
- <bean class="net.shibboleth.sp.oidc.profile.impl.SetAuthenticationRequestToPeerContextConsumer"
- p:objectMapper-ref="shibboleth.JSONObjectMapper"
- p:dataSealer-ref="#{'%{sp.dataSealer:shibboleth.DataSealer}'.trim()}"
- p:unsealStatePredicate="#{getObject('%{sp.oidc.sealAuthenticationStatePredicate:}') ?: ((%{sp.oidc.sealAuthenticationState:true}) ? getObject('shibboleth.Conditions.TRUE') : getObject('shibboleth.Conditions.FALSE'))}"
- />
- </property>
- </bean>
+ p:stateDataContextCreationStrategy-ref="shibboleth.ChildLookupOrCreate.StateDataContextFromInbound"
+ p:stateDataClass="net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData"
+ p:createOutputObjects="true"/>
- <bean id="AuthenticationStateFromCorrelationCookieLookup" parent="shibboleth.Functions.Compose">
- <constructor-arg name="g">
- <bean parent="shibboleth.Functions.Compose">
- <constructor-arg name="g">
- <bean class="net.shibboleth.sp.oidc.messaging.navigate.AuthenticationStateFromCorrelationCookieLookupFunction"/>
- </constructor-arg>
- <constructor-arg name="f" ref="shibboleth.ChildLookup.CorrelationCookieStateContextFromOAuthStateContext"/>
- </bean>
- </constructor-arg>
- <constructor-arg name="f" ref="shibboleth.MessageContextLookup.Inbound"/>
- </bean>
+ <bean id="OAuthStateLookup" class="net.shibboleth.sp.oidc.messaging.navigate.StateFromResponseLookupFunction"/>
+
<bean id="ValidateAuthenticationResponseResult" scope="prototype"
class="net.shibboleth.sp.oidc.profile.impl.ValidateAuthenticationResponseResult" />
-
- <bean id="ValidateResponseStateMatchesRequest" scope="prototype"
- class="net.shibboleth.sp.oidc.profile.impl.ValidateResponseState"
- p:rfpTokenFromOAuthStateLookupStrategy-ref="RFPFromOAuthStateLookup"
- p:rfpFromCookieLookupStrategy-ref="RFPFromCorrelationCookieLookup"/>
-
- <!-- Get the Request Forgery Protection value from the correlation cookie -->
- <bean id="RFPFromCorrelationCookieLookup" parent="shibboleth.Functions.Compose">
- <constructor-arg name="g">
- <bean parent="shibboleth.Functions.Compose">
- <constructor-arg name="g">
- <bean class="net.shibboleth.sp.oidc.messaging.navigate.RFPFromCorrelationCookieLookupFunction"/>
- </constructor-arg>
- <constructor-arg name="f" ref="shibboleth.ChildLookup.CorrelationCookieStateContextFromOAuthStateContext"/>
- </bean>
- </constructor-arg>
- <constructor-arg name="f" ref="shibboleth.MessageContextLookup.Inbound"/>
- </bean>
-
- <!-- Get the Request Forgery Protection value from the OAuth 2.0 state parameter -->
- <bean id="RFPFromOAuthStateLookup" parent="shibboleth.Functions.Compose">
- <constructor-arg name="g">
- <bean parent="shibboleth.Functions.Compose">
- <constructor-arg name="g">
- <bean class="net.shibboleth.sp.oidc.messaging.navigate.RFPFromOAuthStateLookupFunction"/>
- </constructor-arg>
- <constructor-arg name="f">
- <bean class="org.opensaml.messaging.context.navigate.ChildContextLookup"
- c:type="#{ T(net.shibboleth.sp.oidc.context.OAuthStateContext) }" />
- </constructor-arg>
- </bean>
- </constructor-arg>
- <constructor-arg name="f" ref="shibboleth.MessageContextLookup.Inbound"/>
- </bean>
+
+ <bean id="InitializePeerEntityContext" class="net.shibboleth.sp.oidc.profile.impl.InitializePeerEntityContext"
+ p:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContextFromInbound"/>
<bean id="InitializeRelyingPartyContextFromOIDCPeer"
class="net.shibboleth.sp.oidc.profile.impl.InitializeRelyingPartyContextFromOIDCPeer" scope="prototype" />
@@ -204,11 +125,11 @@
p:oAuth2ClientAuthenticationContextLookupStrategy-ref="shibboleth.ChildLookupOrCreate.OAuth2ClientAuthenticationContextFromInboundMessageContext">
<property name="redirectUriLookupStrategy">
<bean class="net.shibboleth.sp.oidc.profile.impl.RedirectUriFromStateLookupStrategy" scope="prototype"
- c:authenticationRequestLookupStrategy-ref="shibboleth.ChildLookup.AuthenticationRequestStateDataFromInbound"/>
+ c:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContextFromInbound"/>
</property>
<property name="pkceCodeVerifierLookupStrategy">
<bean class="net.shibboleth.sp.oidc.profile.impl.PKCECodeVerifierFromStateLookupStrategy" scope="prototype"
- c:authenticationRequestLookupStrategy-ref="shibboleth.ChildLookup.AuthenticationRequestStateDataFromInbound"/>
+ c:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContextFromInbound"/>
</property>
</bean>
@@ -428,11 +349,11 @@
<bean id="AuthenticationRequestTimeLookupFunction" scope="prototype"
class="net.shibboleth.sp.oidc.profile.impl.AuthenticationRequestTimeLookupFunction"
p:clockSkew="%{sp.oidc.idtoken.jwt.verifier.authnRequestClockSkew:PT0S}"
- p:authenticationRequestLookupStrategy-ref="shibboleth.ChildLookup.AuthenticationRequestStateDataFromInbound"/>
+ p:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContextFromInbound"/>
<bean id="DefaultAuthTimeActivationCondition" scope="prototype"
class="net.shibboleth.sp.oidc.profile.impl.AuthTimeRequestedActivationCondition"
- c:authenticationRequestLookupStrategy-ref="shibboleth.ChildLookup.AuthenticationRequestStateDataFromInbound"/>
+ c:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContextFromInbound"/>
<bean id="ACRClaimValidator" class="net.shibboleth.oidc.security.jwt.claims.impl.ACRClaimsValidator" scope="prototype"
@@ -443,7 +364,7 @@
<bean id="DefaultRequestedACRClaimLookupStrategy" scope="prototype"
class="net.shibboleth.sp.oidc.profile.impl.RequestedACRClaimsFromStateLookupStrategy"
- c:authenticationRequestLookupStrategy-ref="shibboleth.ChildLookup.AuthenticationRequestStateDataFromInbound"/>
+ c:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContextFromInbound"/>
<bean id="DefaultRequestedACRValidationActivationCondition" scope="prototype"
class="net.shibboleth.sp.oidc.profile.impl.RequestedACRValidationActivationCondition"
@@ -451,8 +372,7 @@
<!-- End ID Token Claims Validation -->
- <bean id="CheckUserInfoRequiredCondition" class=" net.shibboleth.oidc.profile.config.logic.UserInfoLookupPredicate" />
-
+ <bean id="CheckUserInfoRequiredCondition" class=" net.shibboleth.oidc.profile.config.logic.UserInfoLookupPredicate" />
<bean id="UserInfoEndpointLookup" scope="prototype"
class="net.shibboleth.sp.oidc.profile.impl.UserInfoEndpointLookup"
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml
index f8955b3..99f0586 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/oidc-flow.xml
@@ -5,13 +5,11 @@
<action-state id="DoProfileWork">
<!-- <evaluate expression="PopulateMetricContext" />-->
- <evaluate expression="DecodeMessage" />
- <evaluate expression="MapStateTokenToResource" />
- <evaluate expression="ProcessCorrelationCookie"/>
- <evaluate expression="RecoverAuthenticationState" />
- <evaluate expression="ValidateResponseStateMatchesRequest" />
- <evaluate expression="ValidateAuthenticationResponseResult" /> <!-- Needed at the end, so state is unset even if error from OP -->
+ <evaluate expression="DecodeMessage" />
+ <evaluate expression="RecoverStateData" /> <!-- also performs state correlation checking for CSRF protection -->
+ <evaluate expression="ValidateAuthenticationResponseResult" />
+ <evaluate expression="InitializePeerEntityContext"/>
<evaluate expression="ProviderMetadataLookup" />
<evaluate expression="InitializeRelyingPartyContextFromOIDCPeer" />
<evaluate expression="SelectRelyingPartyConfiguration" />
@@ -90,6 +88,7 @@
<transition on="InvalidAcessToken" to="MessageProcessingError" />
<transition on="InvalidIdToken" to="MessageProcessingError" />
<transition on="InvalidUserInfoClaims" to="MessageProcessingError" />
+ <transition on="InputOutputError" to="MessageProcessingError" />
</global-transitions>
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 58d70d2..e05866e 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
@@ -15,7 +15,6 @@
static-field="net.shibboleth.oidc.profile.config.OIDCSSOProfileConfiguration.PROFILE_ID" />
<!-- end -->
-
<bean id="ValidateSessionInitiatorRequest"
class="net.shibboleth.sp.profile.impl.ValidateSessionInitiatorRequest"
scope="prototype"
@@ -263,30 +262,33 @@
class="net.shibboleth.oidc.profile.config.logic.SignRequestObjectPredicate"
p:relyingPartyContextLookupStrategy-ref="shibboleth.ChildLookup.RelyingParty" />
- <!--
- Store off required authentication state via the storage token manager. The token returned from the storage manager
- is referenced in the correlation cookie for later recovery.
- -->
- <bean id="StoreAuthenticationState"
- class="net.shibboleth.sp.oidc.profile.impl.MapStateValueToStateToken" scope="prototype"
- p:createOutputObjects="true"
- p:errorFatal="%{sp.stateToken.errorsFatal:true}"
- p:stateValueLookupStrategy-ref="AuthenticationRequestStateForStorageStrategy"
- p:stateTokenConsumer-ref="AuthenticationStateTokenConsumer" />
-
- <!-- A consumer that stores off the authentication state token (key) -->
- <bean id="AuthenticationStateTokenConsumer"
- class="net.shibboleth.sp.oidc.profile.impl.SetAuthenticationStateTokenConsumer" />
-
- <!-- A strategy for encoding and sealing any state from the authentication request needed for validating the response -->
- <bean id="AuthenticationRequestStateForStorageStrategy"
+ <!--
+ Create the Authentication State Data from the authentication request that is preserved for later recovery
+ in the consumer flow.
+ -->
+ <bean id="CreateAuthenticationStateData" class="net.shibboleth.sp.oidc.profile.impl.CreateAuthenticationStateData"
+ scope="prototype"
+ p:stateDataContextCreationStrategy-ref="shibboleth.ChildLookupOrCreate.StateDataContextFromOutbound"
+ p:stateDataLookupStrategy-ref="AuthenticationRequestStateForStorageStrategy"/>
+
+ <!--
+ A strategy for creating Authentication Stata Data from the authentication request. Preserved and recovered
+ between request and response, needed for validating the response.
+ -->
+ <bean id="AuthenticationRequestStateForStorageStrategy" scope="prototype"
class="net.shibboleth.sp.oidc.profile.impl.AuthenticationRequestStateForStorageStrategy"
p:authenticationAuthorityLookupStrategy-ref="shibboleth.RelyingPartyIdLookup.Simple"
- p:authenticationRequestLookupStrategy-ref="shibboleth.AuthenticationRequestLookup.FromOutbound"
- p:dataSealer-ref="#{'%{sp.dataSealer:shibboleth.DataSealer}'.trim()}"
- p:sealStatePredicate="#{getObject('%{sp.oidc.sealAuthenticationStatePredicate:}') ?: ((%{sp.oidc.sealAuthenticationState:true}) ? getObject('shibboleth.Conditions.TRUE') : getObject('shibboleth.Conditions.FALSE'))}"
- p:objectMapper="#{getObject('%{sp.oidc.jsonObjectMapper:}') ?: getObject('shibboleth.JSONObjectMapper')}" />
-
+ p:authenticationRequestLookupStrategy-ref="shibboleth.AuthenticationRequestLookup.FromOutbound" />
+
+ <!--
+ Store off the authentication state data in the state data context so it can be recovered later when processing
+ the response. By default, failure to store off the state will be fatal.
+ -->
+ <bean id="PreserveState"
+ class="net.shibboleth.sp.profile.PreserveStateDataAction" scope="prototype"
+ p:createOutputObjects="true"
+ p:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContextFromOutbound"
+ p:errorFatal="%{sp.stateToken.errorsFatal:true}" />
<bean id="HandleOutboundMessage" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
scope="prototype" c:messageHandler-ref="PreEncodeMessageHandler" c:executionDirection="OUTBOUND">
@@ -299,12 +301,12 @@
scope="prototype">
<property name="handlers">
<list>
+ <!-- Pull out the state here, rather than during processToken of the PreserveState step -->
<bean id="AddState" class="net.shibboleth.oidc.profile.messaging.handler.impl.AddStateHandler"
scope="prototype">
<property name="parameterValueLookupStrategy">
- <bean class="net.shibboleth.sp.oidc.profile.impl.StateLookupStrategy" scope="prototype"
- p:dataSealer-ref="#{'%{sp.dataSealer:shibboleth.DataSealer}'.trim()}"
- p:sealStatePredicate="#{getObject('%{sp.oidc.sealOAuthStatePredicate:}') ?: ((%{sp.oidc.sealOAuthState:true}) ? getObject('shibboleth.Conditions.TRUE') : getObject('shibboleth.Conditions.FALSE'))}"/>
+ <bean class="net.shibboleth.sp.oidc.profile.impl.StateTokenLookupStrategy" scope="prototype"
+ c:stateDataContextLookupStrategy-ref="shibboleth.ChildLookup.StateDataContextFromOutbound"/>
</property>
</bean>
@@ -339,30 +341,7 @@
</list>
</property>
</bean>
-
- <!--
- Issue the correlation cookie that relates request to response and stores of parameters needed to recover
- authentication state when processing the response.
- -->
- <bean id="IssueRequestCorrelationCookie" class="net.shibboleth.sp.oidc.profile.impl.IssueStateCookie"
- scope="prototype"
- p:cookieManager-ref="shibboleth.sp.RemotedCookieManager"
- p:cookiePrefix="#{'%{sp.correlation.cookiePrefix:__Host-shibsp_req_}'.trim()}"
- p:createOutputObjects="true"
- p:errorFatal="%{sp.stateToken.errorsFatal:true}"
- p:stateValueLookupStrategy-ref="RequestCorrelationCookieFromStateStrategy"/>
-
- <!--
- Create a request correlation cookie. The cookie contains an authentication token that maps back to stored
- authentication state alongside a request forgery protection nonce used to prevent CSRF attacks.
- -->
- <bean id="RequestCorrelationCookieFromStateStrategy"
- class="net.shibboleth.sp.oidc.messaging.navigate.RequestCorrelationFromStateLookupFunction"
- c:authenticationRequestDataStateLookupStrategy-ref="shibboleth.ChildLookup.AuthenticationRequestStateDataFromOutbound"
- c:authenticationRequestLookupStrategy-ref="shibboleth.AuthenticationRequestLookup.FromOutbound"
- p:dataSealer-ref="#{'%{sp.dataSealer:shibboleth.DataSealer}'.trim()}"
- p:sealStatePredicate="#{getObject('%{sp.oidc.sealCorrelationCookiePredicate:}') ?: ((%{sp.oidc.sealCorrelationCookie:true}) ? getObject('shibboleth.Conditions.TRUE') : getObject('shibboleth.Conditions.FALSE'))}"/>
-
+
<bean id="EncodeMessage" class="net.shibboleth.sp.profile.impl.EncodeMessage" scope="prototype"
p:createOutputObjects="true" p:messageEncoderFactory-ref="messageEncoderFactory" />
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 aeee613..f4f38de 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
@@ -47,9 +47,9 @@
<action-state id="BuildOutboundMessage">
<!-- Must map authentication state to a state token first, so we can add the token to the state in HandleOutboundMessage -->
- <evaluate expression="StoreAuthenticationState"/>
- <evaluate expression="HandleOutboundMessage" />
- <evaluate expression="IssueRequestCorrelationCookie" />
+ <evaluate expression="CreateAuthenticationStateData"/>
+ <evaluate expression="PreserveState"/>
+ <evaluate expression="HandleOutboundMessage" />
<evaluate expression="EncodeMessage" />
<evaluate expression="'proceed'" />
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml
index f78af5c..cf87cb0 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/oidc-common-beans.xml
@@ -73,11 +73,16 @@
class="net.shibboleth.profile.context.navigate.SpringExpressionContextLookupFunction"
c:_0="#{ T(net.shibboleth.oidc.metadata.context.OIDCProviderMetadataContext) }"
c:outputType="#{T(com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata)}"
- c:expression="#input.getProviderInformation()" />
-
- <bean id="shibboleth.ChildLookup.AuthnRequestStateDataContext"
+ c:expression="#input.getProviderInformation()" />
+
+ <bean id="shibboleth.ChildLookup.StateDataContext"
+ class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+ c:type="#{ T(net.shibboleth.sp.context.StateDataContext) }" />
+
+ <bean id="shibboleth.ChildLookupOrCreate.StateDataContext"
class="org.opensaml.messaging.context.navigate.ChildContextLookup"
- c:type="#{ T(net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext) }" />
+ c:type="#{ T(net.shibboleth.sp.context.StateDataContext) }"
+ c:createContext="true" />
<bean id="shibboleth.ChildLookup.OAuth2ClientContext"
class="org.opensaml.messaging.context.navigate.ChildContextLookup"
@@ -218,26 +223,46 @@
</constructor-arg>
</bean>
- <bean id="shibboleth.ChildLookup.AuthenticationRequestStateDataFromInbound"
+ <bean id="shibboleth.ChildLookup.StateDataContextFromOutbound"
parent="shibboleth.Functions.Compose">
<constructor-arg name="g">
- <ref bean="shibboleth.ChildLookup.AuthnRequestStateDataContext" />
+ <ref bean="shibboleth.ChildLookup.StateDataContext" />
</constructor-arg>
<constructor-arg name="f">
- <ref bean="shibboleth.MessageContextLookup.Inbound" />
+ <ref bean="shibboleth.MessageContextLookup.Outbound" />
</constructor-arg>
</bean>
- <bean id="shibboleth.ChildLookup.AuthenticationRequestStateDataFromOutbound"
+ <bean id="shibboleth.ChildLookupOrCreate.StateDataContextFromOutbound"
parent="shibboleth.Functions.Compose">
<constructor-arg name="g">
- <ref bean="shibboleth.ChildLookup.AuthnRequestStateDataContext" />
+ <ref bean="shibboleth.ChildLookupOrCreate.StateDataContext" />
</constructor-arg>
<constructor-arg name="f">
<ref bean="shibboleth.MessageContextLookup.Outbound" />
</constructor-arg>
</bean>
+ <bean id="shibboleth.ChildLookup.StateDataContextFromInbound"
+ parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <ref bean="shibboleth.ChildLookup.StateDataContext" />
+ </constructor-arg>
+ <constructor-arg name="f">
+ <ref bean="shibboleth.MessageContextLookup.Inbound" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="shibboleth.ChildLookupOrCreate.StateDataContextFromInbound"
+ parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g">
+ <ref bean="shibboleth.ChildLookupOrCreate.StateDataContext" />
+ </constructor-arg>
+ <constructor-arg name="f">
+ <ref bean="shibboleth.MessageContextLookup.Inbound" />
+ </constructor-arg>
+ </bean>
+
<!-- Common Actions -->
<bean id="ProviderMetadataLookup" parent="WebFlowInboundMessageHandlerAdaptor" scope="prototype">
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
index 3b41faf..fba5ada 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/sp/service/agent/postconfig.xml
@@ -101,7 +101,7 @@
p:userInfoHttpRequestMethod="%{sp.oidc.userinfo.httpRequestMethod:GET}"
p:scopes="%{sp.oidc.scopes:#{null}}"
p:responseModes="%{sp.oauth2.responseModes:}"
- p:shouldValidateAcrValue="%{sp.oidc.idToken.validateAcrValue:true}">
+ p:validateAcrValue="%{sp.oidc.idToken.validateAcrValue:true}">
</bean>
<util:constant id="OIDC.SSO.FEATURE_ESSENTIAL_ACR_REQUEST"
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 4e8c81e..5439d38 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
@@ -11,13 +11,10 @@
# The OP's keyset document fetch interval i.e. how long should keys from the OP be cached.
#sp.oidc.keyfetch.interval = PT30M
-
# IMPORTANT: The below settings are for intermediate cases which should be regarded experimental in the alpha version.
-
# Global Request And Response Settings
-
# Comma seperated list of additional scopes e.g. PROFILE or EMAIL. The openid scope is added by default.
#sp.oidc.scopes =
# The HTTP method use to send the authorization request
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java
new file mode 100644
index 0000000..d7900e2
--- /dev/null
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/AbstractOIDCTokenConsumerFlowTest.java
@@ -0,0 +1,533 @@
+/*
+ * 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.flows;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.HttpHost;
+import org.apache.hc.core5.http.io.HttpClientResponseHandler;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.mockito.Mockito;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.BeforeMethod;
+
+import com.nimbusds.jose.EncryptionMethod;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEAlgorithm;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.oauth2.sdk.AuthorizationCode;
+import com.nimbusds.oauth2.sdk.AuthorizationResponse;
+import com.nimbusds.oauth2.sdk.ErrorObject;
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.oauth2.sdk.TokenErrorResponse;
+import com.nimbusds.oauth2.sdk.TokenResponse;
+import com.nimbusds.oauth2.sdk.id.State;
+import com.nimbusds.oauth2.sdk.token.AccessToken;
+import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
+import com.nimbusds.oauth2.sdk.token.RefreshToken;
+import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
+import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
+import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
+import com.nimbusds.openid.connect.sdk.claims.UserInfo;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.oidc.security.credential.JWKCredential;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.net.URISupport;
+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.oidc.testing.TestHelper;
+import net.shibboleth.sp.profile.ConsumerConstants;
+
+/**
+ * An abstract test class for OIDC token consumer flow tests.
+ * Provides helper methods to construct OIDC responses and mock endpoints.
+ */
+public abstract class AbstractOIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
+
+ /** Dummy signing key of the dummy OP we are testing against. */
+ @Autowired @Qualifier("dummy.op.signing.Credential") protected JWKCredential opSigningCredential;
+
+ /** Dummy encryption key of the RP/SP. */
+ @Autowired @Qualifier("dummy.sp.encryption.Credential") protected JWKCredential rpEncryptionCredential;
+
+ /** The mocked HttpClient to use when responding to Token and UserInfo requests.*/
+ private HttpClient httpClient;
+
+ /** The OP metadata to use.*/
+ private OIDCProviderMetadata metadata;
+
+ /**
+ * Constructor.
+ *
+ * @param id the flow id
+ */
+ protected AbstractOIDCTokenConsumerFlowTest(@Nonnull final String id) {
+ super(id);
+
+ }
+
+ /**
+ * Pre-test work.
+ *
+ * @throws Exception on error
+ */
+ @BeforeMethod
+ public void beforeMethod() throws Exception{
+ setDefaultAuth();
+ final ApplicationContext applicationContext2 = applicationContext;
+ // Check the mock HTTP client has been defined
+ if (applicationContext2 != null) {
+ httpClient = applicationContext2.getBean("Mock.HttpClient", HttpClient.class);
+ } else {
+ fail("Mocked Http Client could not be found");
+ }
+ if (httpClient == null) {
+ fail("Mocked Http Client could not be found");
+ }
+ // Add a default metadata response
+ final var metadataFromFile = new ClassPathResource("metadata/openid-configuration.json");
+ final String json = new String(metadataFromFile.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
+ metadata = OIDCProviderMetadata.parse(JSONObjectUtils.parse(json));
+
+ }
+
+ /**
+ * Construct a successful OIDC token response with signed id_token.
+ *
+ * @param expiry expiry time
+ * @param issuedAt issue time
+ * @param nonce the nonce
+ * @return the tokens
+ * @throws ParseException
+ * @throws JOSEException
+ */
+ protected OIDCTokenResponse constructSuccessfulTokenResponseSigned(
+ @Nonnull final Instant expiry, @Nonnull final Instant issuedAt, @Nullable final String nonce,
+ @Nullable final Map<String, Object> additionalClaims)
+ throws JOSEException, ParseException {
+
+ final JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder()
+ .subject("jdoe")
+ .issuer(TestConstants.ISSUER)
+ .audience(TestConstants.CLIENT_ID)
+ .expirationTime(Date.from(expiry))
+ .issueTime(Date.from(issuedAt));
+
+ if (nonce != null) {
+ claimsSetBuilder.claim("nonce", nonce);
+ }
+ if (additionalClaims != null) {
+ additionalClaims.entrySet()
+ .stream()
+ .forEach(entry -> claimsSetBuilder.claim(entry.getKey(), entry.getValue()));
+ }
+
+ final JWTClaimsSet claimsSet = claimsSetBuilder.build();
+
+
+ final JWT signedIdToken =
+ TestHelper.createJWT(claimsSet, JWSAlgorithm.RS256, null, null, opSigningCredential, null);
+
+ final AccessToken accessToken = new BearerAccessToken("fake-access-token-value", 3600, null);
+ final RefreshToken refreshToken = new RefreshToken("fake-refresh-token-value");
+ return new OIDCTokenResponse((new OIDCTokens(signedIdToken, accessToken, refreshToken)));
+ }
+
+ /**
+ * Construct an error OIDC token response.
+ *
+ * @param code the error code
+ * @param description the error description
+ * @param httpCode the http code to return
+ */
+ protected TokenErrorResponse constructErrorTokenResponseSigned(
+ final String code, final String description, final int httpCode)
+ throws JOSEException, ParseException {
+
+ final ErrorObject error = new ErrorObject(code, description, httpCode);
+ final TokenErrorResponse errorResponse = new TokenErrorResponse(error);
+
+ return errorResponse;
+ }
+
+ /**
+ * Construct a successful OIDC token response with a signed and encrypted id_token.
+ *
+ * @param expiry expiry time
+ * @param issuedAt issue time
+ * @param nonce the nonce
+ * @return the tokens
+ * @throws ParseException
+ * @throws JOSEException
+ */
+ protected OIDCTokenResponse constructSuccessfulTokenResponseSignedAndEncrypted(
+ @Nonnull final Instant expiry, @Nonnull final Instant issuedAt, @Nullable final String nonce,
+ @Nullable final Map<String, Object> additionalClaims)
+ throws JOSEException, ParseException {
+
+ final JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder()
+ .subject("jdoe")
+ .issuer(TestConstants.ISSUER)
+ .audience(TestConstants.CLIENT_ID)
+ .expirationTime(Date.from(expiry))
+ .issueTime(Date.from(issuedAt));
+
+ if (nonce != null) {
+ claimsSetBuilder.claim("nonce", nonce);
+ }
+ if (additionalClaims != null) {
+ additionalClaims.entrySet()
+ .stream()
+ .forEach(entry -> claimsSetBuilder.claim(entry.getKey(), entry.getValue()));
+ }
+ final JWTClaimsSet claimsSet = claimsSetBuilder.build();
+
+ final JWT encryptedIdToken =
+ TestHelper.createJWT(claimsSet, JWSAlgorithm.RS256, JWEAlgorithm.RSA_OAEP_256,
+ EncryptionMethod.A128CBC_HS256, opSigningCredential, rpEncryptionCredential);
+
+ final AccessToken accessToken = new BearerAccessToken("fake-access-token-value", 3600, null);
+ final RefreshToken refreshToken = new RefreshToken("fake-refresh-token-value");
+ return new OIDCTokenResponse((new OIDCTokens(encryptedIdToken, accessToken, refreshToken)));
+ }
+
+ /**
+ * Construct a successful OIDC user info response in JSON format. application/json.
+ *
+ * @return the user info response
+ */
+ protected UserInfoSuccessResponse constructJSONUserInfoResponse() {
+ final JSONObject json = new JSONObject();
+ json.appendField("sub", "jdoe");
+ json.appendField("name", "John Doe");
+ json.appendField("nickname" , "John");
+ return new UserInfoSuccessResponse(new UserInfo(json));
+ }
+
+ /**
+ * Construct a successful OIDC user info response in JWT format, signed.
+ *
+ * @return the user info response
+ * @throws ParseException
+ * @throws JOSEException
+ */
+ protected UserInfoSuccessResponse constructJWTUserInfoResponseSigned() throws JOSEException, ParseException {
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .subject("jdoe")
+ .issuer(TestConstants.ISSUER)
+ .audience(TestConstants.CLIENT_ID)
+ .claim("name", "John Doe")
+ .claim("nickname","John").build();
+
+ final JWT signedUserInfoToken =
+ TestHelper.createJWT(claimsSet, JWSAlgorithm.RS256, null, null, opSigningCredential, null);
+ return new UserInfoSuccessResponse(signedUserInfoToken);
+ }
+
+ /**
+ * Mock OIDC Token, UserInfo, and Keys endpoints. Returning the supplied responses.
+ * Noting these mocks may not exercise the response decoders, as the responses are returned directly.
+ *
+ * @param tokenResponse the token response
+ * @param userInfoResponse the user info response
+ *
+ * @throws IOException on error.
+ */
+ protected void mockOIDCEndpoints(final TokenResponse tokenResponse,
+ final UserInfoSuccessResponse userInfoResponse) throws IOException {
+
+ Mockito.when(httpClient.execute(
+ Mockito.argThat(req -> req != null && req.getRequestUri().toString()
+ .contains(".well-known/openid-configuration")),
+ Mockito.any(HttpContext.class),
+ Mockito.any(HttpClientResponseHandler.class)))
+ .thenReturn(metadata);
+
+ Mockito.when(httpClient.execute(
+ Mockito.argThat(req -> req != null && req.getRequestUri().toString().contains("/token")),
+ Mockito.any(HttpContext.class),
+ Mockito.any(HttpClientResponseHandler.class)))
+ .thenReturn(tokenResponse);
+
+ Mockito.when(httpClient.execute(
+ Mockito.argThat(req -> req != null && req.getRequestUri().toString().contains("/userinfo")),
+ Mockito.any(HttpContext.class),
+ Mockito.any(HttpClientResponseHandler.class)))
+ .thenReturn(userInfoResponse);
+
+ // Fetch from the keys endpoint mocking the call used by the RemoteJwkSetCache
+ final String jwk = TestHelper.createJWKJSONStringFrom(opSigningCredential);
+ assertNotNull(jwk);
+ final ClassicHttpResponse classicHttpResponse = Mockito.mock(ClassicHttpResponse.class);
+ Mockito.when(classicHttpResponse.getCode()).thenReturn(200);
+ Mockito.when(classicHttpResponse.getEntity())
+ .thenReturn(new StringEntity("{\"keys\":["+jwk+"]}", ContentType.APPLICATION_JSON));
+
+ Mockito.when(httpClient.executeOpen(
+ Mockito.nullable(HttpHost.class),
+ Mockito.argThat(req -> req != null && req.getRequestUri().toString().contains("/keys")),
+ Mockito.any(HttpContext.class)))
+ .thenReturn(classicHttpResponse);
+ }
+
+ /**
+ * Check that the output message contains an instruction to unset cookies using Max-Age=0.
+ *
+ * @param output the DDF output
+ */
+ protected void validateCookiesAreUnset(@Nullable final DDF output) {
+ if (output == null) {
+ fail("DDF output can not be null");
+ }
+ // Check cookies are unset
+ final DDF headers = output.getmember("http.headers");
+ // Check that any Set-Cookie is an unset, we do not set new cookies in the consumer flow
+ if (headers.islist()) {
+ headers.forEach(header -> {
+ if (header.isstring() && "Set-Cookie".equals(header.name())){
+ Assert.assertTrue(header.string().contains("Max-Age=0"),"Cookies must be unset");
+ }
+ });
+ }
+ }
+
+ /**
+ * Decode an encoded response and run assertion checks against it.
+ *
+ * @param result
+ * flow execution result
+ * @param claimNames
+ * set of claims to check for
+ * @param resource
+ * resource URL used in final redirect
+ *
+ * @return the output object
+ */
+ @Nonnull protected DDF validateOutputMessage(@Nonnull final FlowExecutionResult result,
+ @Nullable final Set<String> claimNames, @Nullable final String resource) {
+
+ final ProfileRequestContext prc = retrieveProfileRequestContext(result);
+ assert prc != null;
+ final AgentRequestContext arc = prc.ensureSubcontext(AgentRequestContext.class);
+ final DDF output = arc.getOutput();
+
+ assert output != null;
+ Assert.assertTrue(output.isstruct());
+ final DDF http = output.getmember(RemotedHttpServletRequest.STRUCTURE_NAME);
+ Assert.assertTrue(http.isstruct());
+
+ final byte[] redirect = http.getmember(RemotedHttpServletResponse.REDIRECT).unsafe_string();
+ Assert.assertEquals(resource != null ? resource.getBytes(StandardCharsets.UTF_8) : null, redirect);
+
+ final Set<String> mutableIds = new HashSet<>(claimNames);
+ for (final DDF attr : output.getmember(ConsumerConstants.SESSION_ATTRIBUTES).asList()) {
+ Assert.assertTrue(mutableIds.contains(attr.name()));
+ mutableIds.remove(attr.name());
+ if ("mail".equals(attr.name())) {
+ Assert.assertEquals(attr.asList().stream().map(DDF::string).toList(),
+ CollectionSupport.singletonList("jdoe at example.org"));
+ } else if ("displayName".equals(attr.name())) {
+ Assert.assertEquals(attr.asList().stream().map(DDF::string).toList(),
+ CollectionSupport.singletonList("John Doe"));
+ } else if ("eduPersonScopedAffiliation".equals(attr.name())) {
+ Assert.assertEquals(attr.asList().stream().map(ddf -> {return ddf.getmember("value").string();}).toList(),
+ CollectionSupport.listOf("member"));
+ Assert.assertEquals(attr.asList().stream().map(ddf -> {return ddf.getmember("scope").string();}).toList(),
+ CollectionSupport.listOf("example.org"));
+ }
+ }
+ Assert.assertTrue(mutableIds.isEmpty());
+
+ validateCookiesAreUnset(output);
+ //TODO ADD BACK
+
+// try {
+// final DDF s = output.getmember(ConsumerConstants.SESSION_OPAQUE);
+// assert s != null;
+// Assert.assertTrue(s.isstruct());
+// final DDF nameIdDdf = s.getmember("nameID");
+// Assert.assertTrue(nameIdDdf.isstring());
+// final String nameIdString = nameIdDdf.string();
+// assert nameIdString != null;
+// final byte[] opaque = Base64Support.decodeURLSafe(nameIdString);
+// try (final ByteArrayInputStream in = new ByteArrayInputStream(opaque)) {
+// final XMLObject obj = XMLObjectSupport.unmarshallFromInputStream(parserPool, in);
+// if (obj instanceof final NameID nameID) {
+// Assert.assertEquals(nameID.getValue(), "jdoe at example.org");
+// Assert.assertEquals(nameID.getFormat(), NameIDType.EMAIL);
+// Assert.assertEquals(nameID.getSPProvidedID(), sessionIndex);
+// } else {
+// Assert.fail("Session data was not a NameID");
+// }
+// }
+// } catch (final DecodingException|IOException|UnmarshallingException|XMLParserException e) {
+// Assert.fail(e.getMessage());
+// }
+
+ return output;
+
+ }
+
+
+ /**
+ * Builds a dummy OIDC authorization code response.
+ *
+ * @param redirectURI
+ * redirect URI
+ * @param mode
+ * response mode
+ *
+ * @return authentication success response for the token consumer flow
+ */
+ protected AuthenticationSuccessResponse buildOIDCAuthorizationCodeResponse(
+ @Nonnull final String redirect, @Nonnull final ResponseMode mode, final String stateString) {
+
+ final AuthorizationCode code = new AuthorizationCode("dummy-auth-code");
+ final State state = new State(stateString);
+ URI redirectURI = null;
+ try {
+ redirectURI = new URI(redirect);
+ } catch (final URISyntaxException e) {
+ fail("Bad redirect URI", e);
+ }
+
+ // Build a dummy success response (authorization code)
+ return new AuthenticationSuccessResponse(
+ redirectURI,
+ code,
+ null,
+ null,
+ state,
+ null,
+ mode
+ );
+ }
+
+ /**
+ * Build a invalid_request error response, from openID connect core.
+ *
+ * @return the error response
+ */
+ protected AuthenticationErrorResponse buildErrorResponse(final String stateString) {
+
+ final URI redirectURI = URI.create(TestConstants.RESPONSE_URL);
+ final State state = new State(stateString);
+
+ final ErrorObject error = new ErrorObject(
+ "invalid_request",
+ "Unsupported response_type value",
+ 400,
+ null);
+
+ return new AuthenticationErrorResponse(
+ redirectURI,
+ error,
+ state,
+ ResponseMode.QUERY);
+
+ }
+
+
+ /**
+ * Encodes an OIDC response into the query string of a redirect URI embedded in a remoted message.
+ *
+ * @param response OIDC response to encode
+ *
+ * @return input object suitable for token consumer flow
+ *
+ * @throws IOException on error
+ */
+ @Nonnull protected DDF buildRemotedQueryStringResponse(@Nonnull final AuthorizationResponse response) {
+
+ final DDF obj = new DDF(null).structure();
+ obj.addmember(ConsumerConstants.BASE_URL).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
+ final DDF http = obj.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
+
+ http.addmember(RemotedHttpServletRequest.METHOD).string("GET");
+ http.addmember(RemotedHttpServletRequest.REMOTE_ADDR).string("192.168.1.1");
+ http.addmember(RemotedHttpServletRequest.REQUEST_URL).unsafe_string(TestConstants.RESPONSE_URL.getBytes(StandardCharsets.UTF_8));
+ final URI responseURI = response.toURI();
+ http.addmember(RemotedHttpServletRequest.QUERY_STRING).string(Constraint
+ .isNotNull(responseURI.getQuery(), "Query string is null"));
+ return obj;
+ }
+
+ /**
+ * Encodes an OIDC response into a form_post embedded in a remoted message.
+ *
+ * @param response OIDC response to encode
+ *
+ * @return input object suitable for token consumer flow
+ *
+ * @throws IOException on error
+ */
+ @Nonnull protected DDF buildRemotedFormPostResponse(@Nonnull final AuthorizationResponse response) {
+
+ final DDF obj = new DDF(null).structure();
+ obj.addmember(ConsumerConstants.BASE_URL).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
+ final DDF http = obj.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
+
+ http.addmember(RemotedHttpServletRequest.METHOD).string("POST");
+ http.addmember(RemotedHttpServletRequest.REMOTE_ADDR).string("192.168.1.1");
+ http.addmember(RemotedHttpServletRequest.REQUEST_URL).unsafe_string(
+ TestConstants.RESPONSE_URL.getBytes(StandardCharsets.UTF_8));
+ http.addmember(RemotedHttpServletRequest.CONTENT_TYPE).string("application/x-www-form-urlencoded");
+ if (response instanceof final AuthenticationSuccessResponse successResponse) {
+ http.addmember(RemotedHttpServletRequest.BODY).unsafe_string(Constraint.isNotNull(URISupport.buildQuery(
+ CollectionSupport.listOf(new Pair<>("code", successResponse.getAuthorizationCode().getValue()),
+ new Pair<>("state", response.getState().getValue()))), "Query string is null").getBytes(StandardCharsets.UTF_8));
+ }
+
+ return obj;
+ }
+
+}
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
index 4595c27..8cf10fd 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCSessionInitiatorFlowTest.java
@@ -34,7 +34,6 @@ import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.mockito.Mockito;
import org.opensaml.messaging.decoder.MessageDecodingException;
-import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
@@ -67,7 +66,6 @@ import net.shibboleth.sp.messaging.RemotedHttpServletResponse;
import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
-import net.shibboleth.sp.profile.impl.IssueCorrelationCookie;
@ContextConfiguration(
locations = {
@@ -162,7 +160,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
- setApplicationRequest("test-oidc-application-with-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -197,26 +195,6 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
assertNotNull(request.getCodeChallengeMethod().compareTo(CodeChallengeMethod.S256));
}
- /**
- * Basic flow test but where the resource URL is not supplied.
- *
- * @throws IOException on error
- * @throws MessageDecodingException
- */
- @Test
- public void testFailure_NoTarget() throws IOException, MessageDecodingException {
- setDefaultAuth();
-
- final DDF input = new DDF(null).structure();
- input.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
- input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
- setApplicationRequest("test-oidc-application-with-ro", input);
-
- final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
- assertFlowExecutionResult(result, FLOW_ID);
- assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
- }
-
/**
* Basic flow test which uses the default profile configuration.
*
@@ -255,7 +233,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
input.addmember(SPConstants.STATE).string("state");
- setApplicationRequest("test-oidc-application-without-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -278,7 +256,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
input.addmember(OIDCInitiatorConstants.FORCE_AUTHN).integer(1);
- setApplicationRequest("test-oidc-application-with-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -303,7 +281,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
input.addmember(OIDCInitiatorConstants.MAX_AGE).longinteger(60l);
- setApplicationRequest("test-oidc-application-with-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -327,7 +305,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
input.addmember(OIDCInitiatorConstants.PROMPT).string("none");
- setApplicationRequest("test-oidc-application-with-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -354,7 +332,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
aclist.add(new DDF(null).string("loa1"));
aclist.add(new DDF(null).string("loa2"));
- setApplicationRequest("test-oidc-application-with-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -423,7 +401,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
resourceList.add(new DDF(null).string("https://cal.example.com"));
resourceList.add(new DDF(null).string("https://mail.example.com"));
- setApplicationRequest("test-oidc-application-with-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -449,7 +427,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
input.addmember(OIDCInitiatorConstants.SCOPE).string("email");
- setApplicationRequest("test-oidc-application-with-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -473,7 +451,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
input.addmember(OIDCInitiatorConstants.UI_LOCALES).string("fr-CA fr en");
- setApplicationRequest("test-oidc-application-with-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -498,7 +476,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
input.addmember(InitiatorConstants.RESPONSE_URL).string(RESPONSE_URL);
input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
input.addmember(OIDCInitiatorConstants.DISPLAY).string("page");
- setApplicationRequest("test-oidc-application-with-ro", input);
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertFlowExecutionResult(result, FLOW_ID);
@@ -580,7 +558,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
if ("Set-Cookie".equals(header.name())) {
final String cookie = header.string();
assert cookie != null;
- if (cookie.startsWith("__Host-" + IssueCorrelationCookie.DEFAULT_COOKIE_PREFIX)) {
+ // if (cookie.startsWith("__Host-" + IssueCorrelationCookie.DEFAULT_COOKIE_PREFIX)) {
//TODO our use of the correlation cookie
// final Boolean passive = authnRequest.isPassive();
@@ -589,7 +567,7 @@ public class OIDCSessionInitiatorFlowTest extends AbstractSPFlowTest {
// "__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);
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
index 479f0a2..26f52a2 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
@@ -14,89 +14,35 @@
package net.shibboleth.sp.oidc.flows;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.fail;
-
import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
-import java.text.ParseException;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
-import java.util.HashSet;
import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.apache.hc.client5.http.classic.HttpClient;
-import org.apache.hc.core5.http.ClassicHttpResponse;
-import org.apache.hc.core5.http.ContentType;
-import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.io.HttpClientResponseHandler;
-import org.apache.hc.core5.http.io.entity.StringEntity;
-import org.apache.hc.core5.http.protocol.HttpContext;
-import org.mockito.Mockito;
import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.context.ApplicationContext;
-import org.springframework.core.io.ClassPathResource;
+import org.springframework.test.annotation.DirtiesContext;
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.BeforeMethod;
import org.testng.annotations.Test;
-import com.nimbusds.jose.EncryptionMethod;
-import com.nimbusds.jose.JOSEException;
-import com.nimbusds.jose.JWEAlgorithm;
-import com.nimbusds.jose.JWSAlgorithm;
-import com.nimbusds.jwt.JWT;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.oauth2.sdk.AuthorizationCode;
-import com.nimbusds.oauth2.sdk.AuthorizationResponse;
-import com.nimbusds.oauth2.sdk.ErrorObject;
import com.nimbusds.oauth2.sdk.ResponseMode;
-import com.nimbusds.oauth2.sdk.TokenErrorResponse;
-import com.nimbusds.oauth2.sdk.TokenResponse;
-import com.nimbusds.oauth2.sdk.id.State;
-import com.nimbusds.oauth2.sdk.token.AccessToken;
-import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
-import com.nimbusds.oauth2.sdk.token.RefreshToken;
-import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
-import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
-import com.nimbusds.openid.connect.sdk.OIDCTokenResponse;
-import com.nimbusds.openid.connect.sdk.UserInfoSuccessResponse;
-import com.nimbusds.openid.connect.sdk.claims.UserInfo;
-import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
-import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
-import net.minidev.json.JSONObject;
import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
import net.shibboleth.idp.test.PreferFileSystemApplicationContextInitializer;
-import net.shibboleth.oidc.security.credential.JWKCredential;
import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.collection.Pair;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.net.URISupport;
-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.oidc.testing.TestHelper;
-import net.shibboleth.sp.profile.ConsumerConstants;
/**
- * Unit test for the SP session-initiator flow.
+ * Unit test for the SP token-consumer flow.
+ *
+ * <p>Warning, each test method dirties the context, and Spring will throw away everything and re-initialise
+ * the tests. This allows for things like replay caches to work correctly, at the expense of test execution performance
+ * </p>
*/
@ContextConfiguration(
locations = {
@@ -110,19 +56,9 @@ import net.shibboleth.sp.profile.ConsumerConstants;
)
@WebAppConfiguration
@SuppressWarnings({ "unchecked", "rawtypes", "null" })
-public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
-
- /** Dummy signing key of the dummy OP we are testing against. */
- @Autowired @Qualifier("dummy.op.signing.Credential") protected JWKCredential opSigningCredential;
-
- /** Dummy encryption key of the RP/SP. */
- @Autowired @Qualifier("dummy.sp.encryption.Credential") protected JWKCredential rpEncryptionCredential;
-
- /** The mocked HttpClient to use when responding to Token and UserInfo requests.*/
- private HttpClient httpClient;
+ at DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class OIDCTokenConsumerFlowTest extends AbstractOIDCTokenConsumerFlowTest {
- /** The OP metadata to use.*/
- private OIDCProviderMetadata metadata;
/** Constructor. */
public OIDCTokenConsumerFlowTest() {
@@ -134,23 +70,10 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
*
* @throws Exception on error
*/
+ @Override
@BeforeMethod
public void beforeMethod() throws Exception{
- setDefaultAuth();
- final ApplicationContext applicationContext2 = applicationContext;
- // Check the mock HTTP client has been defined
- if (applicationContext2 != null) {
- httpClient = applicationContext2.getBean("Mock.HttpClient", HttpClient.class);
- } else {
- fail("Mocked Http Client could not be found");
- }
- if (httpClient == null) {
- fail("Mocked Http Client could not be found");
- }
- // Add a default metadata response
- final var metadataFromFile = new ClassPathResource("metadata/openid-configuration.json");
- final String json = new String(metadataFromFile.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
- metadata = OIDCProviderMetadata.parse(JSONObjectUtils.parse(json));
+ super.beforeMethod();
}
@@ -168,18 +91,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -207,18 +126,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.FORM_POST,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedFormPostResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID_POST_RESPONSE_MODE,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID_POST_RESPONSE_MODE, input);
@@ -245,18 +160,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID_PRIVATE_KEY_JWT,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
// Use application which requires private_key_jwt
setApplicationRequest(TestConstants.APPLICATION_ID_PRIVATE_KEY_JWT, input);
@@ -284,18 +195,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(Duration.ofMinutes(1), true, null)));
+ TestConstants.buildAuthenticationState(Duration.ofMinutes(1), true, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -322,18 +229,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(Duration.ofMinutes(1), true, null)));
+ TestConstants.buildAuthenticationState(Duration.ofMinutes(1), true, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -358,18 +261,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -396,18 +295,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -435,18 +330,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1"))));
+ TestConstants.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1")), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -476,18 +367,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1"))));
+ TestConstants.buildAuthenticationState(null, false, CollectionSupport.listOf("loa1")), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -515,18 +402,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -552,18 +435,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -589,18 +468,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -622,18 +497,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
@@ -656,18 +527,14 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -682,27 +549,23 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
}
/**
- * Test failure, the RFP value in the returned state does not match that stored in the
- * correlation cookie.
+ * Test failure, the OAuth 2.0 state value does match to a state cookie which provides CSRF protection alongside
+ * the authentication state.
*
* @throws IOException on error
*/
@Test
- public void testFail_RequestForgeryProtectionValueMisMatch() throws Exception {
+ public void testFail_StateValueMisMatch() throws Exception {
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ "different-state");
final DDF input = buildRemotedQueryStringResponse(response);
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- "bad-rfp", //this is a bad rfp value in the cookie
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -712,38 +575,7 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
final DDF output = assertOutputMessageEvent(result, EventIds.MESSAGE_PROC_ERROR);
System.out.println("test output: " + output.toString());
}
-
- /**
- * Test failure: the correlation cookie state token in the OAuth state parameter does not reference the
- * correlation cookie. Hence the correlation cookie can not be recovered, which is a fatal error.
- *
- * @throws IOException on error
- */
- @Test
- public void testFail_CorrelationCookieMismatch() throws Exception {
- final AuthenticationSuccessResponse response =
- buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.AUTHENTICATION_STATE_WRONG,
- TestConstants.AUTHENTICATION_RFP));
- final DDF input = buildRemotedQueryStringResponse(response);
-
- // Add cookies
- input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
- TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
- TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
-
- setApplicationRequest(TestConstants.APPLICATION_ID, input);
-
- final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
- assertFlowExecutionResult(result, TestConstants.FLOW_ID);
- assertFlowExecutionOutcome(result.getOutcome());
- assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
- }
-
+
/**
* Test error response from the OP.
@@ -752,18 +584,13 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
*/
@Test
public void testFail_ErrorResponse() throws Exception {
- final DDF input = buildRemotedQueryStringResponse(buildErrorResponse(
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP)));
+ final DDF input = buildRemotedQueryStringResponse(buildErrorResponse(TestConstants.STATE_TOKEN));
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -777,24 +604,23 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
/**
* Test error response from OP followed by a replayed success response. It should fail as the state has been
- * replayed.
+ * replayed. That is, if the state originally sent that resulted in an error from the OP, where added to a
+ * forged success response, it will not be allowed by the SP even if it matches to the one in stored state (a cookie
+ * for example).
*
* @throws IOException on error
*/
- //@Test
+ @Test
public void testFail_ErrorResponse_FollowedByReplayedSuccess() throws Exception {
- final DDF input = buildRemotedQueryStringResponse(buildErrorResponse(
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP)));
+
+ // First create a failed response
+ final DDF input = buildRemotedQueryStringResponse(buildErrorResponse(TestConstants.STATE_TOKEN));
// Add cookies
input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, input);
@@ -805,23 +631,19 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
System.out.println("DDF: "+output.toString());
validateCookiesAreUnset(output);
- // Now construct a valid response, as the state is re-used this should fail early, and will not need the
- // mocked endpoints etc.
+ // Now construct a valid response using the state sent in the failed response, as the state is re-used this
+ // should fail early.
final AuthenticationSuccessResponse response =
buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
- TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_RFP));
+ TestConstants.STATE_TOKEN);
final DDF inputSuccessResponse = buildRemotedQueryStringResponse(response);
// Add cookies
inputSuccessResponse.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
TestConstants.STATE_TOKEN,
- TestConstants.AUTHENTICATION_STATE_TOKEN,
TestConstants.APPLICATION_ID,
- TestConstants.AUTHENTICATION_RFP,
- TestConstants.TARGET_URL,
- TestConstants.buildAuthenticationState(null, false, null)));
+ TestConstants.buildAuthenticationState(null, false, null), true));
setApplicationRequest(TestConstants.APPLICATION_ID, inputSuccessResponse);
@@ -829,411 +651,11 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
assertFlowExecutionResult(resultReplayFail, TestConstants.FLOW_ID);
assertFlowExecutionOutcome(resultReplayFail.getOutcome());
- // TODO change to appropriate for replay error
- assertOutputMessageEvent(resultReplayFail, EventIds.ACCESS_DENIED);
System.out.println("DDF: "+output.toString());
+ assertOutputMessageEvent(resultReplayFail, EventIds.MESSAGE_PROC_ERROR);
validateCookiesAreUnset(output);
}
- /**
- * Construct a successful OIDC token response with signed id_token.
- *
- * @param expiry expiry time
- * @param issuedAt issue time
- * @param nonce the nonce
- * @return the tokens
- * @throws ParseException
- * @throws JOSEException
- */
- private OIDCTokenResponse constructSuccessfulTokenResponseSigned(
- @Nonnull final Instant expiry, @Nonnull final Instant issuedAt, @Nullable final String nonce,
- @Nullable final Map<String, Object> additionalClaims)
- throws JOSEException, ParseException {
-
- final JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder()
- .subject("jdoe")
- .issuer(TestConstants.ISSUER)
- .audience(TestConstants.CLIENT_ID)
- .expirationTime(Date.from(expiry))
- .issueTime(Date.from(issuedAt));
-
- if (nonce != null) {
- claimsSetBuilder.claim("nonce", nonce);
- }
- if (additionalClaims != null) {
- additionalClaims.entrySet()
- .stream()
- .forEach(entry -> claimsSetBuilder.claim(entry.getKey(), entry.getValue()));
- }
-
- final JWTClaimsSet claimsSet = claimsSetBuilder.build();
-
-
- final JWT signedIdToken =
- TestHelper.createJWT(claimsSet, JWSAlgorithm.RS256, null, null, opSigningCredential, null);
-
- final AccessToken accessToken = new BearerAccessToken("fake-access-token-value", 3600, null);
- final RefreshToken refreshToken = new RefreshToken("fake-refresh-token-value");
- return new OIDCTokenResponse((new OIDCTokens(signedIdToken, accessToken, refreshToken)));
- }
-
- /**
- * Construct an error OIDC token response.
- *
- * @param code the error code
- * @param description the error description
- * @param httpCode the http code to return
- */
- private TokenErrorResponse constructErrorTokenResponseSigned(
- final String code, final String description, final int httpCode)
- throws JOSEException, ParseException {
-
- final ErrorObject error = new ErrorObject(code, description, httpCode);
- final TokenErrorResponse errorResponse = new TokenErrorResponse(error);
-
- return errorResponse;
- }
-
- /**
- * Construct a successful OIDC token response with a signed and encrypted id_token.
- *
- * @param expiry expiry time
- * @param issuedAt issue time
- * @param nonce the nonce
- * @return the tokens
- * @throws ParseException
- * @throws JOSEException
- */
- private OIDCTokenResponse constructSuccessfulTokenResponseSignedAndEncrypted(
- @Nonnull final Instant expiry, @Nonnull final Instant issuedAt, @Nullable final String nonce,
- @Nullable final Map<String, Object> additionalClaims)
- throws JOSEException, ParseException {
-
- final JWTClaimsSet.Builder claimsSetBuilder = new JWTClaimsSet.Builder()
- .subject("jdoe")
- .issuer(TestConstants.ISSUER)
- .audience(TestConstants.CLIENT_ID)
- .expirationTime(Date.from(expiry))
- .issueTime(Date.from(issuedAt));
-
- if (nonce != null) {
- claimsSetBuilder.claim("nonce", nonce);
- }
- if (additionalClaims != null) {
- additionalClaims.entrySet()
- .stream()
- .forEach(entry -> claimsSetBuilder.claim(entry.getKey(), entry.getValue()));
- }
- final JWTClaimsSet claimsSet = claimsSetBuilder.build();
-
- final JWT encryptedIdToken =
- TestHelper.createJWT(claimsSet, JWSAlgorithm.RS256, JWEAlgorithm.RSA_OAEP_256,
- EncryptionMethod.A128CBC_HS256, opSigningCredential, rpEncryptionCredential);
-
- final AccessToken accessToken = new BearerAccessToken("fake-access-token-value", 3600, null);
- final RefreshToken refreshToken = new RefreshToken("fake-refresh-token-value");
- return new OIDCTokenResponse((new OIDCTokens(encryptedIdToken, accessToken, refreshToken)));
- }
-
- /**
- * Construct a successful OIDC user info response in JSON format. application/json.
- *
- * @return the user info response
- */
- private UserInfoSuccessResponse constructJSONUserInfoResponse() {
- final JSONObject json = new JSONObject();
- json.appendField("sub", "jdoe");
- json.appendField("name", "John Doe");
- json.appendField("nickname" , "John");
- return new UserInfoSuccessResponse(new UserInfo(json));
- }
-
- /**
- * Construct a successful OIDC user info response in JWT format, signed.
- *
- * @return the user info response
- * @throws ParseException
- * @throws JOSEException
- */
- private UserInfoSuccessResponse constructJWTUserInfoResponseSigned() throws JOSEException, ParseException {
- final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
- .subject("jdoe")
- .issuer(TestConstants.ISSUER)
- .audience(TestConstants.CLIENT_ID)
- .claim("name", "John Doe")
- .claim("nickname","John").build();
-
- final JWT signedUserInfoToken =
- TestHelper.createJWT(claimsSet, JWSAlgorithm.RS256, null, null, opSigningCredential, null);
- return new UserInfoSuccessResponse(signedUserInfoToken);
- }
-
- /**
- * Mock OIDC Token, UserInfo, and Keys endpoints. Returning the supplied responses.
- * Noting these mocks may not exercise the response decoders, as the responses are returned directly.
- *
- * @param tokenResponse the token response
- * @param userInfoResponse the user info response
- *
- * @throws IOException on error.
- */
- private void mockOIDCEndpoints(final TokenResponse tokenResponse,
- final UserInfoSuccessResponse userInfoResponse) throws IOException {
-
- Mockito.when(httpClient.execute(
- Mockito.argThat(req -> req != null && req.getRequestUri().toString()
- .contains(".well-known/openid-configuration")),
- Mockito.any(HttpContext.class),
- Mockito.any(HttpClientResponseHandler.class)))
- .thenReturn(metadata);
-
- Mockito.when(httpClient.execute(
- Mockito.argThat(req -> req != null && req.getRequestUri().toString().contains("/token")),
- Mockito.any(HttpContext.class),
- Mockito.any(HttpClientResponseHandler.class)))
- .thenReturn(tokenResponse);
-
- Mockito.when(httpClient.execute(
- Mockito.argThat(req -> req != null && req.getRequestUri().toString().contains("/userinfo")),
- Mockito.any(HttpContext.class),
- Mockito.any(HttpClientResponseHandler.class)))
- .thenReturn(userInfoResponse);
-
- // Fetch from the keys endpoint mocking the call used by the RemoteJwkSetCache
- final String jwk = TestHelper.createJWKJSONStringFrom(opSigningCredential);
- assertNotNull(jwk);
- final ClassicHttpResponse classicHttpResponse = Mockito.mock(ClassicHttpResponse.class);
- Mockito.when(classicHttpResponse.getCode()).thenReturn(200);
- Mockito.when(classicHttpResponse.getEntity())
- .thenReturn(new StringEntity("{\"keys\":["+jwk+"]}", ContentType.APPLICATION_JSON));
-
- Mockito.when(httpClient.executeOpen(
- Mockito.nullable(HttpHost.class),
- Mockito.argThat(req -> req != null && req.getRequestUri().toString().contains("/keys")),
- Mockito.any(HttpContext.class)))
- .thenReturn(classicHttpResponse);
- }
-
- /**
- * Check that the output message contains an instruction to unset cookies using Max-Age=0, and specifically the
- * correlation cookie.
- *
- * @param output the DDF output
- */
- private void validateCookiesAreUnset(@Nullable final DDF output) {
- if (output == null) {
- fail("DDF output can not be null");
- }
- // Check cookies are unset
- final DDF headers = output.getmember("http.headers");
- // Check that any Set-Cookie is an unset, we do not set new cookies in the consumer flow
- final AtomicBoolean correlationCookieUnset = new AtomicBoolean(false);
- if (headers.islist()) {
- headers.forEach(header -> {
- if (header.isstring() && "Set-Cookie".equals(header.name())){
- Assert.assertTrue(header.string().contains("Max-Age=0"),"Cookies must be unset");
- }
- // Specific check for the correlation cookie unset, which is required
- if (header.string().contains(TestConstants.CORRELATION_COOKIE_PREFIX)) {
- Assert.assertTrue(header.string().contains("Max-Age=0"),"Correlation cookies must be unset");
- correlationCookieUnset.set(true);
- }
- });
- }
- Assert.assertTrue(correlationCookieUnset.get(),"Correlation cookies must be unset");
- }
-
- /**
- * Decode an encoded response and run assertion checks against it.
- *
- * @param result
- * flow execution result
- * @param claimNames
- * set of claims to check for
- * @param resource
- * resource URL used in final redirect
- *
- * @return the output object
- */
- @Nonnull private DDF validateOutputMessage(@Nonnull final FlowExecutionResult result,
- @Nullable final Set<String> claimNames, @Nullable final String resource) {
-
- final ProfileRequestContext prc = retrieveProfileRequestContext(result);
- assert prc != null;
- final AgentRequestContext arc = prc.ensureSubcontext(AgentRequestContext.class);
- final DDF output = arc.getOutput();
-
- assert output != null;
- Assert.assertTrue(output.isstruct());
- final DDF http = output.getmember(RemotedHttpServletRequest.STRUCTURE_NAME);
- Assert.assertTrue(http.isstruct());
-
- final byte[] redirect = http.getmember(RemotedHttpServletResponse.REDIRECT).unsafe_string();
- Assert.assertEquals(resource != null ? resource.getBytes(StandardCharsets.UTF_8) : null, redirect);
-
- final Set<String> mutableIds = new HashSet<>(claimNames);
- for (final DDF attr : output.getmember(ConsumerConstants.SESSION_ATTRIBUTES).asList()) {
- Assert.assertTrue(mutableIds.contains(attr.name()));
- mutableIds.remove(attr.name());
- if ("mail".equals(attr.name())) {
- Assert.assertEquals(attr.asList().stream().map(DDF::string).toList(),
- CollectionSupport.singletonList("jdoe at example.org"));
- } else if ("displayName".equals(attr.name())) {
- Assert.assertEquals(attr.asList().stream().map(DDF::string).toList(),
- CollectionSupport.singletonList("John Doe"));
- } else if ("eduPersonScopedAffiliation".equals(attr.name())) {
- Assert.assertEquals(attr.asList().stream().map(ddf -> {return ddf.getmember("value").string();}).toList(),
- CollectionSupport.listOf("member"));
- Assert.assertEquals(attr.asList().stream().map(ddf -> {return ddf.getmember("scope").string();}).toList(),
- CollectionSupport.listOf("example.org"));
- }
- }
- Assert.assertTrue(mutableIds.isEmpty());
-
- validateCookiesAreUnset(output);
- //TODO ADD BACK
-
-// try {
-// final DDF s = output.getmember(ConsumerConstants.SESSION_OPAQUE);
-// assert s != null;
-// Assert.assertTrue(s.isstruct());
-// final DDF nameIdDdf = s.getmember("nameID");
-// Assert.assertTrue(nameIdDdf.isstring());
-// final String nameIdString = nameIdDdf.string();
-// assert nameIdString != null;
-// final byte[] opaque = Base64Support.decodeURLSafe(nameIdString);
-// try (final ByteArrayInputStream in = new ByteArrayInputStream(opaque)) {
-// final XMLObject obj = XMLObjectSupport.unmarshallFromInputStream(parserPool, in);
-// if (obj instanceof final NameID nameID) {
-// Assert.assertEquals(nameID.getValue(), "jdoe at example.org");
-// Assert.assertEquals(nameID.getFormat(), NameIDType.EMAIL);
-// Assert.assertEquals(nameID.getSPProvidedID(), sessionIndex);
-// } else {
-// Assert.fail("Session data was not a NameID");
-// }
-// }
-// } catch (final DecodingException|IOException|UnmarshallingException|XMLParserException e) {
-// Assert.fail(e.getMessage());
-// }
-
- return output;
-
- }
-
-
- /**
- * Builds a dummy OIDC authorization code response.
- *
- * @param redirectURI
- * redirect URI
- * @param mode
- * response mode
- *
- * @return authentication success response for the token consumer flow
- */
- private AuthenticationSuccessResponse buildOIDCAuthorizationCodeResponse(
- @Nonnull final String redirect, @Nonnull final ResponseMode mode, final String stateString) {
-
- final AuthorizationCode code = new AuthorizationCode("dummy-auth-code");
- final State state = new State(stateString);
- URI redirectURI = null;
- try {
- redirectURI = new URI(redirect);
- } catch (final URISyntaxException e) {
- fail("Bad redirect URI", e);
- }
-
- // Build a dummy success response (authorization code)
- return new AuthenticationSuccessResponse(
- redirectURI,
- code,
- null,
- null,
- state,
- null,
- mode
- );
- }
-
- /**
- * Build a invalid_request error response, from openID connect core.
- *
- * @return the error response
- */
- private AuthenticationErrorResponse buildErrorResponse(final String stateString) {
-
- final URI redirectURI = URI.create(TestConstants.RESPONSE_URL);
- final State state = new State(stateString);
-
- final ErrorObject error = new ErrorObject(
- "invalid_request",
- "Unsupported response_type value",
- 400,
- null);
-
- return new AuthenticationErrorResponse(
- redirectURI,
- error,
- state,
- ResponseMode.QUERY);
-
- }
-
-
- /**
- * Encodes an OIDC response into the query string of a redirect URI embedded in a remoted message.
- *
- * @param response OIDC response to encode
- *
- * @return input object suitable for token consumer flow
- *
- * @throws IOException on error
- */
- @Nonnull private DDF buildRemotedQueryStringResponse(@Nonnull final AuthorizationResponse response) {
-
- final DDF obj = new DDF(null).structure();
- obj.addmember(ConsumerConstants.BASE_URL).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
- final DDF http = obj.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
-
- http.addmember(RemotedHttpServletRequest.METHOD).string("GET");
- http.addmember(RemotedHttpServletRequest.REMOTE_ADDR).string("192.168.1.1");
- http.addmember(RemotedHttpServletRequest.REQUEST_URL).unsafe_string(TestConstants.RESPONSE_URL.getBytes(StandardCharsets.UTF_8));
- final URI responseURI = response.toURI();
- http.addmember(RemotedHttpServletRequest.QUERY_STRING).string(Constraint
- .isNotNull(responseURI.getQuery(), "Query string is null"));
- return obj;
- }
-
- /**
- * Encodes an OIDC response into a form_post embedded in a remoted message.
- *
- * @param response OIDC response to encode
- *
- * @return input object suitable for token consumer flow
- *
- * @throws IOException on error
- */
- @Nonnull private DDF buildRemotedFormPostResponse(@Nonnull final AuthorizationResponse response) {
-
- final DDF obj = new DDF(null).structure();
- obj.addmember(ConsumerConstants.BASE_URL).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
- final DDF http = obj.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
-
- http.addmember(RemotedHttpServletRequest.METHOD).string("POST");
- http.addmember(RemotedHttpServletRequest.REMOTE_ADDR).string("192.168.1.1");
- http.addmember(RemotedHttpServletRequest.REQUEST_URL).unsafe_string(
- TestConstants.RESPONSE_URL.getBytes(StandardCharsets.UTF_8));
- http.addmember(RemotedHttpServletRequest.CONTENT_TYPE).string("application/x-www-form-urlencoded");
- if (response instanceof final AuthenticationSuccessResponse successResponse) {
- http.addmember(RemotedHttpServletRequest.BODY).unsafe_string(Constraint.isNotNull(URISupport.buildQuery(
- CollectionSupport.listOf(new Pair<>("code", successResponse.getAuthorizationCode().getValue()),
- new Pair<>("state", response.getState().getValue()))), "Query string is null").getBytes(StandardCharsets.UTF_8));
- }
-
- return obj;
- }
-
-
}
\ No newline at end of file
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java
new file mode 100644
index 0000000..2a60bd8
--- /dev/null
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowUsingStorageServiceTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.flows;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+
+import org.opensaml.storage.StorageService;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.web.WebAppConfiguration;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.oauth2.sdk.ResponseMode;
+import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
+
+import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
+import net.shibboleth.idp.test.PreferFileSystemApplicationContextInitializer;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
+import net.shibboleth.sp.state.impl.StorageServiceStateManager;
+
+/**
+ * Unit test for the SP token-consumer flow but using the storage service to handle state, as initialised by the
+ * context initializer.
+ *
+ * <p>Requires interaction with the storage service to add the state data, and then the flow should be able to retrieve.
+ * The flow is configured to use the storage service in the context initializer, and the test adds the state data to
+ * the storage service directly in the pre-test setup method.</p>
+ *
+ * <p>Warning, each test method dirties the context, and Spring will throw away everything and re-initialise
+ * the tests. This allows for things like replay caches to work correctly, at the expense of test execution performance
+ * </p>
+ */
+ at ContextConfiguration(
+ locations = {
+ "classpath:/net/shibboleth/sp/oidc-test-beans.xml", },
+ initializers = {
+ TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService.class,
+ PreferFileSystemApplicationContextInitializer.class,
+ IdPPropertiesApplicationContextInitializer.class
+ },
+ inheritInitializers = false
+ )
+ at WebAppConfiguration
+ at SuppressWarnings({ "unchecked", "rawtypes", "null" })
+ at DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
+public class OIDCTokenConsumerFlowUsingStorageServiceTest extends AbstractOIDCTokenConsumerFlowTest {
+
+
+ /** Constructor. */
+ public OIDCTokenConsumerFlowUsingStorageServiceTest() {
+ super(TestConstants.FLOW_ID);
+ }
+
+ /**
+ * Pre-test work.
+ *
+ * @throws Exception on error
+ */
+ @Override
+ @BeforeMethod
+ public void beforeMethod() throws Exception{
+ super.beforeMethod();
+
+ // Grab the storage service being using
+ final StorageService storageService =
+ applicationContext.getBean("shibboleth.StorageService", StorageService.class);
+ final ObjectMapper om = applicationContext.getBean("shibboleth.JSONObjectMapper", ObjectMapper.class);
+
+
+ assertNotNull(storageService, "StorageService bean could not be accessed");
+ assertNotNull(om, "JSONObjectMapper bean could not be accessed");
+
+ // Add an authentication record to the storage service
+ final AuthenticationRequestStateData stateData =
+ TestConstants.buildAuthenticationRequestStateData(null, false, null);
+
+ final String serializedState = om.writeValueAsString(stateData);
+ final String encoded = Base64Support.encode(serializedState.getBytes(StandardCharsets.UTF_8), false);
+
+ //TODO this is brittle
+ final StringBuilder builder = new StringBuilder(StorageServiceStateManager.class.getName());
+ builder.append('!').append("testsp.example.org").append('!').append(TestConstants.APPLICATION_ID);
+ final String context = builder.toString();
+ storageService.create(context,
+ TestConstants.STATE_COOKIE_STORAGE_KEY, encoded, Instant.now().plus(Duration.ofMinutes(30)).toEpochMilli());
+
+ }
+
+
+ /**
+ * Test successful flow with a signed id_token and a plain user info response.
+ *
+ * @throws IOException on error
+ */
+ @Test
+ public void testSuccess_SignedIDToken_PlainUserInfo() throws Exception {
+
+ mockOIDCEndpoints(constructSuccessfulTokenResponseSigned(Instant.now().plusSeconds(3600), Instant.now(), null, null),
+ constructJSONUserInfoResponse());
+
+ final AuthenticationSuccessResponse response =
+ buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestConstants.STATE_TOKEN);
+ final DDF input = buildRemotedQueryStringResponse(response);
+
+ // Add cookies
+ input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ TestConstants.STATE_TOKEN,
+ TestConstants.APPLICATION_ID,
+ TestConstants.STATE_COOKIE_STORAGE_KEY, false));
+
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, TestConstants.FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ final DDF output = assertOutputMessageSuccess(result);
+ assert output != null;
+ System.out.println("test output: " + output.toString());
+ validateOutputMessage(result, CollectionSupport.setOf("sub","mail","displayName","eduPersonScopedAffiliation"),
+ TestConstants.RESOURCE_URL);
+ }
+
+
+}
\ No newline at end of file
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java
index 480edda..2858279 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java
@@ -16,7 +16,6 @@ package net.shibboleth.sp.oidc.flows;
import static org.testng.Assert.fail;
-import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
@@ -34,15 +33,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
-import com.nimbusds.jose.util.StandardCharset;
-import net.minidev.json.JSONObject;
-import net.minidev.json.JSONValue;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
import net.shibboleth.shared.codec.Base64Support;
-import net.shibboleth.shared.codec.EncodingException;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
-import net.shibboleth.sp.oidc.profile.OIDCConstants;
/** A class to hold constants used for tests.*/
public final class TestConstants {
@@ -67,23 +61,14 @@ public final class TestConstants {
/** Resource URL. */
@Nonnull public static final String RESOURCE_URL = "https://sp.example.org/secure";
- /**
- * The request forgery protection nonce sent in the authentication request and part of the state returned in an
- * authentication response. Not the nonce in an id_token.
- */
- @Nonnull public static final String AUTHENTICATION_RFP = "f26d922221226cd183729bf13247dbe2";
-
/** The nonce in the ID token.*/
@Nonnull public static final String ID_TOKEN_NONCE = "bd1b5f211250c57e";
+
+ /** The key to the storage record that gets held in the request state cookie set by the Storage Service.*/
+ @Nonnull public static final String STATE_COOKIE_STORAGE_KEY = "c516767a37f59fe32e049ac1cb1a8921";
/** State token string used in cookie names. */
- public static final String STATE_TOKEN = "1761316967710_1622a5c726da8f7b36e24f19eed82aea";
-
- /** Authentication state token used to reference the stored authentication state. */
- public static final String AUTHENTICATION_STATE_TOKEN = "1761316967710_authnstate";
-
- /** State string used in cookie names. A state which should not match to the a correlation cookie. */
- public static final String AUTHENTICATION_STATE_WRONG = "1761316967710_wrong";
+ public static final String STATE_TOKEN = "1761316967710_1622a5c726da8f7b36e24f19eed82aea";
/** Application ID used in cookie names. */
public static final String APPLICATION_ID = "test-oidc-application-with-ro";
@@ -97,101 +82,73 @@ public final class TestConstants {
/** Target URL encoded in cookie. */
public static final String TARGET_URL = "https://sp.example.org/secure";
- /** The defaulted prefix of the correlation cookie.*/
- public static final String CORRELATION_COOKIE_PREFIX = "__Host-shibsp_req_";
-
/** The defaulted prefix for state tokens.*/
- public static final String STATE_TOKEN_PREFIX = "__Host-shibsp_state__";
+ public static final String STATE_TOKEN_PREFIX = "__Host-shibsp_state_";
+
/**
* Build cookie header bytes for the given parameters. These take the form of:
* <pre>
- * __Host-_shibsp_req_{stateToken}={(authnStateToken, rfp)}; (correlation cookie)
- * __Host-shibsp_state_{appId}_{stateToken}={targetURL}; (resource URL state)
- * __Host-shibsp_state_{appId}_{authnStateToken}={authn_request_json}; (authentication state)
+ * __Host-shibsp_state_{appId}_{stateToken}={contents};
* </pre>
*
- * @param stateToken the token value used for mapping response to the stored correlation cookie and target URL
- * @param authnStateToken the token value, held inside the 'req' correlation cookie, of the persisted authentication state.
+ * @param stateToken the token value used for mapping the OAuth 2.0 state to the cookie
* @param appID the application ID
- * @param rfp the request forgery protection nonce used to protect against CSRF in the request and response OAuth
- * state parameter not to be confused with the OIDC ID token nonce. Stored inside the correlation cookie
- * @param targetURL the target URL to redirect to after processing
- * @param authnRequestStateJSON the authentication request state JSON for recovery of important parameters
+ * @param stateCookieContents the contents of the state cookie, either the authentication request state JSON
+ * or a key to the authentication request state in the storage manager
+ * @param urlEncode TODO
* @return the cookie header bytes
* @throws Exception on error
*/
- public static byte[] buildCookieHeader(final String stateToken,
- final String authnStateToken, final String appID, final String rfp,
- final String targetURL, final String authnRequestStateJSON) throws Exception {
+ public static byte[] buildCookieHeader(final String stateToken, final String appID,
+ final String stateCookieContents, final boolean urlEncode) throws Exception {
-
- final String targetUrlB64 = Base64Support.encodeURLSafe(targetURL.getBytes(StandardCharsets.UTF_8));
- final String authnJsonEnc = Base64Support.encodeURLSafe(authnRequestStateJSON.getBytes(StandardCharsets.UTF_8));
+ String contentsEncoded = null;
+ if (urlEncode) {
+ contentsEncoded =
+ Base64Support.encodeURLSafe(stateCookieContents.getBytes(StandardCharsets.UTF_8));
+ } else {
+ contentsEncoded = stateCookieContents;
+ }
// Compose the header with semicolons and spacing as in the example
final StringBuilder sb = new StringBuilder();
- sb.append(CORRELATION_COOKIE_PREFIX).append(stateToken).append('=')
- .append(buildCorrelationCookieString(authnStateToken, rfp)).append(";\n");
+
sb.append(STATE_TOKEN_PREFIX).append(appID).append('_').append(stateToken)
- .append('=').append(targetUrlB64).append("; \n");
- sb.append(STATE_TOKEN_PREFIX).append(appID).append('_').append(authnStateToken).append('=').append(authnJsonEnc).append(";\n");
+ .append('=').append(contentsEncoded).append("; \n");
+
System.out.println(sb.toString());
return sb.toString().getBytes("UTF-8");
}
/**
- * Build the OAuth state string for the given state and RFP. This is used for the state parameter in the
- * authentication request, and contains important information about the authentication request.
+ * Build the authentication request state data for the given parameters.
*
- * @param state the state used to map to the correlation cookie bound to the browser session.
- * @param rfp the request forgery protection nonce used to protect against CSRF in the request and response OAuth
- * state parameter.
- * @return the JSON serialized and Base64 encoded state string for the given parameters
- */
- public static String buildOAuthStateString(final String state, final String rfp) {
- final JSONObject stateObject = new JSONObject();
- stateObject.appendField(OIDCConstants.STATE_FIELD, state);
- stateObject.appendField(OIDCConstants.RFP_FIELD,rfp);
- final String stateJsonString = stateObject.toJSONString(JSONValue.COMPRESSION);
- try {
- final byte[] stateJsonAsBytes = stateJsonString.getBytes(StandardCharset.UTF_8);
- assert stateJsonAsBytes != null;
- return Base64Support.encodeURLSafe(stateJsonAsBytes);
- } catch (final EncodingException e) {
- fail(e.getMessage());
- return null;
- }
-
- }
-
- /**
- * Build the correlation cookie value for the given authentication state token and RFP. The authnStateToken is used
- * to recover the authentication state stored off in the state token manager. The RFP is used to protect against
- * CSRF in the authentication request and response, and should be matched to that present in the OAuth state
- * parameter. Both are stored in the cookie value, which is JSON serialized and Base64 encoded.
- *
- * @param authnStateToken the token value used for recovery of the authentication state stored off in the state
- * token manager.
- * @param rfp the request forgery protection nonce used to protect against CSRF in the request and response
+ * @param maxAge the maximum age of the authentication request, used for validation of the authentication response
+ * @param authTimeRequired a flag whether the authentication time is required, used for validation of the authentication response
+ * @param acrs the ACRs to request, used for validation of the authentication response. May be null if no ACRs are requested.
* @return
*/
- public static String buildCorrelationCookieString(final String authnStateToken, final String rfp) {
- final JSONObject stateObject = new JSONObject();
- stateObject.appendField(OIDCConstants.AUTHN_STATE_FIELD, authnStateToken);
- stateObject.appendField(OIDCConstants.RFP_FIELD,rfp);
- final String stateJsonString = stateObject.toJSONString(JSONValue.COMPRESSION);
- try {
- final byte[] stateJsonAsBytes = stateJsonString.getBytes(StandardCharset.UTF_8);
- assert stateJsonAsBytes != null;
- return Base64Support.encodeURLSafe(stateJsonAsBytes);
- } catch (final EncodingException e) {
- fail(e.getMessage());
- return null;
+ public static AuthenticationRequestStateData buildAuthenticationRequestStateData(
+ final Duration maxAge, final boolean authTimeRequired,
+ @Nullable final List<String> acrs) {
+ final AuthenticationRequestStateData state = new AuthenticationRequestStateData()
+ .setNonce(ID_TOKEN_NONCE)
+ .setMaxAge(maxAge)
+ .setAuthTimeRequired(authTimeRequired);
+ state.setIssuer(CLIENT_ID)
+ .setAuthenticationAuthority(ISSUER)
+ .setResponseLocation(RESPONSE_URL)
+ .setRequestTime(Instant.now());
+
+ if (acrs != null) {
+ state.setAcrs(acrs);
}
+ return state;
}
-
+
+
/**
* Build the authentication request state JSON for the given parameters. This is used for the state value, and
* contains important information about the authentication request.
@@ -204,19 +161,8 @@ public final class TestConstants {
*/
public static String buildAuthenticationState(final Duration maxAge, final boolean authTimeRequired,
@Nullable final List<String> acrs) throws URISyntaxException {
- final AuthenticationRequestStateData state = new AuthenticationRequestStateData()
- .setClientId(APPLICATION_ID)
- .setNonce(ID_TOKEN_NONCE)
- .setAuthenticationAuthority(ISSUER)
- .setRedirectUri(new URI(RESPONSE_URL))
- .setMaxAge(maxAge)
- .setAuthTimeRequired(authTimeRequired)
- .setAuthnRequestTime(Instant.now());
-
- if (acrs != null) {
- state.setAcrs(acrs);
- }
+ final var state = buildAuthenticationRequestStateData(maxAge, authTimeRequired, acrs);
final ObjectMapper jsonMapper = JsonMapper.builder()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.serializationInclusion(Include.NON_ABSENT)
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 2964fcd..9474543 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
@@ -39,8 +39,8 @@ public class TestSPOIDCEnvironmentApplicationContextInitializer extends TestSPEn
mock.setProperty("idp.home", "classpath:/net/shibboleth/idp/module");
mock.setProperty("idp.webflows", "classpath*:/flows");
mock.setProperty("sp.service.agents.resources", "test.sp.oidc.AgentResolverResources");
- // Use cookie based state managment
- mock.setProperty("sp.stateToken.Manager","shibboleth.sp.CookieStateTokenManager");
+ // Use cookie based state management
+ mock.setProperty("sp.stateToken.Manager","shibboleth.sp.CookieStateManager");
// Use a mocked HTTP client
mock.setProperty("sp.oidc.HttpClient","Mock.HttpClient");
// Set agent auth to basic
@@ -49,14 +49,12 @@ public class TestSPOIDCEnvironmentApplicationContextInitializer extends TestSPEn
mock.setProperty("sp.oidc.resolveAttributes", "true");
// Ensure ACR checks are performed
mock.setProperty("sp.oidc.idtoken.validateAcrValue", "true");
- // Turn off the sealing of authentication request state for tests
- mock.setProperty("sp.oidc.sealAuthenticationState", "false");
- // Turn off the sealing of the correlation cookie for tests
- mock.setProperty("sp.oidc.sealCorrelationCookie", "false");
- // Turn off the sealing of OAuth state for tests
- mock.setProperty("sp.oidc.sealOAuthState", "false");
+ // Turn off the sealing state data for testing
+ mock.setProperty("sp.stateToken.sealed", "false");;
// Create a basic default client secret
mock.setProperty("sp.oidc.defaultClientSecret", "secret");
+ // Turn on the replay cache for testing
+ mock.setProperty("sp.stateToken.checkReplay", "true");
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/sp/oidc-test.properties");
applicationContext.getEnvironment().getPropertySources().addFirst(mock);
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/TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService.java
similarity index 75%
copy from sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializer.java
copy to sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService.java
index 2964fcd..11f7250 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/TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService.java
@@ -22,16 +22,21 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.mock.env.MockPropertySource;
import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.flows.TestSPEnvironmentApplicationContextInitializer;
/**
/**
- * An {@link ApplicationContextInitializer} which extends the parent plugin's test initializer.
+ * An {@link ApplicationContextInitializer} which extends the parent plugin's test initializer. Specifically configures
+ * the storage service and cookie backed state manager.
+ *
+ * TODO combine this and TestSPOIDCEnvironmentApplicationContextInitializer
*/
-public class TestSPOIDCEnvironmentApplicationContextInitializer extends TestSPEnvironmentApplicationContextInitializer {
+public class TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService
+ extends TestSPOIDCEnvironmentApplicationContextInitializer {
/** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(TestSPOIDCEnvironmentApplicationContextInitializer.class);
+ @Nonnull private final
+ Logger log = LoggerFactory.getLogger(TestSPOIDCEnvironmentApplicationContextInitializerWithStorageService.class);
+
/** {@inheritDoc} */
@Override public void initialize(@Nonnull final ConfigurableApplicationContext applicationContext) {
@@ -39,8 +44,8 @@ public class TestSPOIDCEnvironmentApplicationContextInitializer extends TestSPEn
mock.setProperty("idp.home", "classpath:/net/shibboleth/idp/module");
mock.setProperty("idp.webflows", "classpath*:/flows");
mock.setProperty("sp.service.agents.resources", "test.sp.oidc.AgentResolverResources");
- // Use cookie based state managment
- mock.setProperty("sp.stateToken.Manager","shibboleth.sp.CookieStateTokenManager");
+ // Use storage service and cookie based state managment
+ mock.setProperty("sp.stateToken.Manager","shibboleth.sp.StorageAndCookieStateManager");
// Use a mocked HTTP client
mock.setProperty("sp.oidc.HttpClient","Mock.HttpClient");
// Set agent auth to basic
@@ -49,14 +54,12 @@ public class TestSPOIDCEnvironmentApplicationContextInitializer extends TestSPEn
mock.setProperty("sp.oidc.resolveAttributes", "true");
// Ensure ACR checks are performed
mock.setProperty("sp.oidc.idtoken.validateAcrValue", "true");
- // Turn off the sealing of authentication request state for tests
- mock.setProperty("sp.oidc.sealAuthenticationState", "false");
- // Turn off the sealing of the correlation cookie for tests
- mock.setProperty("sp.oidc.sealCorrelationCookie", "false");
- // Turn off the sealing of OAuth state for tests
- mock.setProperty("sp.oidc.sealOAuthState", "false");
+ // Turn off the sealing state data for testing
+ mock.setProperty("sp.stateToken.sealed", "false");
// Create a basic default client secret
mock.setProperty("sp.oidc.defaultClientSecret", "secret");
+ // Turn on the replay cache for testing
+ mock.setProperty("sp.stateToken.checkReplay", "true");
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/sp/oidc-test.properties");
applicationContext.getEnvironment().getPropertySources().addFirst(mock);
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties
index 0859911..f9ed4c9 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/sp.properties
@@ -23,7 +23,7 @@ sp.service.agents.checkInterval = PT5M
# Default state token management (SAML RelayState, etc.)
#sp.stateToken.errorsFatal = false
-# Set to shibboleth.sp.CookieStateTokenManager to switch to cookie-based mechanism
+# Set to shibboleth.sp.CookieStateManager to switch to cookie-based mechanism
#sp.stateToken.Manager = shibboleth.sp.StorageStateTokenManager
# Controls storage back-end for storage-based state tokens
#sp.stateToken.StorageService = shibboleth.StorageService
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/OIDCProviderMetadataLookupHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/OIDCProviderMetadataLookupHandler.java
index 7fe7e6e..3afe327 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/OIDCProviderMetadataLookupHandler.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/metadata/impl/OIDCProviderMetadataLookupHandler.java
@@ -80,7 +80,7 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
*/
public void setContextClassLookupStrategy(
@Nonnull final Function<MessageContext, ? extends AbstractOIDCEntityContext> strategy) {
- checkSetterPreconditions();
+ checkSetterPreconditions();
contextClassLookupStrategy =
Constraint.isNotNull(strategy, "Context class lookup strategy can not be null");
}
@@ -91,7 +91,7 @@ public class OIDCProviderMetadataLookupHandler extends AbstractMessageHandler {
* @param strategy the strategy function
*/
public void setCopyContextStrategy(@Nullable final Function<MessageContext, OIDCProviderMetadataContext> strategy) {
- checkSetterPreconditions();
+ checkSetterPreconditions();
copyContextStrategy = strategy;
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/DecodeStateAsJsonObjectConsumer.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/DecodeStateAsJsonObjectConsumer.java
index 530e365..71d322f 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/DecodeStateAsJsonObjectConsumer.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/DecodeStateAsJsonObjectConsumer.java
@@ -160,7 +160,7 @@ public class DecodeStateAsJsonObjectConsumer extends AbstractIdentifiableInitial
.setState(responseState.getValue())
.setStateJson(jsonState);
- } catch (DecodingException | ParseException | DataSealerException e) {
+ } catch (final DecodingException | ParseException | DataSealerException e) {
log.debug("Unable to decode OAuth state into a JSON token", e);
return false;
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/package-info.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/package-info.java
new file mode 100644
index 0000000..cf16baa
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+/**
+ * OIDC profile decoding implementation classes.
+ */
+package net.shibboleth.sp.oidc.profile.decoding.impl;
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractHttpOAuthAction.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractHttpOAuthAction.java
index eecb03c..826da8c 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractHttpOAuthAction.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractHttpOAuthAction.java
@@ -162,7 +162,8 @@ public abstract class AbstractHttpOAuthAction<T extends Response>
* @throws MessageHandlerException on error making the request, or if an error response is returned.
*/
@Nonnull protected T handleRequest(@Nonnull final ProfileRequestContext profileRequestContext,
- @Nullable final AbstractAuthenticatableOIDCContext authenticatableContext) throws MessageHandlerException {
+ @Nullable final AbstractAuthenticatableOIDCContext authenticatableContext)
+ throws MessageHandlerException {
try {
final ClassicHttpRequest request = getHttpRequestEncoderStrategy().apply(profileRequestContext);
if (request == null) {
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractProviderMetadataLookupFunction.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractProviderMetadataLookupFunction.java
index 5cc71fb..e4609d5 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractProviderMetadataLookupFunction.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AbstractProviderMetadataLookupFunction.java
@@ -30,7 +30,7 @@ import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
import net.shibboleth.shared.logic.Constraint;
/**
- * An abstract base class for pulling out the Provider metadata
+ * An abstract base class for pulling out the OpenID Provider metadata.
*
* @param <F> context type
* @param <ResultType> result type
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthTimeRequestedActivationCondition.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthTimeRequestedActivationCondition.java
index 88a6b6e..c347b08 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthTimeRequestedActivationCondition.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthTimeRequestedActivationCondition.java
@@ -19,6 +19,7 @@ import java.util.function.Function;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
@@ -28,30 +29,32 @@ import com.nimbusds.jwt.JWTClaimsSet;
import net.shibboleth.shared.annotation.ParameterName;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.sp.context.StateDataContext;
+import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
/**
- * Has the auth_time claim been requested? This is taken from the authentication request state. A failure is treated
- * as a request for auth_time to be checked (for a better security posture).
+ * Has the auth_time claim been requested? This is taken from the authentication request state. Any issues finding
+ * if the auth_time claim was request results in a <em>true</em> response: the claim should be checked.
*/
+ at ThreadSafe
public final class AuthTimeRequestedActivationCondition implements BiPredicate<ProfileRequestContext, JWTClaimsSet> {
/** Class logger. */
@Nonnull private final Logger log = LoggerFactory.getLogger(AuthTimeRequestedActivationCondition.class);
- /** Strategy to find the authentication request.*/
+ /** Strategy to find the state data context.*/
@Nonnull private final
- Function<ProfileRequestContext, AuthnRequestStateDataContext> authenticationRequestLookupStrategy;
+ Function<ProfileRequestContext, StateDataContext> stateDataContextLookupStrategy;
/**
* Constructor.
*
- * @param strategy strategy to find the authentication request
+ * @param strategy strategy to find the state data context
*/
- public AuthTimeRequestedActivationCondition(@ParameterName(name = "authenticationRequestLookupStrategy")
- @Nonnull final Function<ProfileRequestContext, AuthnRequestStateDataContext> strategy) {
- authenticationRequestLookupStrategy = Constraint.isNotNull(strategy,
- "authenticationRequestLookupStrategy can not be null");
+ public AuthTimeRequestedActivationCondition(@ParameterName(name = "stateDataContextLookupStrategy")
+ @Nonnull final Function<ProfileRequestContext, StateDataContext> strategy) {
+ stateDataContextLookupStrategy = Constraint.isNotNull(strategy,
+ "StateDataContextLookupStrategy can not be null");
}
/**
@@ -61,20 +64,28 @@ public final class AuthTimeRequestedActivationCondition implements BiPredicate<P
*/
@Override
public boolean test(@Nullable final ProfileRequestContext prc, @Nullable final JWTClaimsSet claims) {
+ if (prc == null) {
+ log.trace("The profile context was null, 'auth_time' will now be checked");
+ return true;
+ }
- final AuthnRequestStateDataContext authnRequestCtx = authenticationRequestLookupStrategy.apply(prc);
- final var localAuthnRequest = authnRequestCtx != null ? authnRequestCtx.getAuthnState() : null;
- if (authnRequestCtx == null || localAuthnRequest == null) {
+ final StateDataContext stateDataCtx = stateDataContextLookupStrategy.apply(prc);
+ final var localStateData = stateDataCtx != null ? stateDataCtx.getStateData() : null;
+ if (stateDataCtx == null || localStateData == null) {
// Return true, because if we are not sure, we should default to testing the auth_time as a better security
// posture
log.trace("Authentication request state not found in profile context, is the "
- + "authenticationRequestLookupStrategy configured correctly? 'auth_time' will now be checked");
+ + "stateDataContextLookupStrategy configured correctly? 'auth_time' will now be checked");
return true;
}
- final boolean authTimeWasRequested = localAuthnRequest.isAuthTimeRequired();
- log.trace("Authentication time (auth_time) {} request during authentication",
- authTimeWasRequested ? "was" : "was not");
- return authTimeWasRequested;
+ if (localStateData instanceof final AuthenticationRequestStateData authnRequest) {
+ final boolean authTimeWasRequested = authnRequest.isAuthTimeRequired();
+ log.trace("Authentication time (auth_time) {} request during authentication",
+ authTimeWasRequested ? "was" : "was not");
+ return authTimeWasRequested;
+ }
+ log.trace("Unexpected StateData type {}, defaulting to auth_time check", localStateData.getClass().getName());
+ return true;
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestStateForStorageStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestStateForStorageStrategy.java
index d665b10..4ea0178 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestStateForStorageStrategy.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestStateForStorageStrategy.java
@@ -15,7 +15,6 @@
package net.shibboleth.sp.oidc.profile.impl;
import java.util.function.Function;
-import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -23,36 +22,30 @@ import javax.annotation.Nullable;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.PredicateSupport;
import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.security.DataSealer;
-import net.shibboleth.shared.security.DataSealerException;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
+import net.shibboleth.sp.state.StateData;
/**
- * A {@link Function} that produces a serialized JSON string representing authentication request state that is required
+ * A {@link Function} that produces {@link StateData} representing authentication request state that is required
* for validating the authentication response. This can be stored in a way that can be recovered later when the
* response is received.
*
* <p>It is important that this information is stored in a way that is protected from modification by the client, as
- * tampering with this information may allow an attacker to forge authentication responses. This is supported
- * using the dataSealer—which should only ever be turned off for testing and debugging.</p>
+ * tampering with this information may allow an attacker to forge authentication responses.</p>
*
* <p>If an authentication authority is not available, or the authentication request
- * is not available, null will be returned. This will ultimately prevent the authentication response from being
- * validated correctly, and the calling action may wish to terminate the authentication request before a response
+ * is not available, <code>null</code> will be returned. This will ultimately prevent the authentication response
+ * from being validated, therefore the calling action may wish to terminate the authentication process before a request
* is made.</p>
*/
public class AuthenticationRequestStateForStorageStrategy extends AbstractIdentifiableInitializableComponent
- implements Function<ProfileRequestContext,String> {
+ implements Function<ProfileRequestContext,AuthenticationRequestStateData> {
/** Class logger. */
@Nonnull
@@ -61,25 +54,9 @@ public class AuthenticationRequestStateForStorageStrategy extends AbstractIdenti
/** Strategy to locate authentication authority, that is the issuer of the eventual authentication response. */
@NonnullAfterInit private Function<ProfileRequestContext,String> authenticationAuthorityLookupStrategy;
- /** JSON object mapper. */
- @NonnullAfterInit private ObjectMapper objectMapper;
-
/** Strategy to locate the authentication request. */
- @NonnullAfterInit private Function<ProfileRequestContext,OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
-
- /** Optional data sealer to use. */
- @Nullable private DataSealer dataSealer;
-
- /**
- * If the dataSealer is provided should it be used to seal the authentication request state? Defaults to true,
- * that is, if the dataSealer is provided, always seal state.
- */
- @Nonnull private Predicate<ProfileRequestContext> sealState;
-
- /** Constructor.*/
- public AuthenticationRequestStateForStorageStrategy() {
- sealState = PredicateSupport.alwaysTrue();
- }
+ @NonnullAfterInit
+ private Function<ProfileRequestContext,OIDCAuthenticationRequest> authenticationRequestLookupStrategy;
/** {@inheritDoc} */
@Override
@@ -92,53 +69,8 @@ public class AuthenticationRequestStateForStorageStrategy extends AbstractIdenti
if (authenticationRequestLookupStrategy == null) {
throw new ComponentInitializationException("Authentication request lookup strategy can not be null");
}
- if (objectMapper == null) {
- throw new ComponentInitializationException("ObjectMapper cannot be null");
- }
}
- /**
- * Sets {@link DataSealer} to use.
- *
- * @param sealer data sealer
- */
- public void setDataSealer(@Nullable final DataSealer sealer) {
- checkSetterPreconditions();
-
- dataSealer = sealer;
- }
-
- /**
- * Set the predicate to determine whether to seal the state.
- *
- * @param predicate the seal state predicate to set.
- */
- public void setSealStatePredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
- checkSetterPreconditions();
-
- sealState = Constraint.isNotNull(predicate, "Seal state predicate can not be null");
- }
-
- /**
- * Set the flag to determine whether to seal the state.
- *
- * @param flag the flag to set.
- */
- public void setSealState(final boolean flag) {
- checkSetterPreconditions();
-
- sealState = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
- }
-
- /**
- * Set the JSON {@link ObjectMapper} to use for serialization.
- *
- * @param mapper object mapper
- */
- public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
- checkSetterPreconditions();
- objectMapper = Constraint.isNotNull(mapper, "ObjectMapper cannot be null");
- }
/**
* Set the authentication request lookup strategy to use.
@@ -157,14 +89,15 @@ public class AuthenticationRequestStateForStorageStrategy extends AbstractIdenti
*
* @param strategy the strategy.
*/
- public void setAuthenticationAuthorityLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+ public void setAuthenticationAuthorityLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, String> strategy) {
checkSetterPreconditions();
authenticationAuthorityLookupStrategy = Constraint.isNotNull(strategy,
"Authentication Authority Lookup Strategy can not be null");
}
/** {@inheritDoc} */
- @Nullable public String apply(@Nullable final ProfileRequestContext prc) {
+ @Nullable public AuthenticationRequestStateData apply(@Nullable final ProfileRequestContext prc) {
checkComponentActive();
@@ -173,43 +106,25 @@ public class AuthenticationRequestStateForStorageStrategy extends AbstractIdenti
return null;
}
- // Find the relying party or authenticating authority this request has been sent to.
+ // Find the relying party or authenticating authority this request will be issued to.
final String authenticationAuthority = authenticationAuthorityLookupStrategy.apply(prc);
// Locate the authentication request to pull out information for saving off.
final OIDCAuthenticationRequest authnRequest = authenticationRequestLookupStrategy.apply(prc);
if (authenticationAuthority == null) {
- log.debug("{}: Authentication authority is null, response correlation will fail", getId());
+ log.warn("{}: Authentication authority is null, response correlation will fail", getId());
return null;
}
if (authnRequest == null) {
- log.debug("{}: Authentication request is null, response correlation will fail", getId());
+ log.warn("{}: Authentication request is null, response correlation will fail", getId());
return null;
}
// Construct a DTO for saving off the authentication request state
final AuthenticationRequestStateData state =
AuthenticationRequestStateData.from(authnRequest, authenticationAuthority);
- log.debug("{}: Created authentication request state data: {}", getId(), state);
+ log.trace("{}: Created authentication request state data: {}", getId(), state);
- try {
- // Serialize the authentication request state
- final String serializedState = objectMapper.writeValueAsString(state);
- if (serializedState == null) {
- return null;
- }
- final DataSealer localDataSealer = dataSealer;
- if (localDataSealer != null && sealState.test(prc)) {
- log.debug("{}: Authentication request state was sealed", getId());
- return localDataSealer.wrap(serializedState);
- } else {
- log.warn("{}: Authentication request state was NOT sealed, either DataSealer is not configured or "
- + "sealing predicate returned false. Sealing should be enabled in production", getId());
- return serializedState;
- }
- } catch (final JsonProcessingException | DataSealerException e) {
- log.error("{}: Issue generating authentication request state",getId(),e);
- }
- return null;
+ return state;
}
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestTimeLookupFunction.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestTimeLookupFunction.java
index 15c9fe4..e1f4d0e 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestTimeLookupFunction.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestTimeLookupFunction.java
@@ -25,12 +25,13 @@ import javax.annotation.concurrent.NotThreadSafe;
import org.opensaml.profile.context.ProfileRequestContext;
import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
+import net.shibboleth.sp.state.StateData;
/**
* Lookup function that returns the time at which the RP sent the authentication request, as taken from the
- * authentication request state object recovered when the response was obtained from the agent.
+ * authentication request state object recovered when the response delivered via the Agent.
*
* <p>If the time can not be found, <code>null</code> is returned.</p>
*/
@@ -38,9 +39,9 @@ import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
public class AuthenticationRequestTimeLookupFunction implements Function<ProfileRequestContext, Instant> {
/**
- * Strategy used to locate the {@link AuthnRequestStateDataContext}.
+ * Strategy used to locate the {@link StateDataContext}.
*/
- @Nonnull private Function<ProfileRequestContext, AuthnRequestStateDataContext> authenticationRequestLookupStrategy;
+ @Nonnull private Function<ProfileRequestContext, StateDataContext> stateDataContextLookupStrategy;
/**
* A clock skew to apply to the authentication request time. This accounts for differences between system clocks by
@@ -51,12 +52,12 @@ public class AuthenticationRequestTimeLookupFunction implements Function<Profile
/** Constructor.*/
public AuthenticationRequestTimeLookupFunction() {
- authenticationRequestLookupStrategy = prc -> {
+ stateDataContextLookupStrategy = prc -> {
final var inboundMsgCtx = prc.getInboundMessageContext();
if (inboundMsgCtx == null) {
return null;
}
- return inboundMsgCtx.getSubcontext(AuthnRequestStateDataContext.class);
+ return inboundMsgCtx.getSubcontext(StateDataContext.class);
};
final Duration ofSeconds = Duration.ofSeconds(0);
assert ofSeconds != null;
@@ -73,14 +74,14 @@ public class AuthenticationRequestTimeLookupFunction implements Function<Profile
}
/**
- * Set the strategy used to locate the {@link AuthenticationRequestStateData} to use.
+ * Set the strategy used to locate the {@link StateDataContext} to use.
*
* @param strategy lookup strategy
*/
- public void setAuthenticationRequestLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, AuthnRequestStateDataContext> strategy) {
- authenticationRequestLookupStrategy =
- Constraint.isNotNull(strategy, "AuthenticationRequestLookupStrategy lookup strategy cannot be null");
+ public void setStateDataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, StateDataContext> strategy) {
+ stateDataContextLookupStrategy =
+ Constraint.isNotNull(strategy, "StateDataContextLookupStrategy lookup strategy cannot be null");
}
@Override
@@ -88,10 +89,16 @@ public class AuthenticationRequestTimeLookupFunction implements Function<Profile
if (input == null) {
return null;
}
- final AuthnRequestStateDataContext authnRequestCtx = authenticationRequestLookupStrategy.apply(input);
- final AuthenticationRequestStateData authnRequest = authnRequestCtx != null ? authnRequestCtx.getAuthnState() : null;
- final Instant authTime = authnRequest != null ? authnRequest.getAuthnRequestTime() : null;
- return authTime == null ? null : authTime.minus(clockSkew);
+ final StateDataContext stateDataCtx = stateDataContextLookupStrategy.apply(input);
+ if (stateDataCtx == null) {
+ return null;
+ }
+ final StateData stateData = stateDataCtx.getStateData();
+ if (stateData instanceof final AuthenticationRequestStateData authnRequest) {
+ final Instant authTime = authnRequest.getRequestTime();
+ return authTime == null ? null : authTime.minus(clockSkew);
+ }
+ return null;
}
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateData.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateData.java
new file mode 100644
index 0000000..790a906
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/CreateAuthenticationStateData.java
@@ -0,0 +1,121 @@
+/*
+ * 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.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+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.slf4j.Logger;
+
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.context.StateDataContext;
+import net.shibboleth.sp.profile.AbstractApplicationAction;
+import net.shibboleth.sp.state.StateData;
+
+/**
+ *
+ * An action that looks up {@link StateData} using a configured lookup strategy
+ * and adds it to a {@link StateDataContext} obtained from the profile request
+ * context using a configured creation strategy.
+ *
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ */
+public class CreateAuthenticationStateData extends AbstractApplicationAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(CreateAuthenticationStateData.class);
+
+ /** Lookup strategy for the contents of the state token, as stored in a {@link StateData} or subclass thereof. */
+ @NonnullAfterInit private Function<ProfileRequestContext,StateData> stateDataLookupStrategy;
+
+ /** Strategy used to create the {@link StateDataContext} to populate. */
+ @Nonnull private Function<ProfileRequestContext,StateDataContext> stateDataContextCreationStrategy;
+
+ /**
+ * Constructor.
+ */
+ public CreateAuthenticationStateData() {
+ stateDataContextCreationStrategy = new ChildContextLookup<>(StateDataContext.class, true);
+ }
+
+ /**
+ * Sets the strategy used to create the {@link StateDataContext}.
+ *
+ * @param strategy creation strategy
+ */
+ public void setStateDataContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,StateDataContext> strategy) {
+ checkSetterPreconditions();
+ stateDataContextCreationStrategy =
+ Constraint.isNotNull(strategy, "StateDataContext creation strategy cannot be null");
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (stateDataLookupStrategy == null) {
+ throw new ComponentInitializationException("StateValueLookupStrategy cannot be null");
+ }
+ }
+
+ /**
+ * Sets the lookup strategy for obtaining the {@link StateData} of the state token.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setStateDataLookupStrategy(@Nonnull final Function<ProfileRequestContext,StateData> strategy) {
+ checkSetterPreconditions();
+
+ stateDataLookupStrategy = Constraint.isNotNull(strategy, "State data lookup strategy cannot be null");
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final StateDataContext stateDataContext = stateDataContextCreationStrategy.apply(profileRequestContext);
+
+ if (stateDataContext == null) {
+ log.error("{} Error creating StateDataContext", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+
+ final StateData stateData = stateDataLookupStrategy.apply(profileRequestContext);
+
+ if (stateData == null) {
+ log.error("{} Error creating StateData", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+
+ stateDataContext.setStateData(stateData);
+ log.debug("{} Created authentication state data for preservation '{}'", getLogPrefix(), stateData);
+
+ }
+
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java
index 0a09b00..34c0d1d 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExchangeCodeForAccessToken.java
@@ -109,8 +109,8 @@ public class ExchangeCodeForAccessToken extends AbstractHttpOAuthAction<TokenRes
log.trace("{} Token request response was successful '{}'",getLogPrefix(),
tokenResponse.indicatesSuccess());
} else {
- throw new MessageHandlerException("Token response was not of the expected format, expected OIDC token "
- + "response, got " + responseObject.getClass().getSimpleName());
+ throw new MessageHandlerException("Token response was not of the expected format, expected OIDC token"
+ + " response, got " + responseObject.getClass().getSimpleName());
}
} catch (final MessageHandlerException e) {
log.error("{} Failed to exchange authorisation code for token result",getLogPrefix(), e);
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java
index 9ad5e4c..27e8976 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ExtractOIDCClaims.java
@@ -269,7 +269,8 @@ public class ExtractOIDCClaims extends AbstractApplicationAction {
accumulator.putAll(attributeContext.getIdPAttributes());
// Extract standard attributes.
- if (standardExtractionStrategy != null && profileConfiguration.isExtractStandardAttributes(profileRequestContext)) {
+ if (standardExtractionStrategy != null &&
+ profileConfiguration.isExtractStandardAttributes(profileRequestContext)) {
log.debug("{} Extracting standard attributes", getLogPrefix());
assert standardExtractionStrategy != null;
final Collection<IdPAttribute> standards = standardExtractionStrategy.apply(endUserClaimsContext);
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeAuthorizationRequest.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeAuthorizationRequest.java
index 6a1b23b..41e5e21 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeAuthorizationRequest.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeAuthorizationRequest.java
@@ -75,7 +75,7 @@ public class InitializeAuthorizationRequest extends AbstractProfileAction {
*/
public void setOAuth2ClientContextLookupStrategy(
@Nonnull final Function<ProfileRequestContext, OAuth2ClientContext> strgy) {
- checkSetterPreconditions();
+ checkSetterPreconditions();
oauth2ClientContextLookupStrategy = Constraint.isNotNull(strgy,
"OAuth2 client context lookup strategy cannot be null");
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
index 32c06e2..38e4e79 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientAuthenticationMethodHandler.java
@@ -80,15 +80,15 @@ import net.shibboleth.shared.primitive.StringSupport;
* TODO split the logic out into commons to share for RP and Proxy
*/
public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractMessageHandler {
+
+ /** Lookup function for parent ProfileRequestContext. */
+ @Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
+ = new ParentProfileRequestContextLookup<>();
/** Class logger. */
@Nonnull
private final Logger log = LoggerFactory.getLogger(InitializeOAuth2ClientAuthenticationMethodHandler.class);
- /** Lookup function for parent ProfileRequestContext. */
- @Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
- = new ParentProfileRequestContextLookup<>();
-
/**
* The strategy used to lookup or create the {@link OAuth2ClientAuthenticationContext}
* for storing the client authentication.
@@ -175,7 +175,7 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
* @param expiry the JWT 'exp' claim offset
*/
public void setJwtBearerExpiryOffset(@Nonnull final Duration expiry) {
- checkSetterPreconditions();
+ checkSetterPreconditions();
jwtBearerExpiryOffset = Constraint.isNotNull(expiry, "jwtBearerExpiryOffset can not be null");
}
@@ -241,7 +241,7 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
*/
public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
checkSetterPreconditions();
- issuerLookupStrategy = Constraint.isNotNull(strategy, "IssuerLookupStrategy lookup strategy cannot be null");;
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "IssuerLookupStrategy lookup strategy cannot be null");
}
@@ -262,7 +262,8 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
final RelyingPartyContext rpCtx = relyingPartyContextLookupStrategy.compose(PRC_LOOKUP).apply(messageContext);
if (rpCtx != null && rpCtx.getConfiguration() != null &&
- rpCtx.getProfileConfig() instanceof final OIDCAuthenticationRelyingPartyProfileConfiguration rpConfig) {
+ rpCtx.getProfileConfig() instanceof final OIDCAuthenticationRelyingPartyProfileConfiguration
+ rpConfig) {
profileConfiguration = rpConfig;
}
if (profileConfiguration == null) {
@@ -423,7 +424,7 @@ public class InitializeOAuth2ClientAuthenticationMethodHandler extends AbstractM
@Nonnull private JWTClaimsSet buildClientAuthenticationJwtClaims(@Nonnull final MessageContext messageContext)
throws MessageHandlerException{
- String audience;
+ final String audience;
if (profileConfiguration.isUseTargetedEndpointAsJWTAudience(PRC_LOOKUP.apply(messageContext))) {
audience = providerMetadata.getTokenEndpointURI().toString();
} else {
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java
index 31e8720..770bf53 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializeOAuth2ClientContext.java
@@ -104,7 +104,7 @@ public class InitializeOAuth2ClientContext extends AbstractAgentAction {
*/
public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
checkSetterPreconditions();
- issuerLookupStrategy = Constraint.isNotNull(strategy, "IssuerLookupStrategy lookup strategy cannot be null");;
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "IssuerLookupStrategy lookup strategy cannot be null");
}
/**
@@ -114,7 +114,7 @@ public class InitializeOAuth2ClientContext extends AbstractAgentAction {
*/
public void setRedirectUriOverrideLookupStrategy(
@Nonnull final Function<ProfileRequestContext, URI> strategy) {
- checkSetterPreconditions();
+ checkSetterPreconditions();
redirectUriOverrideLookupStrategy =
Constraint.isNotNull(strategy, "Redirect URI lookup strategy can not be null");
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializePeerEntityContext.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializePeerEntityContext.java
new file mode 100644
index 0000000..a50e3d0
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/InitializePeerEntityContext.java
@@ -0,0 +1,101 @@
+/*
+ * 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.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.context.StateDataContext;
+import net.shibboleth.sp.state.StateData;
+
+/**
+ * An action that initializes the {@link OIDCPeerEntityContext} based on the issuer value from the {@link StateData}.
+ *
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ */
+public class InitializePeerEntityContext extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull
+ private final Logger log = LoggerFactory.getLogger(InitializePeerEntityContext.class);
+
+ /** Strategy used to locate the {@link StateDataContext}. */
+ @NonnullAfterInit private Function<ProfileRequestContext,StateDataContext> stateDataContextLookupStrategy;
+
+ /**
+ * Set the strategy used to locate the {@link StateDataContext} to pull the issuer out of.
+ *
+ * @param strategy The strategy to set.
+ */
+ public void setStateDataContextLookupStrategy(
+ final Function<ProfileRequestContext, StateDataContext> strategy) {
+ checkSetterPreconditions();
+ stateDataContextLookupStrategy = Constraint.isNotNull(strategy,
+ "stateDataContextLookupStrategy can not be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (null == stateDataContextLookupStrategy) {
+ throw new ComponentInitializationException("StateDataContext lookup strategy cannot be null");
+ }
+ }
+
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final MessageContext messageContext = profileRequestContext.getInboundMessageContext();
+ if (messageContext == null) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return;
+ }
+
+ final OIDCPeerEntityContext peerContext = messageContext.ensureSubcontext(OIDCPeerEntityContext.class);
+ final StateDataContext dataContext = stateDataContextLookupStrategy.apply(profileRequestContext);
+
+ if (dataContext == null) {
+ log.warn("{} No StateDataContext found, can not proceed", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+ final StateData state = dataContext.getStateData();
+ if (state == null) {
+ log.warn("{} No StateData found, can not proceed", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+ peerContext.setIdentifier(state.getAuthenticationAuthority());
+
+ log.debug("{} Initialized inbound context for '{}'", getLogPrefix(), peerContext.getIdentifier());
+ }
+
+
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/IssueStateCookie.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/IssueStateCookie.java
deleted file mode 100644
index d718fda..0000000
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/IssueStateCookie.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * 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.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.google.common.escape.Escaper;
-import com.google.common.net.UrlEscapers;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.sp.context.AgentRequestContext;
-import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
-import net.shibboleth.sp.profile.AbstractApplicationAction;
-import net.shibboleth.sp.profile.SPConstants;
-
-/**
- * Action that issues a cookie used to record state about a request for
- * later enforcement/evaluation.
- *
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link EventIds#INVALID_MESSAGE}
- */
-public class IssueStateCookie extends AbstractApplicationAction {
-
- /** Default cookie prefix. */
- @Nonnull @NotEmpty public static final String DEFAULT_COOKIE_PREFIX = "shibsp_req_";
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(IssueStateCookie.class);
-
- /** Cookie manager. */
- @NonnullAfterInit private CookieManager cookieManager;
-
- /** Cookie prefix. */
- @Nonnull private String cookiePrefix;
-
- /** Whether an error constructing a correlation cookie is fatal. */
- private boolean errorFatal;
-
- /** Lookup strategy for the contents of the state cookie. */
- @NonnullAfterInit private Function<ProfileRequestContext,String> stateValueLookupStrategy;
-
- /** State token value used in cookie name. */
- @NonnullBeforeExec private String stateToken;
-
- /** The value of the state cookie. */
- @NonnullBeforeExec private String stateValue;
-
- /** Constructor. */
- public IssueStateCookie() {
- cookiePrefix = DEFAULT_COOKIE_PREFIX;
- }
-
- /**
- * Sets the {@link CookieManager} to use.
- *
- * @param manager cookie manager instance
- */
- public void setCookieManager(@Nonnull final CookieManager manager) {
- checkSetterPreconditions();
-
- cookieManager = Constraint.isNotNull(manager, "CookieManager cannot be null");
- }
-
- /**
- * Sets the cookie prefix.
- *
- * <p>Defaults to {@link #DEFAULT_COOKIE_PREFIX}.</p>
- *
- * @param prefix cookie prefix
- */
- public void setCookiePrefix(@Nonnull @NotEmpty final String prefix) {
- checkSetterPreconditions();
-
- cookiePrefix = Constraint.isNotNull(StringSupport.trimOrNull(prefix), "Cookie prefix cannot be null or empty");
- }
-
- /**
- * Sets whether an error computing a state token should result in a fatal event.
- *
- * <p>Defaults to false.</p>
- *
- * @param flag flag to set
- */
- public void setErrorFatal(final boolean flag) {
- checkSetterPreconditions();
-
- errorFatal = flag;
- }
-
- /**
- * Sets the lookup strategy for obtaining the contents of the state cookie.
- *
- * @param strategy lookup strategy
- */
- public void setStateValueLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
- checkSetterPreconditions();
-
- stateValueLookupStrategy = Constraint.isNotNull(strategy, "State value lookup strategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (cookieManager == null) {
- throw new ComponentInitializationException("CookieManager cannot be null");
- } else if (stateValueLookupStrategy == null) {
- throw new ComponentInitializationException("State value lookup strategy cannot be null");
- }
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- stateValue = stateValueLookupStrategy.apply(profileRequestContext);
-
- if (stateValue == null) {
- if (errorFatal) {
- log.warn("{} State value was missing", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- } else {
- log.debug("{} No cookie contents available, skipping creation of state cookie", getLogPrefix());
- }
- return false;
- }
-
- final DDF input = ensureAgentRequestContext().getInput();
- if (input != null) {
- stateToken = input.getmember(SPConstants.STATE).string();
- }
-
- if (stateToken == null) {
- if (stateValue != null) {
- if (errorFatal) {
- log.warn("{} Input was missing {} parameter", getLogPrefix(), SPConstants.STATE);
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- } else {
- log.debug("{} Input was missing {} parameter, skipping creation of state cookie", getLogPrefix(),
- SPConstants.STATE);
- }
- }
- return false;
- }
-
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- ensureOutputObjects();
- final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
-
- // We do the crazy stuff to accomodate the cookies being set or unset.
- try {
- RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
- agentRequestContext.getRemotedHttpServletResponse());
-
- cookieManager.purgeStaleCookies(cookiePrefix);
-
- log.debug("{} Tracking state {} against state token {}", getLogPrefix(), stateValue, stateToken);
-
- final Escaper escaper = UrlEscapers.urlFormParameterEscaper();
- final String escapedValue = escaper.escape(stateValue);
- assert escapedValue != null;
- cookieManager.addCookie(cookiePrefix + escaper.escape(stateToken), escapedValue);
- } finally {
- RemotedHttpServletRequestResponseContext.clearCurrent();
- }
- }
-
-}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MapStateTokenToStateValue.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MapStateTokenToStateValue.java
deleted file mode 100644
index 64d64c0..0000000
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MapStateTokenToStateValue.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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.io.IOException;
-import java.util.function.BiFunction;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.context.AgentRequestContext;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
-import net.shibboleth.sp.profile.AbstractApplicationAction;
-
-
-/**
- * Profile action that recovers a state value from a state token and uses a customisable strategy to set it onto the
- * {@link ProfileRequestContext}.
- *
- * <p>Failures encountered during processing may be treated as fatal according to the {@link #errorFatal} flag. If
- * fatal and an error occurs, an {@link EventIds#INVALID_MESSAGE} event is raised.</p>
- *
- * <p>This action does not interpret the state value itself. Instead, the token value consumer is expected to
- * understand the structure and semantics of the recovered state value and set it on the {@link ProfileRequestContext}
- * appropriately.</p>
- *
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link EventIds#INVALID_MESSAGE}
- */
-public class MapStateTokenToStateValue extends AbstractApplicationAction {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(MapStateTokenToStateValue.class);
-
- /** Lookup strategy for state token. */
- @NonnullAfterInit private Function<ProfileRequestContext,String> stateTokenLookupStrategy;
-
- /**
- * A consumer {@link BiFunction} that process the state value and adds it to the profile request context. The
- * returned boolean indicates if the consumer function was successful or not. An unsuccessful outcome may trigger
- * an error depending on the {@link #errorFatal} flag.
- */
- @NonnullAfterInit private BiFunction<ProfileRequestContext, byte[], Boolean> stateValueConsumer;
-
- /** Whether an error mapping the state token to a state value is fatal. Defaults to true. */
- private boolean errorFatal;
-
- /**
- * Constructor.
- */
- public MapStateTokenToStateValue() {
- errorFatal = true;
- }
-
- /**
- * Sets the lookup strategy to obtain the protocol specific state token.
- *
- * @param strategy lookup strategy
- */
- public void setStateTokenLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
- checkSetterPreconditions();
- stateTokenLookupStrategy = Constraint.isNotNull(strategy, "State token lookup strategy cannot be null");
- }
-
- /**
- * Set the state value consumer to add the recovered state value to the profile request context. The return value
- * indicates if the consumer function was successful or not. An unsuccessful outcome may trigger an error
- * depending on the {@link #errorFatal} flag.
- *
- * @param consumer The consumer to set.
- */
- public void setStateValueConsumer(final BiFunction<ProfileRequestContext, byte[], Boolean> consumer) {
- checkSetterPreconditions();
- stateValueConsumer = Constraint.isNotNull(consumer, "StateValueConsumer can not be null");
- }
-
- /**
- * Sets whether an error looking up a state value from a state token should result in a fatal event.
- *
- * <p>Defaults to true.</p>
- *
- * @param flag flag to set
- */
- public void setErrorFatal(final boolean flag) {
- checkSetterPreconditions();
-
- errorFatal = flag;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (stateTokenLookupStrategy == null) {
- throw new ComponentInitializationException("State token lookup strategy cannot be null");
- }
- if (stateValueConsumer == null) {
- throw new ComponentInitializationException("State value consumer cannot be null");
- }
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- ensureOutputObjects();
-
- final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
-
- // We do the crazy stuff to accomodate cookie-backed state management
- // (and to get the relevant state token in the first place).
- try {
- RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
- agentRequestContext.getRemotedHttpServletResponse());
-
- final String token = stateTokenLookupStrategy.apply(profileRequestContext);
- if (token == null) {
- log.debug("{} No state token returned from lookup strategy", getLogPrefix());
- if (errorFatal) {
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- }
- return;
- }
-
- final byte[] stateValue = ensureApplication().getStateTokenManager().recoverFromStateToken(
- ensureAgent(), ensureApplication(), token);
-
- if (stateValue == null && !errorFatal) {
- log.debug("{} No state value found for state token '{}'", getLogPrefix(), token);
- return;
- } else if (stateValue == null && errorFatal){
- log.debug("{} No state value found for state token '{}'", getLogPrefix(), token);
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- return;
- }
-
- final Boolean success = stateValueConsumer.apply(profileRequestContext, stateValue);
- // Treat null as failure
- if ((success == null || Boolean.FALSE.equals(success)) && errorFatal) {
- log.warn("{} Could not set state value", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- return;
- }
-
- } catch (final IOException e) {
- log.warn("{} Exception recovering state value from state token", getLogPrefix(), e);
- if (errorFatal) {
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- }
- } finally {
- RemotedHttpServletRequestResponseContext.clearCurrent();
- }
- }
-
-}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MapStateValueToStateToken.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MapStateValueToStateToken.java
deleted file mode 100644
index b862d24..0000000
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MapStateValueToStateToken.java
+++ /dev/null
@@ -1,165 +0,0 @@
-/*
- * 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.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.context.AgentRequestContext;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
-import net.shibboleth.sp.profile.AbstractApplicationAction;
-import net.shibboleth.sp.profile.StateTokenManager;
-
-/**
- *
- * An action that extracts a state value from the {@link ProfileRequestContext} using a configured lookup strategy
- * and maps that value into an application-managed state token. The resulting token is then stored via a configured
- * consumer for later use: such as placement into an OAuth 2.0 "state" parameter or other state preserving or
- * correlation mechanism.
- *
- * <p>The action delegates the actual token creation and storage to the application's
- * {@link StateTokenManager}.</p>
- *
- * <p>Errors may be ignored or result in an {@link EventIds#IO_ERROR} event.</p>
- *
- * @event {@link EventIds#IO_ERROR}
- */
-public class MapStateValueToStateToken extends AbstractApplicationAction {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(MapStateValueToStateToken.class);
-
- /** Whether an error constructing a state token is fatal. Default is true.*/
- private boolean errorFatal;
-
- /** Lookup strategy for the contents of the state token. */
- @NonnullAfterInit private Function<ProfileRequestContext,String> stateValueLookupStrategy;
-
- /** A consumer that adds the state token to the profile request context.*/
- @NonnullAfterInit private BiConsumer<ProfileRequestContext, String> stateTokenConsumer;
-
- /**
- * Constructor.
- */
- public MapStateValueToStateToken() {
- errorFatal = true;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (stateValueLookupStrategy == null) {
- throw new ComponentInitializationException("StateValueLookupStrategy cannot be null");
- }
- if (stateTokenConsumer == null) {
- throw new ComponentInitializationException("StateTokenConsumer cannot be null");
- }
- }
-
-
- /**
- * Sets whether an error computing a state token should result in a fatal event.
- *
- * <p>Defaults to true.</p>
- *
- * @param flag flag to set
- */
- public void setErrorFatal(final boolean flag) {
- checkSetterPreconditions();
-
- errorFatal = flag;
- }
-
- /**
- * Sets the lookup strategy for obtaining the contents of the state token.
- *
- * @param strategy lookup strategy
- */
- public void setStateValueLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
- checkSetterPreconditions();
-
- stateValueLookupStrategy = Constraint.isNotNull(strategy, "State value lookup strategy cannot be null");
- }
-
- /**
- * Set the consumer used to store off the state token onto the profile request context.
- *
- * @param consumer The stateTokenConsumer to set.
- */
- public void setStateTokenConsumer(@Nonnull final BiConsumer<ProfileRequestContext, String> consumer) {
- checkSetterPreconditions();
-
- stateTokenConsumer = Constraint.isNotNull(consumer, "StateTokenConsumer can not be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- ensureOutputObjects();
-
- final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
-
- // We do the crazy stuff to accomodate cookie-backed state management.
- try {
- RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
- agentRequestContext.getRemotedHttpServletResponse());
-
- try {
- final String stateValue = stateValueLookupStrategy.apply(profileRequestContext);
- if (stateValue != null) {
- final byte[] stateValueBytes = stateValue.getBytes(StandardCharsets.UTF_8);
- // b64 encode is handled by the state manager
- assert stateValueBytes != null;
- final String token = ensureApplication().getStateTokenManager().preserveToStateToken(
- ensureAgent(), ensureApplication(), stateValueBytes);
-
- stateTokenConsumer.accept(profileRequestContext, token);
-
- log.debug("{} State preserved to state token: {}", getLogPrefix(), token);
- } else{
- log.debug("{} State value was null, state not preserved", getLogPrefix());
- if (errorFatal) {
- ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
- return;
- }
- }
- } catch (final IOException | RuntimeException e) {
- log.warn("{} Exception preserving state to token", getLogPrefix(), e);
- if (errorFatal) {
- ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
- }
- }
- } finally {
- RemotedHttpServletRequestResponseContext.clearCurrent();
- }
- }
-
-}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MaxAgeFromAuthenticationRequestLookupFunction.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MaxAgeFromAuthenticationRequestLookupFunction.java
index cf675d1..bfddd25 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MaxAgeFromAuthenticationRequestLookupFunction.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/MaxAgeFromAuthenticationRequestLookupFunction.java
@@ -29,13 +29,14 @@ import net.shibboleth.profile.context.RelyingPartyContext;
import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
import net.shibboleth.shared.annotation.ParameterName;
import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
+import net.shibboleth.sp.state.StateData;
/**
- * Locate the maximum authentication age from the authentication request. If not present in the request, it is taken
- * from the profile configuration associated with the relying party context. Returning a default value if neither are
- * found.
+ * Locate the maximum authentication age from the authentication request state. If not present in the request, it is
+ * taken from the profile configuration associated with the relying party context. Returning a default value if neither
+ * are found.
*/
@NotThreadSafe
public class MaxAgeFromAuthenticationRequestLookupFunction extends AbstractRelyingPartyLookupFunction<Duration> {
@@ -44,9 +45,9 @@ public class MaxAgeFromAuthenticationRequestLookupFunction extends AbstractRelyi
@Nonnull private final Duration maxAgeDefault;
/**
- * Strategy used to locate the {@link AuthnRequestStateDataContext}. Defaults to under the inbound message context.
+ * Strategy used to locate the {@link StateDataContext}. Defaults to under the inbound message context.
*/
- @Nonnull private Function<ProfileRequestContext, AuthnRequestStateDataContext> authenticationRequestLookupStrategy;
+ @Nonnull private Function<ProfileRequestContext, StateDataContext> stateDataContextLookupStrategy;
/**
* Constructor.
@@ -57,12 +58,12 @@ public class MaxAgeFromAuthenticationRequestLookupFunction extends AbstractRelyi
@ParameterName(name = "maxAgeDefault") @Nonnull final Duration defaultAge) {
maxAgeDefault = Constraint.isNotNull(defaultAge, "Max Age default can not be null");
- authenticationRequestLookupStrategy = prc -> {
+ stateDataContextLookupStrategy = prc -> {
final var inboundMsgCtx = prc.getInboundMessageContext();
if (inboundMsgCtx == null) {
return null;
}
- return inboundMsgCtx.getSubcontext(AuthnRequestStateDataContext.class);
+ return inboundMsgCtx.getSubcontext(StateDataContext.class);
};
}
@@ -71,10 +72,10 @@ public class MaxAgeFromAuthenticationRequestLookupFunction extends AbstractRelyi
*
* @param strategy the strategy
*/
- public void setAuthenticationRequestLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, AuthnRequestStateDataContext> strategy) {
- authenticationRequestLookupStrategy = Constraint.isNotNull(strategy,
- "AuthenticationRequestLookupStrategy can not be null");
+ public void setStateDataContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, StateDataContext> strategy) {
+ stateDataContextLookupStrategy = Constraint.isNotNull(strategy,
+ "StateDataContextLookupStrategy can not be null");
}
@Override
@@ -87,10 +88,10 @@ public class MaxAgeFromAuthenticationRequestLookupFunction extends AbstractRelyi
// Max_age from authentication request is authoritative over that from the profile config.
- final AuthnRequestStateDataContext authnRequestCtx = authenticationRequestLookupStrategy.apply(input);
- final AuthenticationRequestStateData authnRequest =
- authnRequestCtx != null ? authnRequestCtx.getAuthnState() : null;
- if (authnRequest != null) {
+ final StateDataContext stateDataCtx = stateDataContextLookupStrategy.apply(input);
+ final StateData stateData = stateDataCtx != null ? stateDataCtx.getStateData() : null;
+
+ if (stateData instanceof final AuthenticationRequestStateData authnRequest) {
final Duration authnRequestMaxAge = authnRequest.getMaxAge();
if (authnRequestMaxAge != null) {
return authnRequestMaxAge;
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/NonceFromAuthenticationRequestStateLookupStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/NonceFromAuthenticationRequestStateLookupStrategy.java
index a55639e..ebdf8f9 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/NonceFromAuthenticationRequestStateLookupStrategy.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/NonceFromAuthenticationRequestStateLookupStrategy.java
@@ -26,9 +26,10 @@ import org.opensaml.profile.context.ProfileRequestContext;
import com.nimbusds.jwt.JWTClaimsSet;
+import net.shibboleth.shared.annotation.ParameterName;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
/**
@@ -40,8 +41,8 @@ public final class NonceFromAuthenticationRequestStateLookupStrategy
implements BiFunction<ProfileRequestContext,JWTClaimsSet, String> {
/** The strategy used to lookup the {@link AuthenticationRequestStateData}.*/
- @Nonnull
- private final Function<ProfileRequestContext, AuthenticationRequestStateData> authenticationStateDataLookupStrategy;
+ @Nonnull private final
+ Function<ProfileRequestContext, AuthenticationRequestStateData> authenticationStateDataLookupStrategy;
/**
*
@@ -50,6 +51,7 @@ public final class NonceFromAuthenticationRequestStateLookupStrategy
* @param strategy the lookup strategy to use
*/
public NonceFromAuthenticationRequestStateLookupStrategy(
+ @ParameterName(name="authenticationStateDataLookupStrategy")
@Nonnull final Function<ProfileRequestContext, AuthenticationRequestStateData> strategy) {
authenticationStateDataLookupStrategy =
Constraint.isNotNull(strategy, "Authentication state data lookup strategy can not be null");
@@ -65,9 +67,10 @@ public final class NonceFromAuthenticationRequestStateLookupStrategy
if (messageContext == null) {
return null;
}
- final AuthnRequestStateDataContext context = messageContext.getSubcontext(AuthnRequestStateDataContext.class);
- if (context != null) {
- return context.getAuthnState();
+ final StateDataContext context = messageContext.getSubcontext(StateDataContext.class);
+ if (context != null && context.getStateData()
+ instanceof final AuthenticationRequestStateData authnRequest) {
+ return authnRequest;
}
return null;
};
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PKCECodeVerifierFromStateLookupStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PKCECodeVerifierFromStateLookupStrategy.java
index bc873bb..8bc15d8 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PKCECodeVerifierFromStateLookupStrategy.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PKCECodeVerifierFromStateLookupStrategy.java
@@ -24,48 +24,53 @@ import org.opensaml.profile.context.ProfileRequestContext;
import net.shibboleth.shared.annotation.ParameterName;
import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
/**
- * A lookup strategy that retrieves the redirect_uri from the {@link AuthenticationRequestStateData} stored in the
- * {@link AuthnRequestStateDataContext} associated with a {@link ProfileRequestContext}.
+ * A lookup strategy that retrieves the PKCE Code verifier from the {@link AuthenticationRequestStateData} stored in the
+ * {@link StateDataContext} associated with a {@link ProfileRequestContext}.
*/
@ThreadSafe
-public class PKCECodeVerifierFromStateLookupStrategy implements Function<ProfileRequestContext, String> {
+public final class PKCECodeVerifierFromStateLookupStrategy implements Function<ProfileRequestContext, String> {
/**
- * Strategy used to locate the {@link AuthnRequestStateDataContext}.
+ * Strategy used to locate the {@link StateDataContext}.
*/
@Nonnull
- private final Function<ProfileRequestContext, AuthnRequestStateDataContext> authenticationRequestLookupStrategy;
+ private final Function<ProfileRequestContext, StateDataContext> stateDataContextLookupStrategy;
/**
* Constructor.
*
* @param strategy strategy to find the authentication request state context
*/
- public PKCECodeVerifierFromStateLookupStrategy(@ParameterName(name = "authenticationRequestLookupStrategy")
- @Nonnull final Function<ProfileRequestContext, AuthnRequestStateDataContext> strategy) {
- authenticationRequestLookupStrategy = Constraint.isNotNull(strategy,
- "authenticationRequestLookupStrategy can not be null");
+ public PKCECodeVerifierFromStateLookupStrategy(@ParameterName(name = "stateDataContextLookupStrategy")
+ @Nonnull final Function<ProfileRequestContext, StateDataContext> strategy) {
+ stateDataContextLookupStrategy = Constraint.isNotNull(strategy,
+ "StateDataContextLookupStrategy can not be null");
}
@Override
- @Nullable public String apply(final ProfileRequestContext prc) {
-
- final AuthnRequestStateDataContext authnRequestCtx = authenticationRequestLookupStrategy.apply(prc);
-
- if (authnRequestCtx == null) {
+ @Nullable public String apply(@Nullable final ProfileRequestContext prc) {
+ if (prc == null) {
return null;
}
- final AuthenticationRequestStateData authnRequest = authnRequestCtx.getAuthnState();
- if (authnRequest == null) {
- return null;
+ final StateDataContext stateDataCtx = stateDataContextLookupStrategy.apply(prc);
+ if (stateDataCtx != null &&
+ stateDataCtx.getStateData() instanceof final AuthenticationRequestStateData authnRequest) {
+
+ final String verifier = StringSupport.trimOrNull(authnRequest.getPkceCodeVerifier());
+ if (verifier != null) {
+ return verifier;
+ }
+
}
- return authnRequest.getPkceCodeVerifier();
+ return null;
+
}
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PrepareOIDCInboundMessageContext.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PrepareOIDCInboundMessageContext.java
index bfcff9c..88f4359 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PrepareOIDCInboundMessageContext.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/PrepareOIDCInboundMessageContext.java
@@ -84,7 +84,7 @@ public class PrepareOIDCInboundMessageContext extends AbstractProfileAction {
* @param flag the flag to set.
*/
public void setAddToExistingInboundMessageContext(final boolean flag) {
- checkSetterPreconditions();
+ checkSetterPreconditions();
addToExistingInboundMessageContextPredicate =
flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RedirectUriFromStateLookupStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RedirectUriFromStateLookupStrategy.java
index e6b0060..8739d86 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RedirectUriFromStateLookupStrategy.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RedirectUriFromStateLookupStrategy.java
@@ -15,6 +15,7 @@
package net.shibboleth.sp.oidc.profile.impl;
import java.net.URI;
+import java.net.URISyntaxException;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -22,34 +23,41 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
import net.shibboleth.shared.annotation.ParameterName;
import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
+import net.shibboleth.sp.state.StateData;
/**
- * A lookup strategy that retrieves the redirect_uri from the {@link AuthenticationRequestStateData} stored in the
- * {@link AuthnRequestStateDataContext} associated with a {@link ProfileRequestContext}.
+ * A lookup strategy that retrieves the response location from the {@link AuthenticationRequestStateData} stored in the
+ * {@link StateDataContext} associated with a {@link ProfileRequestContext}, converts it to a redirect_uri
+ * and returns it. Any failure returns a <code>null</code> URI.
*/
@ThreadSafe
public class RedirectUriFromStateLookupStrategy implements Function<ProfileRequestContext, URI> {
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(RedirectUriFromStateLookupStrategy.class);
+
/**
- * Strategy used to locate the {@link AuthnRequestStateDataContext}.
+ * Strategy used to locate the {@link StateDataContext}.
*/
@Nonnull
- private final Function<ProfileRequestContext, AuthnRequestStateDataContext> authenticationRequestLookupStrategy;
+ private final Function<ProfileRequestContext, StateDataContext> stateDataContextLookupStrategy;
/**
* Constructor.
*
* @param strategy strategy to find the authentication request state context
*/
- public RedirectUriFromStateLookupStrategy(@ParameterName(name = "authenticationRequestLookupStrategy")
- @Nonnull final Function<ProfileRequestContext, AuthnRequestStateDataContext> strategy) {
- authenticationRequestLookupStrategy = Constraint.isNotNull(strategy,
+ public RedirectUriFromStateLookupStrategy(@ParameterName(name = "stateDataContextLookupStrategy")
+ @Nonnull final Function<ProfileRequestContext, StateDataContext> strategy) {
+ stateDataContextLookupStrategy = Constraint.isNotNull(strategy,
"authenticationRequestLookupStrategy can not be null");
}
@@ -57,16 +65,26 @@ public class RedirectUriFromStateLookupStrategy implements Function<ProfileReque
@Override
@Nullable public URI apply(final ProfileRequestContext prc) {
- final AuthnRequestStateDataContext authnRequestCtx = authenticationRequestLookupStrategy.apply(prc);
+ final StateDataContext stateDataCtx = stateDataContextLookupStrategy.apply(prc);
- if (authnRequestCtx == null) {
+ if (stateDataCtx == null) {
return null;
}
- final AuthenticationRequestStateData authnRequest = authnRequestCtx.getAuthnState();
- if (authnRequest == null) {
- return null;
+ final StateData stateData = stateDataCtx.getStateData();
+ if (stateData instanceof final AuthenticationRequestStateData authnRequest) {
+ if (authnRequest.getResponseLocation() == null) {
+ log.warn("Unable to find redirect_uri from authentication request state data");
+ return null;
+ }
+ try {
+ return new URI(authnRequest.getResponseLocation());
+ } catch (final URISyntaxException e) {
+ log.error("Unable to convert response location '{}' to redirect_uri",
+ authnRequest.getResponseLocation(), e);
+ return null;
+ }
}
- return authnRequest.getRedirectUri();
+ return null;
}
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRClaimsFromStateLookupStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRClaimsFromStateLookupStrategy.java
index bbf1b53..232e43b 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRClaimsFromStateLookupStrategy.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRClaimsFromStateLookupStrategy.java
@@ -30,11 +30,12 @@ import com.nimbusds.jwt.JWTClaimsSet;
import net.shibboleth.shared.annotation.ParameterName;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
+import net.shibboleth.sp.state.StateData;
/**
- * Strategy that looks up any requested ACR claims from the saved off authentication request state. Not judgement is
+ * Strategy that looks up any requested ACR claims from the saved off authentication request state. No judgement is
* made over how these ACR values were requested, for example, either as essential or optional.
*/
@ThreadSafe
@@ -42,18 +43,18 @@ public class RequestedACRClaimsFromStateLookupStrategy
implements BiFunction<ProfileRequestContext,JWTClaimsSet, List<String>> {
/**
- * Strategy used to locate the {@link AuthnRequestStateDataContext}.
+ * Strategy used to locate the {@link StateDataContext}.
*/
- @Nonnull private final Function<ProfileRequestContext, AuthnRequestStateDataContext> authenticationRequestLookupStrategy;
+ @Nonnull private final Function<ProfileRequestContext, StateDataContext> stateDataContextLookupStrategy;
/**
* Constructor.
*
* @param strategy strategy to find the authentication request state context
*/
- public RequestedACRClaimsFromStateLookupStrategy(@ParameterName(name = "authenticationRequestLookupStrategy")
- @Nonnull final Function<ProfileRequestContext, AuthnRequestStateDataContext> strategy) {
- authenticationRequestLookupStrategy = Constraint.isNotNull(strategy,
+ public RequestedACRClaimsFromStateLookupStrategy(@ParameterName(name = "stateDataContextLookupStrategy")
+ @Nonnull final Function<ProfileRequestContext, StateDataContext> strategy) {
+ stateDataContextLookupStrategy = Constraint.isNotNull(strategy,
"authenticationRequestLookupStrategy can not be null");
}
@@ -61,20 +62,20 @@ public class RequestedACRClaimsFromStateLookupStrategy
@Override
@Nonnull public List<String> apply(final ProfileRequestContext prc, final JWTClaimsSet claims) {
- final AuthnRequestStateDataContext authnRequestCtx = authenticationRequestLookupStrategy.apply(prc);
+ final StateDataContext stateDataCtx = stateDataContextLookupStrategy.apply(prc);
- if (authnRequestCtx == null) {
+ if (stateDataCtx == null) {
return CollectionSupport.emptyList();
}
- final AuthenticationRequestStateData authnRequest = authnRequestCtx.getAuthnState();
- if (authnRequest == null) {
- return CollectionSupport.emptyList();
- }
- return authnRequest.getAcrs().stream()
- .filter(Objects::nonNull)
- .map(String::trim)
- .filter(s -> !s.isEmpty()).collect(CollectionSupport.nonnullCollector(Collectors.toList())).get();
+ final StateData stateData = stateDataCtx.getStateData();
+ if (stateData instanceof final AuthenticationRequestStateData authnRequest) {
+ return authnRequest.getAcrs().stream()
+ .filter(Objects::nonNull)
+ .map(String::trim)
+ .filter(s -> !s.isEmpty()).collect(CollectionSupport.nonnullCollector(Collectors.toList())).get();
+ }
+ return CollectionSupport.emptyList();
}
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRValidationActivationCondition.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRValidationActivationCondition.java
index 01234b6..02f38b3 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRValidationActivationCondition.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRValidationActivationCondition.java
@@ -78,6 +78,6 @@ public class RequestedACRValidationActivationCondition implements BiPredicate<Pr
log.debug("No profileConfiguration available, activating ACR validation");
return true;
}
- return profileConfiguration.shouldValidateAcrValue(prc);
+ return profileConfiguration.isValidateAcrValue(prc);
}
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ResolveStateCookie.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ResolveStateCookie.java
deleted file mode 100644
index cfcb356..0000000
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ResolveStateCookie.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * 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.util.function.BiFunction;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.google.common.escape.Escaper;
-import com.google.common.net.UrlEscapers;
-
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.sp.context.AgentRequestContext;
-import net.shibboleth.sp.messaging.RemotedHttpServletRequestResponseContext;
-import net.shibboleth.sp.profile.AbstractApplicationAction;
-
-/**
- * Action that processes a previously issued state cookie, referenced by a state token, submitted with the request and
- * extracts the value for use by subsequent validation steps. The cookie is immediately unset once retrieved.
- *
- * <p>The value is set onto the context tree using a custom consumer strategy.</p>
- *
- * <p>The absence of either state value or cookie value can either be treated as fatal or not fatal depending on the
- * errorFatal flag.</p>
- *
- * @event {@link EventIds#PROCEED_EVENT_ID}
- * @event {@link EventIds#INVALID_MESSAGE}
- */
-public class ResolveStateCookie extends AbstractApplicationAction {
-
- /** Default cookie prefix. */
- @Nonnull @NotEmpty public static final String DEFAULT_COOKIE_PREFIX = "_shibsp_req_";
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(ResolveStateCookie.class);
-
- /** Cookie manager. */
- @NonnullAfterInit private CookieManager cookieManager;
-
- /** Cookie prefix. */
- @Nonnull private String cookiePrefix;
-
- /** Lookup strategy for state token value. */
- @NonnullAfterInit private Function<ProfileRequestContext,String> stateTokenLookupStrategy;
-
- /**
- * A consumer (as a function) to set the cookie value into the appropriate context in the tree. The return boolean
- * indicates if the consumer function was successful or not. An unsuccessful outcome may trigger an error depending
- * on the {@link #errorFatal} flag.
- */
- @Nullable private BiFunction<ProfileRequestContext, String, Boolean> cookieValueConsumerStrategy;
-
- /** Whether an error recovering a correlation cookie is fatal. */
- private boolean errorFatal;
-
- /** State token accompanying request. */
- @NonnullBeforeExec private String stateToken;
-
- /** Constructor. */
- public ResolveStateCookie() {
- cookiePrefix = DEFAULT_COOKIE_PREFIX;
- }
-
- /**
- * Sets the {@link CookieManager} to use.
- *
- * @param manager cookie manager instance
- */
- public void setCookieManager(@Nonnull final CookieManager manager) {
- checkSetterPreconditions();
-
- cookieManager = Constraint.isNotNull(manager, "CookieManager cannot be null");
- }
-
- /**
- * Set the consumer function strategy to use to set the cookie value into the context tree. The return value
- * indicates if the consumer function was successful or not. An unsuccessful outcome may trigger an error
- * depending on the errorFatal flag.
- *
- * @param strategy The strategy to set.
- */
- public void setCookieValueConsumerStrategy(final BiFunction<ProfileRequestContext, String, Boolean> strategy) {
- checkSetterPreconditions();
- cookieValueConsumerStrategy = Constraint.isNotNull(strategy,
- "cookieValueConsumerStrategy can not be null");
- }
-
- /**
- * Sets the cookie prefix.
- *
- * <p>Defaults to {@link ResolveStateCookie#DEFAULT_COOKIE_PREFIX}.</p>
- *
- * @param prefix cookie prefix
- */
- public void setCookiePrefix(@Nonnull @NotEmpty final String prefix) {
- checkSetterPreconditions();
-
- cookiePrefix = Constraint.isNotNull(StringSupport.trimOrNull(prefix), "Cookie prefix cannot be null or empty");
- }
-
- /**
- * Sets whether an error recovering a state token should result in a fatal event.
- *
- * <p>Defaults to false.</p>
- *
- * @param flag flag to set
- */
- public void setErrorFatal(final boolean flag) {
- checkSetterPreconditions();
-
- errorFatal = flag;
- }
-
- /**
- * Sets the lookup strategy for obtaining the state token.
- *
- * @param strategy lookup strategy
- */
- public void setStateTokenLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
- checkSetterPreconditions();
-
- stateTokenLookupStrategy = Constraint.isNotNull(strategy, "State token lookup strategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (cookieManager == null) {
- throw new ComponentInitializationException("CookieManager cannot be null");
- } else if (stateTokenLookupStrategy == null) {
- throw new ComponentInitializationException("State token lookup strategy cannot be null");
- }
- }
-
- /** {@inheritDoc} */
- @Override
- protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
- if (!super.doPreExecute(profileRequestContext)) {
- return false;
- }
-
- stateToken = stateTokenLookupStrategy.apply(profileRequestContext);
- if (stateToken == null) {
- if (!errorFatal) {
- log.debug("{} No state token found in request, non-fatal skipping correlation cookie processing",
- getLogPrefix());
- return false;
- } else {
- log.warn("{} No state token found in request, fatal error", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- return false;
- }
- }
- // If error is fatal, we do not skip processing here.
- return true;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
- ensureOutputObjects();
- final AgentRequestContext agentRequestContext = ensureAgentRequestContext();
-
- // We do the crazy stuff to accomodate the cookies being read and unset.
- try {
- RemotedHttpServletRequestResponseContext.loadCurrent(agentRequestContext.getRemotedHttpServletRequest(),
- agentRequestContext.getRemotedHttpServletResponse());
-
- final Escaper escaper = UrlEscapers.urlFormParameterEscaper();
- final String cookieName = cookiePrefix + escaper.escape(stateToken);
-
- final String value = cookieManager.getCookieValue(cookieName, null);
- cookieManager.unsetCookie(cookieName);
-
- if (value == null && !errorFatal) {
- log.debug("{} No cookie found for state token '{}'", getLogPrefix(), stateToken);
- return;
- } else if (value == null){
- log.debug("{} No cookie found for state token '{}'", getLogPrefix(), stateToken);
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- return;
- }
-
- if (cookieValueConsumerStrategy != null) {
- final Boolean success = cookieValueConsumerStrategy.apply(profileRequestContext, value);
- // Treat null as failure
- if ((success == null || Boolean.FALSE.equals(success)) && errorFatal) {
- log.warn("{} Could not set correlation cookie", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
- return;
- }
- log.debug("{} Resolved cookie for state token '{}'", getLogPrefix(), stateToken);
- } else {
- log.debug("{} No cookie value consumer strategy defined, cookie value not processed", getLogPrefix());
- }
- } finally {
- RemotedHttpServletRequestResponseContext.clearCurrent();
- }
- }
-
-}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/SetAuthenticationRequestToPeerContextConsumer.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/SetAuthenticationRequestToPeerContextConsumer.java
deleted file mode 100644
index 4a3cce5..0000000
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/SetAuthenticationRequestToPeerContextConsumer.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * 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.nio.charset.StandardCharsets;
-import java.util.function.BiFunction;
-import java.util.function.Predicate;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.messaging.context.MessageContext;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.PredicateSupport;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.primitive.StringSupport;
-import net.shibboleth.shared.security.DataSealer;
-import net.shibboleth.shared.security.DataSealerException;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
-import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
-
-/**
- * A consumer function that converts the authentication request state data JSON into an
- * {@link AuthenticationRequestStateData} object and sets it onto an {@link AuthnRequestStateDataContext} within the
- * inbound message context, along with setting the issuer onto the {@link OIDCPeerEntityContext}.
- *
- * <p>The supplied byte array is assumed to represent UTF‑8 JSON, already Base64‑decoded upstream.</p>
- *
- * <p>In production use, you should always attempt to unseal the authentication request state, such that it will fail if
- * it was not sealed. Allowing unsealed authentication request state should only be used for testing.</p>
- */
-//TODO this class is complex and more fitting a decoder type mechanism
-public class SetAuthenticationRequestToPeerContextConsumer extends AbstractIdentifiableInitializableComponent
- implements BiFunction<ProfileRequestContext, byte[], Boolean> {
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(SetAuthenticationRequestToPeerContextConsumer.class);
-
- /** JSON object mapper. */
- @NonnullAfterInit private ObjectMapper objectMapper;
-
- /** Optional data sealer to use. */
- @Nullable private DataSealer dataSealer;
-
- /**
- * If the dataSealer is provided should it be used to unwrap the authentication request state? Defaults to true,
- * that is, if the dataSealer is provided, always attempt to unwrap the state.
- */
- @Nonnull private Predicate<ProfileRequestContext> unsealState;
-
- /** Constructor.*/
- public SetAuthenticationRequestToPeerContextConsumer() {
- unsealState = PredicateSupport.alwaysTrue();
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
-
- if (objectMapper == null) {
- throw new ComponentInitializationException("ObjectMapper cannot be null");
- }
- }
-
- /**
- * Set the JSON {@link ObjectMapper} to use for serialization.
- *
- * @param mapper object mapper
- */
- public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
- checkSetterPreconditions();
-
- objectMapper = Constraint.isNotNull(mapper, "ObjectMapper cannot be null");
- }
-
- /**
- * Sets {@link DataSealer} to use.
- *
- * @param sealer data sealer
- */
- public void setDataSealer(@Nullable final DataSealer sealer) {
- checkSetterPreconditions();
-
- dataSealer = sealer;
- }
-
- /**
- * Set the predicate to determine whether to unseal the state.
- *
- * @param predicate the seal state predicate to set.
- */
- public void setUnsealStatePredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
- checkSetterPreconditions();
-
- unsealState = Constraint.isNotNull(predicate, "Seal state predicate can not be null");
- }
-
- /**
- * Set the flag to determine whether to unseal the state.
- *
- * @param flag the flag to set.
- */
- public void setUnsealState(final boolean flag) {
- checkSetterPreconditions();
-
- unsealState = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
- }
-
- /** {@inheritDoc} */
- @Override
- public Boolean apply(@Nullable final ProfileRequestContext profileRequestContext, @Nullable final byte[] value) {
- checkComponentActive();
- if (profileRequestContext == null || value == null) {
- log.trace("Authentication request value could not be decoded, the input value was null");
- return false;
- }
- // The value is already base64 decoded by the token manager
- String valueString = new String(value, StandardCharsets.UTF_8);
- // Possibly unseal and convert to class
- try {
- if (unsealState.test(profileRequestContext) && dataSealer != null) {
- valueString = dataSealer.unwrap(valueString);
- }
- final AuthenticationRequestStateData authnState =
- objectMapper.readValue(valueString, AuthenticationRequestStateData.class);
- log.debug("Recovered authentication request state '{}'", authnState);
- final MessageContext inboundCtx = profileRequestContext.getInboundMessageContext();
- if (inboundCtx == null) {
- log.trace("There is no Inbound Context, cannot set authentication request data");
- return false;
- }
- if (StringSupport.trimOrNull(authnState.getAuthenticationAuthority()) == null) {
- // Fail early as this is usually terminal
- log.debug("Authenticating authority could not be recovered");
- return false;
- }
- // Add identifier to the peer context now we know it
- inboundCtx.ensureSubcontext(OIDCPeerEntityContext.class)
- .setIdentifier(authnState.getAuthenticationAuthority());
- // Attach the recovered authentication request state information to the inbound context for later use
- inboundCtx.ensureSubcontext(AuthnRequestStateDataContext.class).setAuthnState(authnState);
- return true;
-
- } catch (final JsonProcessingException | DataSealerException e) {
- log.error("Unable to process authentication request state data, was it sealed and you are not unsealing it?"
- , e);
- return false;
- }
- }
-
-}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/SetAuthenticationStateTokenConsumer.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/SetAuthenticationStateTokenConsumer.java
deleted file mode 100644
index 80d0357..0000000
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/SetAuthenticationStateTokenConsumer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.util.function.BiConsumer;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.messaging.context.MessageContext;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
-
-/**
- * A consumer that sets the authentication state token on the {@link AuthnRequestStateDataContext} in the outbound
- * message context of the {@link ProfileRequestContext}.
- */
-public class SetAuthenticationStateTokenConsumer implements BiConsumer<ProfileRequestContext, String>{
-
- /** Class logger. */
- @Nonnull private final Logger log = LoggerFactory.getLogger(SetAuthenticationStateTokenConsumer.class);
-
- /** {@inheritDoc} */
- @Override
- public void accept(final ProfileRequestContext prc, final String token) {
- final MessageContext outbound = prc.getOutboundMessageContext();
- if (outbound == null) {
- return;
- }
- log.trace("Setting authentication state token '{}' onto token context", token);
- final AuthnRequestStateDataContext stateCtx = outbound.ensureSubcontext(AuthnRequestStateDataContext.class);
- stateCtx.setToken(token);
-
- }
-
-}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StandardExtractionStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StandardExtractionStrategy.java
index e955b10..2fdcd7f 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StandardExtractionStrategy.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StandardExtractionStrategy.java
@@ -81,6 +81,7 @@ public class StandardExtractionStrategy implements Function<EndUserClaimsContext
authnContextClassRefAttributeId = StringSupport.trimOrNull(id);
}
+ /** {@inheritDoc} */
@Nullable public Collection<IdPAttribute> apply(@Nullable final EndUserClaimsContext claimsContext) {
final JWTClaimsSet idTokenClaims = claimsContext != null ? claimsContext.getUnprocessedIdTokenClaims() : null;
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StateTokenLookupStrategy.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StateTokenLookupStrategy.java
new file mode 100644
index 0000000..f906585
--- /dev/null
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/StateTokenLookupStrategy.java
@@ -0,0 +1,72 @@
+/*
+ * 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.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+
+import net.shibboleth.oidc.profile.core.StateToken;
+import net.shibboleth.shared.annotation.ParameterName;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.sp.context.StateDataContext;
+
+/**
+ * A strategy function that looks up the state value from the token stored in the {@link StateDataContext}. This
+ * keys/links the OAuth 2.0 state to the stored authentication request data.
+*/
+public class StateTokenLookupStrategy implements Function<MessageContext, StateToken> {
+
+ /** Lookup function for parent ProfileRequestContext. */
+ @Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
+ = new ParentProfileRequestContextLookup<>();
+
+
+ /** Strategy used to locate the {@link StateDataContext}. */
+ @Nonnull private final Function<ProfileRequestContext,StateDataContext> stateDataContextLookupStrategy;
+
+ /**
+ *
+ * Constructor.
+ *
+ * @param strategy the lookup strategy used to locate the state data context
+ */
+ public StateTokenLookupStrategy(@Nonnull @ParameterName(name="stateDataContextLookupStrategy") final
+ Function<ProfileRequestContext, StateDataContext> strategy) {
+ stateDataContextLookupStrategy =
+ Constraint.isNotNull(strategy, "StateDataContextLookupStrategy can not be null");
+ }
+
+
+ /** {@inheritDoc} */
+ @Override
+ public StateToken apply(final MessageContext messageCtx) {
+ if (messageCtx == null) {
+ return null;
+ }
+ final StateDataContext dataContext = stateDataContextLookupStrategy.apply(PRC_LOOKUP.apply(messageCtx));
+ if (dataContext == null) {
+ return null;
+ }
+
+ return new StateToken(dataContext.getStateToken(), null);
+
+ }
+
+}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateUserInfoJSONObjectClaims.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateUserInfoJSONObjectClaims.java
index 627b95c..9283bb2 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateUserInfoJSONObjectClaims.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/impl/ValidateUserInfoJSONObjectClaims.java
@@ -87,7 +87,7 @@ public class ValidateUserInfoJSONObjectClaims extends AbstractProfileAction {
*/
public void setTokenResponseContextLookupStrategy(
@Nonnull final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
- checkSetterPreconditions();
+ checkSetterPreconditions();
tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
"TokenResponseContext lookup strategy cannot be null");
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthTimeRequestedActivationConditionTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthTimeRequestedActivationConditionTest.java
index c483f0f..8c442e5 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthTimeRequestedActivationConditionTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthTimeRequestedActivationConditionTest.java
@@ -17,11 +17,13 @@ package net.shibboleth.sp.oidc.profile.impl;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
+import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.nimbusds.jwt.JWTClaimsSet;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
@@ -33,13 +35,18 @@ public class AuthTimeRequestedActivationConditionTest extends BaseApplicationAct
/** The condition to test.*/
private AuthTimeRequestedActivationCondition condition;
+ @BeforeMethod
+ public void setup() throws ComponentInitializationException {
+ super.beforeMethod();
+ }
+
@Test
public void testAuthTimeRequested() {
final AuthenticationRequestStateData state = new AuthenticationRequestStateData()
.setAuthTimeRequired(true);
condition = new AuthTimeRequestedActivationCondition(prc ->
- new AuthnRequestStateDataContext().setAuthnState(state));
+ new StateDataContext().setStateData(state));
final boolean requested = condition.test(prc, null);
assertTrue(requested);
@@ -53,7 +60,7 @@ public class AuthTimeRequestedActivationConditionTest extends BaseApplicationAct
.setAuthTimeRequired(false);
condition = new AuthTimeRequestedActivationCondition(
- prcArg -> new AuthnRequestStateDataContext().setAuthnState(state)
+ prcArg -> new StateDataContext().setStateData(state)
);
final boolean requested = condition.test(prc, null);
@@ -75,7 +82,7 @@ public class AuthTimeRequestedActivationConditionTest extends BaseApplicationAct
public void testFailSecureWhenAuthnStateIsNull() {
// Context present but authnState is null -> should default to true (fail-secure)
condition = new AuthTimeRequestedActivationCondition(
- prcArg -> new AuthnRequestStateDataContext().setAuthnState(null)
+ prcArg -> new StateDataContext().setStateData(null)
);
final boolean requested = condition.test(prc, null);
@@ -88,7 +95,7 @@ public class AuthTimeRequestedActivationConditionTest extends BaseApplicationAct
.setAuthTimeRequired(true);
condition = new AuthTimeRequestedActivationCondition(
- prcArg -> new AuthnRequestStateDataContext().setAuthnState(state)
+ prcArg -> new StateDataContext().setStateData(state)
);
final JWTClaimsSet claims1 = new JWTClaimsSet.Builder().claim("foo", "bar").build();
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestStateForStorageStrategyTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestStateForStorageStrategyTest.java
index 11b3cc3..b2edbb6 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestStateForStorageStrategyTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestStateForStorageStrategyTest.java
@@ -14,22 +14,18 @@
package net.shibboleth.sp.oidc.profile.impl;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import java.time.Duration;
import java.time.Instant;
-import org.mockito.Mockito;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.Base64Variants;
import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
@@ -39,7 +35,7 @@ import com.nimbusds.openid.connect.sdk.Nonce;
import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.security.DataSealer;
+import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
/**
@@ -86,39 +82,28 @@ public class AuthenticationRequestStateForStorageStrategyTest extends BaseApplic
maxAge = Duration.ofMinutes(1);
authnRequest.setMaxAge(maxAge);
assert mapper != null;
- strategy.setAuthenticationRequestLookupStrategy(prc -> authnRequest);
- strategy.setObjectMapper(mapper);
+ strategy.setAuthenticationRequestLookupStrategy(prc -> authnRequest);
}
@Test
public void testSuccessfulConstruction() throws Exception {
strategy.initialize();
- final String requestState = strategy.apply(prc);
-
- final JsonNode root = mapper.readTree(requestState);
- assertTrue(root.hasNonNull("nonce"), "Nonce should be present and non-null");
- assertTrue(root.hasNonNull("authority"), "Issuer should be present and non-null");
- assertTrue(root.hasNonNull("pkce_code_verifier"), "Pkce_code_verifier should be present and non-null");
- assertTrue(root.hasNonNull("authn_request_time"), "authn_request_time should be present and non-null");
- assertTrue(root.hasNonNull("max_age"), "max_age should be present and non-null");
- assertTrue(root.hasNonNull("is_auth_time_required"), "is_auth_time_required should be present and non-null");
- assertTrue(root.get("is_auth_time_required").asBoolean(),"is_auth_time_required should be required");
- }
-
- @Test
- public void testFailedConstruction_NoAuthenticatingAuthority() throws Exception {
- strategy.setAuthenticationAuthorityLookupStrategy(prc -> null);
- strategy.initialize();
- final String requestState = strategy.apply(prc);
-
- assertNull(requestState, "no authenticating authority should result in a null construction");
+ final AuthenticationRequestStateData requestState = strategy.apply(prc);
+ assert requestState != null;
+ assertTrue(requestState.getNonce()!=null, "Nonce should be present and non-null");
+ assertTrue(requestState.getAuthenticationAuthority()!=null,
+ "Authenitcation authority should be present and non-null");
+ assertTrue(requestState.getPkceCodeVerifier()!=null, "Pkce_code_verifier should be present and non-null");
+ assertTrue(requestState.getRequestTime()!=null, "authn_request_time should be present and non-null");
+ assertTrue(requestState.getMaxAge()!=null, "max_age should be present and non-null");
+ assertTrue(requestState.isAuthTimeRequired(), "is_auth_time_required should be present and non-null");
}
@Test
public void testFailedConstruction_NoAuthenticationRequest() throws Exception {
strategy.setAuthenticationRequestLookupStrategy(prc -> null);
strategy.initialize();
- final String requestState = strategy.apply(prc);
+ final AuthenticationRequestStateData requestState = strategy.apply(prc);
assertNull(requestState, "no authentication request should result in a null construction");
}
@@ -127,56 +112,16 @@ public class AuthenticationRequestStateForStorageStrategyTest extends BaseApplic
public void testSuccessfulConstruction_NoPKCE() throws Exception {
authnRequest.setCodeVerifier(null);
strategy.initialize();
- final String requestState = strategy.apply(prc);
-
- final JsonNode root = mapper.readTree(requestState);
- assertTrue(root.hasNonNull("nonce"), "Nonce should be present and non-null");
- assertTrue(root.hasNonNull("authority"), "Issuer should be present and non-null");
- assertNull(root.get("pkce_code_verifier"), "Pkce_code_verifier should be null");
- assertTrue(root.hasNonNull("authn_request_time"), "authn_request_time should be present and non-null");
- assertTrue(root.hasNonNull("max_age"), "max_age should be present and non-null");
- assertTrue(root.hasNonNull("is_auth_time_required"), "is_auth_time_required should be present and non-null");
- assertTrue(root.get("is_auth_time_required").asBoolean(),"is_auth_time_required should be required");
-
- }
-
- @SuppressWarnings("null")
- @Test
- public void testSuccessfulConstruction_WithDataSealer() throws Exception {
- final DataSealer sealer = Mockito.mock(DataSealer.class);
- Mockito.when(sealer.wrap(Mockito.anyString())).thenReturn("sealed");
- strategy.setDataSealer(sealer);
- strategy.initialize();
- final String requestState = strategy.apply(prc);
- assertNotNull(requestState, "request state should not be null");
- assert requestState != null;
- assertTrue(requestState.contains("sealed"), "request state should be sealed");
-
- }
-
- @SuppressWarnings("null")
- @Test
- public void testSuccessfulConstruction_WithDataSealerButSealedPredicateIsFalse() throws Exception {
- final DataSealer sealer = Mockito.mock(DataSealer.class);
- Mockito.when(sealer.wrap(Mockito.anyString())).thenReturn("sealed");
- strategy.setDataSealer(sealer);
- strategy.setSealStatePredicate(prc -> false);
- strategy.initialize();
+ final AuthenticationRequestStateData requestState = strategy.apply(prc);
- final String requestState = strategy.apply(prc);
-
- assertNotNull(requestState, "request state should not be null");
assert requestState != null;
- assertFalse(requestState.contains("sealed"), "request state should be sealed");
-
- final JsonNode root = mapper.readTree(requestState);
- assertTrue(root.hasNonNull("nonce"), "Nonce should be present and non-null");
- assertTrue(root.hasNonNull("authority"), "Issuer should be present and non-null");
- assertTrue(root.hasNonNull("pkce_code_verifier"), "Pkce_code_verifier should be present and non-null");
- assertTrue(root.hasNonNull("authn_request_time"), "authn_request_time should be present and non-null");
- assertTrue(root.hasNonNull("max_age"), "max_age should be present and non-null");
- assertTrue(root.hasNonNull("is_auth_time_required"), "is_auth_time_required should be present and non-null");
- assertTrue(root.get("is_auth_time_required").asBoolean(),"is_auth_time_required should be required");
+ assertTrue(requestState.getNonce()!=null, "Nonce should be present and non-null");
+ assertTrue(requestState.getAuthenticationAuthority()!=null,
+ "Authenitcation authority should be present and non-null");
+ assertTrue(requestState.getPkceCodeVerifier()==null, "Pkce_code_verifier should be present and non-null");
+ assertTrue(requestState.getRequestTime()!=null, "authn_request_time should be present and non-null");
+ assertTrue(requestState.getMaxAge()!=null, "max_age should be present and non-null");
+ assertTrue(requestState.isAuthTimeRequired(), "is_auth_time_required should be present and non-null");
}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestTimeLookupFunctionTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestTimeLookupFunctionTest.java
index d697468..91b862f 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestTimeLookupFunctionTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/AuthenticationRequestTimeLookupFunctionTest.java
@@ -23,7 +23,7 @@ import java.time.Instant;
import org.opensaml.profile.context.ProfileRequestContext;
import org.testng.annotations.Test;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
/**
@@ -38,11 +38,12 @@ public class AuthenticationRequestTimeLookupFunctionTest {
public void testAuthTimeReturned() {
final Instant now = Instant.now();
- final AuthenticationRequestStateData state = new AuthenticationRequestStateData().setAuthnRequestTime(now);
+ final AuthenticationRequestStateData state = (AuthenticationRequestStateData)
+ new AuthenticationRequestStateData().setRequestTime(now);
final AuthenticationRequestTimeLookupFunction function = new AuthenticationRequestTimeLookupFunction();
- function.setAuthenticationRequestLookupStrategy(prc -> new AuthnRequestStateDataContext().setAuthnState(state));
+ function.setStateDataContextLookupStrategy(prc -> new StateDataContext().setStateData(state));
final Instant result = function.apply(new ProfileRequestContext());
assertEquals(result, now);
@@ -56,13 +57,14 @@ public class AuthenticationRequestTimeLookupFunctionTest {
final Instant now = Instant.now();
final Duration skew = Duration.ofSeconds(10);
- final AuthenticationRequestStateData state = new AuthenticationRequestStateData().setAuthnRequestTime(now);
+ final AuthenticationRequestStateData state = (AuthenticationRequestStateData)
+ new AuthenticationRequestStateData().setRequestTime(now);
final AuthenticationRequestTimeLookupFunction function = new AuthenticationRequestTimeLookupFunction();
assert skew != null;
function.setClockSkew(skew);
- function.setAuthenticationRequestLookupStrategy(prc -> new AuthnRequestStateDataContext().setAuthnState(state));
+ function.setStateDataContextLookupStrategy(prc -> new StateDataContext().setStateData(state));
final Instant result = function.apply(new ProfileRequestContext());
assertEquals(result, now.minus(skew));
@@ -77,7 +79,7 @@ public class AuthenticationRequestTimeLookupFunctionTest {
final AuthenticationRequestTimeLookupFunction function = new AuthenticationRequestTimeLookupFunction();
- function.setAuthenticationRequestLookupStrategy(prc -> new AuthnRequestStateDataContext().setAuthnState(state));
+ function.setStateDataContextLookupStrategy(prc -> new StateDataContext().setStateData(state));
final Instant result = function.apply(new ProfileRequestContext());
assertNull(result);
@@ -90,7 +92,7 @@ public class AuthenticationRequestTimeLookupFunctionTest {
public void testNoAuthnContextReturnsNull() {
final AuthenticationRequestTimeLookupFunction function = new AuthenticationRequestTimeLookupFunction();
- function.setAuthenticationRequestLookupStrategy(prc -> null);
+ function.setStateDataContextLookupStrategy(prc -> null);
final Instant result = function.apply(new ProfileRequestContext());
assertNull(result);
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/IssueStateCookieTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/IssueStateCookieTest.java
deleted file mode 100644
index 9f0a1f1..0000000
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/IssueStateCookieTest.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.webflow.execution.Event;
-import org.testng.Assert;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-
-import jakarta.servlet.http.Cookie;
-import net.shibboleth.idp.profile.testing.ActionTestingSupport;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.codec.DecodingException;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.shared.net.CookieManager.SameSiteValue;
-import net.shibboleth.shared.primitive.NonnullSupplier;
-import net.shibboleth.sp.ddf.DDF;
-import net.shibboleth.sp.profile.SPConstants;
-import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
-
-
-
-/**
- * Unit test for {@link IssueStateCookie} action.
- */
- at SuppressWarnings("javadoc")
-public class IssueStateCookieTest extends BaseApplicationActionTest {
-
- @Nonnull @NotEmpty private static final String TEST_STATE = "foo";
- @Nonnull @NotEmpty private static final String TEST_VALUE = "123456789";
-
- private String stateValue;
-
- private DDF input;
- private MockHttpServletRequest request;
- private MockHttpServletResponse response;
-
- private CookieManager cookieManager;
- private IssueStateCookie action;
-
- /**
- * Set up test.
- *
- * @throws ComponentInitializationException
- */
- @SuppressWarnings("null")
- @BeforeMethod
- public void setUp() throws ComponentInitializationException {
- super.beforeMethod();
-
- request = new MockHttpServletRequest();
- response = new MockHttpServletResponse();
-
- cookieManager = new CookieManager();
- cookieManager.setHttpServletRequestSupplier(NonnullSupplier.of(request));
- cookieManager.setHttpServletResponseSupplier(NonnullSupplier.of(response));
- cookieManager.setCookieLimit(10);
- cookieManager.setSameSite(SameSiteValue.None);
- cookieManager.setMaxAge(-1);
- cookieManager.initialize();
-
- action = new IssueStateCookie();
- action.setCookieManager(cookieManager);
- action.setStateValueLookupStrategy(new Function<>() {
- public String apply(final ProfileRequestContext t) {
- return stateValue;
- }
- });
-
- action.setErrorFatal(true);
-
- input = new DDF(null).structure();
- arc.setInput(input);
- }
-
- /**
- * Tear down test.
- */
- @AfterMethod
- public void tearDown() {
- action.destroy();
- cookieManager.destroy();
- }
-
- @DataProvider
- Object[][] correlationData() {
- return new Object[][] {
- new Object[] { null, TEST_VALUE},
- new Object[] { TEST_STATE, TEST_VALUE},
- };
- }
-
- @Test(dataProvider="correlationData")
- public void testAction(final String state, final String id) throws ComponentInitializationException {
- action.initialize();
- evaluateAction(state, id);
- }
-
- @Test
- public void testAction_NullValue() throws ComponentInitializationException {
- action.setErrorFatal(false);
- action.initialize();
- evaluateAction(TEST_STATE, null);
- }
-
- private void evaluateAction(final String state, final String id) {
- stateValue = id;
- if (state != null) {
- input.addmember(SPConstants.STATE).string(state);
- }
-
- final Event event = action.execute(src);
-
- if (state != null) {
- ActionTestingSupport.assertProceedEvent(event);
- } else {
- ActionTestingSupport.assertEvent(event, EventIds.INVALID_MESSAGE);
- return;
- }
-
- if (id == null) {
- Assert.assertEquals(response.getCookies().length, 0);
- return;
- }
-
- final Cookie cookie = response.getCookie(IssueStateCookie.DEFAULT_COOKIE_PREFIX + state);
- assert cookie != null;
- Assert.assertEquals(cookie.getValue(), stateValue);
- Assert.assertEquals(cookie.getMaxAge(), -1);
- Assert.assertEquals(cookie.getAttribute("SameSite"), SameSiteValue.None.getValue());
- }
-
- @Test
- public void testPurge() throws ComponentInitializationException, DecodingException, InterruptedException {
- action.initialize();
- final List<Cookie> cookies = new ArrayList<>(12);
- for (int i = 0; i < 12; ++i) {
- cookies.add(new Cookie(IssueStateCookie.DEFAULT_COOKIE_PREFIX + i, "foo" + i));
- Thread.sleep(250);
- }
- request.setCookies(cookies.toArray(new Cookie[12]));
-
- evaluateAction(TEST_STATE, TEST_VALUE);
-
- Assert.assertEquals(response.getCookies().length, 3);
- }
-
-}
\ No newline at end of file
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/MaxAgeFromAuthenticationRequestLookupFunctionTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/MaxAgeFromAuthenticationRequestLookupFunctionTest.java
index 077147c..7148fe1 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/MaxAgeFromAuthenticationRequestLookupFunctionTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/MaxAgeFromAuthenticationRequestLookupFunctionTest.java
@@ -23,7 +23,7 @@ import org.testng.annotations.Test;
import net.shibboleth.oidc.profile.config.impl.DefaultOIDCAuthorizationConfiguration;
import net.shibboleth.profile.context.RelyingPartyContext;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
/**
@@ -44,7 +44,7 @@ public class MaxAgeFromAuthenticationRequestLookupFunctionTest {
final MaxAgeFromAuthenticationRequestLookupFunction function =
new MaxAgeFromAuthenticationRequestLookupFunction(Duration.ofSeconds(120));
- function.setAuthenticationRequestLookupStrategy(prc -> new AuthnRequestStateDataContext().setAuthnState(state));
+ function.setStateDataContextLookupStrategy(prc -> new StateDataContext().setStateData(state));
final Duration result = function.apply(new ProfileRequestContext());
assertEquals(result, expected);
@@ -62,7 +62,7 @@ public class MaxAgeFromAuthenticationRequestLookupFunctionTest {
final MaxAgeFromAuthenticationRequestLookupFunction func = new MaxAgeFromAuthenticationRequestLookupFunction(
Duration.ofSeconds(999));
- func.setAuthenticationRequestLookupStrategy(prc -> null); // no authn request → fallback path
+ func.setStateDataContextLookupStrategy(prc -> null); // no authn request -- fallback path
final ProfileRequestContext prc = new ProfileRequestContext();
@@ -84,7 +84,7 @@ public class MaxAgeFromAuthenticationRequestLookupFunctionTest {
new MaxAgeFromAuthenticationRequestLookupFunction(
defaultValue);
- function.setAuthenticationRequestLookupStrategy(prc -> null);
+ function.setStateDataContextLookupStrategy(prc -> null);
final Duration result = function.apply(new ProfileRequestContext());
assertEquals(result, defaultValue);
@@ -104,7 +104,7 @@ public class MaxAgeFromAuthenticationRequestLookupFunctionTest {
new MaxAgeFromAuthenticationRequestLookupFunction(
defaultValue);
- function.setAuthenticationRequestLookupStrategy(prc -> new AuthnRequestStateDataContext().setAuthnState(state));
+ function.setStateDataContextLookupStrategy(prc -> new StateDataContext().setStateData(state));
final ProfileRequestContext prc = new ProfileRequestContext();
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRClaimsFromStateLookupStrategyTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRClaimsFromStateLookupStrategyTest.java
index 7eeaace..aabcfb9 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRClaimsFromStateLookupStrategyTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRClaimsFromStateLookupStrategyTest.java
@@ -25,7 +25,7 @@ import org.mockito.Mockito;
import org.testng.annotations.Test;
import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
+import net.shibboleth.sp.context.StateDataContext;
import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
@@ -46,10 +46,10 @@ public class RequestedACRClaimsFromStateLookupStrategyTest extends BaseApplicati
public void testSuccess_AcrReturnedNullRemoved() {
// Arrange
- final AuthnRequestStateDataContext ctx = Mockito.mock(AuthnRequestStateDataContext.class);
+ final StateDataContext ctx = Mockito.mock(StateDataContext.class);
final AuthenticationRequestStateData state = Mockito.mock(AuthenticationRequestStateData.class);
- Mockito.when(ctx.getAuthnState()).thenReturn(state);
+ Mockito.when(ctx.getStateData()).thenReturn(state);
Mockito.when(state.getAcrs()).thenReturn(Arrays.asList("loa1", null));
final var strategy = new RequestedACRClaimsFromStateLookupStrategy(prc -> ctx);
@@ -64,10 +64,10 @@ public class RequestedACRClaimsFromStateLookupStrategyTest extends BaseApplicati
public void testSuccess_EmptyAcrList() {
// Arrange
- final AuthnRequestStateDataContext ctx = Mockito.mock(AuthnRequestStateDataContext.class);
+ final StateDataContext ctx = Mockito.mock(StateDataContext.class);
final AuthenticationRequestStateData state = Mockito.mock(AuthenticationRequestStateData.class);
- Mockito.when(ctx.getAuthnState()).thenReturn(state);
+ Mockito.when(ctx.getStateData()).thenReturn(state);
Mockito.when(state.getAcrs()).thenReturn(CollectionSupport.emptyList());
final var strategy = new RequestedACRClaimsFromStateLookupStrategy(prc -> ctx);
@@ -82,9 +82,9 @@ public class RequestedACRClaimsFromStateLookupStrategyTest extends BaseApplicati
public void testSuccess_NullRequestState() {
// Arrange
- final AuthnRequestStateDataContext ctx = Mockito.mock(AuthnRequestStateDataContext.class);
+ final StateDataContext ctx = Mockito.mock(StateDataContext.class);
- Mockito.when(ctx.getAuthnState()).thenReturn(null);
+ Mockito.when(ctx.getStateData()).thenReturn(null);
final var strategy = new RequestedACRClaimsFromStateLookupStrategy(prc -> ctx);
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRValidationActivationConditionTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRValidationActivationConditionTest.java
index fab748f..a2ba5aa 100644
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRValidationActivationConditionTest.java
+++ b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/RequestedACRValidationActivationConditionTest.java
@@ -51,14 +51,14 @@ public class RequestedACRValidationActivationConditionTest extends BaseApplicati
@Test
public void testShouldValidateAcr_fromProfileConfig() {
- rpConfig.setShouldValidateAcrValue(true);
+ rpConfig.setValidateAcrValue(true);
final boolean activate = condition.test(prc, null);
assertTrue(activate);
}
@Test
public void testShouldNotValidateAcr_fromProfileConfig() {
- rpConfig.setShouldValidateAcrValue(false);
+ rpConfig.setValidateAcrValue(false);
final boolean activate = condition.test(prc, null);
assertFalse(activate);
}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ResolveStateCookieTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ResolveStateCookieTest.java
deleted file mode 100644
index dd8f95f..0000000
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/ResolveStateCookieTest.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * 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 static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
-
-import org.mockito.Mockito;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.EventContext;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.net.CookieManager;
-import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
-
-/**
- * Tests for {@link ResolveStateCookie}.
- */
-public class ResolveStateCookieTest extends BaseApplicationActionTest {
-
- private ResolveStateCookie action;
- private CookieManager cookieManager;
-
-
- @SuppressWarnings("null")
- @BeforeMethod
- public void BeforeMethod() throws ComponentInitializationException {
- super.beforeMethod();
- action = new ResolveStateCookie();
- cookieManager = Mockito.mock(CookieManager.class);
- action.setCookieManager(cookieManager);
- }
-
- @Test
- public void testMissingStateTokenNonFatal() throws ComponentInitializationException {
-
- action.setStateTokenLookupStrategy(prcFunct -> null);
- action.setErrorFatal(false);
- action.initialize();
-
- @SuppressWarnings("null")
- final boolean result = action.doPreExecute(prc);
- assertFalse(result);
- }
-
- /* Missing state token is fatal.*/
- @Test
- public void testMissingStateTokenFatal() throws ComponentInitializationException {
-
- action.setStateTokenLookupStrategy(prcFunct -> null);
- action.setErrorFatal(true);
- action.initialize();
-
- @SuppressWarnings("null")
- final boolean result = action.doPreExecute(prc);
- assertFalse(result);
- final var ec = prc.getSubcontext(EventContext.class);
- assertNotNull(ec);
- assert ec != null;
- assertEquals(ec.getEvent(), EventIds.INVALID_MESSAGE);
- }
-
- /* The cookie is missing, but this is not fatal.*/
- @SuppressWarnings("null")
- @Test
- public void testMissingCookieNonFatal() throws ComponentInitializationException {
-
- action.setStateTokenLookupStrategy(prcFunct -> "state");
- action.setErrorFatal(false);
- action.initialize();
-
- Mockito.when(cookieManager.getCookieValue(Mockito.anyString(), Mockito.any())).thenReturn(null);
-
- assertTrue(action.doPreExecute(prc));
- action.doExecute(prc);
-
- final var ec = prc.getSubcontext(EventContext.class);
- assertNull(ec);
- }
-
- /* The cookie is missing, and this is fatal.*/
- @SuppressWarnings("null")
- @Test
- public void testMissingCookieFatal() throws ComponentInitializationException {
-
- action.setStateTokenLookupStrategy(prcFunct -> "state");
- action.setErrorFatal(true);
- action.initialize();
-
- Mockito.when(cookieManager.getCookieValue(Mockito.anyString(), Mockito.any())).thenReturn(null);
-
- assertTrue(action.doPreExecute(prc));
- action.doExecute(prc);
-
- final var ec = prc.getSubcontext(EventContext.class);
- assertNotNull(ec);
- assert ec != null;
- assertEquals(ec.getEvent(), EventIds.INVALID_MESSAGE);
- }
-
- /* Test cookie value found and set.*/
- @SuppressWarnings("null")
- @Test
- public void testConsumerSuccess() throws ComponentInitializationException {
-
- action.setStateTokenLookupStrategy(prcFunct -> "state");
- action.setCookieValueConsumerStrategy((prcFunct, value) -> Boolean.TRUE);
- action.setErrorFatal(true);
- action.initialize();
-
- Mockito.when(cookieManager.getCookieValue(Mockito.anyString(), Mockito.any())).thenReturn("cookie-value");
-
- assertTrue(action.doPreExecute(prc));
- action.doExecute(prc);
-
- final var ec = prc.getSubcontext(EventContext.class);
- assertNull(ec);
-
- }
-
- /* Could not set cookie back to context, but not fatal.*/
- @SuppressWarnings("null")
- @Test
- public void testConsumerFailureNonFatal() throws ComponentInitializationException {
-
- action.setStateTokenLookupStrategy(prcFunct -> "state");
- action.setCookieValueConsumerStrategy((prcFunct, value) -> Boolean.FALSE);
- action.setErrorFatal(false);
- action.initialize();
-
- Mockito.when(cookieManager.getCookieValue(Mockito.anyString(), Mockito.any())).thenReturn("cookie-value");
-
- assertTrue(action.doPreExecute(prc));
- action.doExecute(prc);
-
- final var ec = prc.getSubcontext(EventContext.class);
- assertNull(ec);
- }
-
- /* Could not set cookie back to context, and fatal.*/
- @SuppressWarnings("null")
- @Test
- public void testConsumerFailureFatal() throws ComponentInitializationException {
-
- action.setStateTokenLookupStrategy(prcFunct -> "state");
- action.setCookieValueConsumerStrategy((prcFunct, value) -> Boolean.FALSE);
- action.setErrorFatal(true);
- action.initialize();
-
- Mockito.when(cookieManager.getCookieValue(Mockito.anyString(), Mockito.any())).thenReturn("cookie-value");
-
- assertTrue(action.doPreExecute(prc));
- action.doExecute(prc);
-
- final var ec = prc.getSubcontext(EventContext.class);
- assertNotNull(ec);
- assert ec != null;
- assertEquals(ec.getEvent(), EventIds.INVALID_MESSAGE);
- }
-
-}
diff --git a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/SetAuthenticationRequestToPeerContextConsumerTest.java b/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/SetAuthenticationRequestToPeerContextConsumerTest.java
deleted file mode 100644
index b262a84..0000000
--- a/sp-oidc-impl/src/test/java/net/shibboleth/sp/oidc/profile/impl/SetAuthenticationRequestToPeerContextConsumerTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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 static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-
-import java.nio.charset.StandardCharsets;
-
-import org.mockito.Mockito;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import net.shibboleth.oidc.profile.messaging.context.OIDCPeerEntityContext;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.security.DataSealer;
-import net.shibboleth.sp.oidc.context.AuthnRequestStateDataContext;
-import net.shibboleth.sp.oidc.profile.AuthenticationRequestStateData;
-import net.shibboleth.sp.profile.impl.BaseApplicationActionTest;
-
-/**
- * Tests for {@link SetAuthenticationRequestToPeerContextConsumer}.
- */
-public class SetAuthenticationRequestToPeerContextConsumerTest extends BaseApplicationActionTest{
-
- /** The Consumer, which is a function, to test.*/
- private SetAuthenticationRequestToPeerContextConsumer function;
-
- /** The authentication state data used to construct and suitable JSON input string for deserialising.*/
- private AuthenticationRequestStateData authnState;
-
- /** The serialized form of the authnState.*/
- private String authnStateSerialized;
-
- /** The serialized form of the authnState as UTF-8 encoded bytes.*/
- private byte[] authnStateSerializedAsBytes;
-
- @Override
- @BeforeMethod
- public void beforeMethod() throws ComponentInitializationException {
- super.beforeMethod();
-
- function = new SetAuthenticationRequestToPeerContextConsumer();
- function.setId("TestComponent");
-
- authnState = new AuthenticationRequestStateData();
- authnState.setAuthenticationAuthority("https://op.example.org");
- authnState.setPkceCodeVerifier("PKCECodeVerifier");
- authnState.setNonce("nonce");
-
- final ObjectMapper mapper = new ObjectMapper();
- function.setObjectMapper(mapper);
- try {
- authnStateSerialized = mapper.writeValueAsString(authnState);
- authnStateSerializedAsBytes = authnStateSerialized.getBytes(StandardCharsets.UTF_8);
- } catch (final JsonProcessingException e) {
- throw new ComponentInitializationException(e);
- }
-
- }
-
- @Test
- public void testSuccess() throws Exception {
- function.initialize();
-
- function.apply(prc, authnStateSerializedAsBytes);
-
- final var inboundMsgCtx = prc.getInboundMessageContext();
- assertNotNull(inboundMsgCtx);
- assert inboundMsgCtx != null;
- final var peerCtx = inboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);
- assertNotNull(peerCtx);
- assert peerCtx != null;
- assertEquals(peerCtx.getIdentifier(), "https://op.example.org");
- final var authnStateCtx = inboundMsgCtx.getSubcontext(AuthnRequestStateDataContext.class);
- assertNotNull(authnStateCtx);
- assert authnStateCtx != null;
- assertNotNull(authnStateCtx.getAuthnState());
- final var authnStateFromCtx = authnStateCtx.getAuthnState();
- assertNotNull(authnStateFromCtx);
- assert authnStateFromCtx != null;
- assertEquals(authnStateFromCtx.getNonce(), "nonce");
- assertEquals(authnStateFromCtx.getPkceCodeVerifier(), "PKCECodeVerifier");
- }
-
- @SuppressWarnings("null")
- @Test
- public void testSuccess_SealedWithDataSealer() throws Exception {
- final DataSealer sealer = Mockito.mock(DataSealer.class);
- Mockito.when(sealer.unwrap(Mockito.anyString())).thenReturn(authnStateSerialized);
- function.setDataSealer(sealer);
- function.initialize();
-
- function.apply(prc, authnStateSerializedAsBytes);
-
- final var inboundMsgCtx = prc.getInboundMessageContext();
- assertNotNull(inboundMsgCtx);
- assert inboundMsgCtx != null;
- final var peerCtx = inboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);
- assertNotNull(peerCtx);
- assert peerCtx != null;
- assertEquals(peerCtx.getIdentifier(), "https://op.example.org");
- final var authnStateCtx = inboundMsgCtx.getSubcontext(AuthnRequestStateDataContext.class);
- assertNotNull(authnStateCtx);
- assert authnStateCtx != null;
- assertNotNull(authnStateCtx.getAuthnState());
- final var authnStateFromCtx = authnStateCtx.getAuthnState();
- assertNotNull(authnStateFromCtx);
- assert authnStateFromCtx != null;
- assertEquals(authnStateFromCtx.getNonce(), "nonce");
- assertEquals(authnStateFromCtx.getPkceCodeVerifier(), "PKCECodeVerifier");
- }
-
- @Test
- public void testFail_BadJSONAuthnState() throws Exception {
- function.initialize();
-
- function.apply(prc, "bad".getBytes(StandardCharsets.UTF_8));
-
- final var inboundMsgCtx = prc.getInboundMessageContext();
- assertNotNull(inboundMsgCtx);
- assert inboundMsgCtx != null;
- final var peerCtx = inboundMsgCtx.getSubcontext(OIDCPeerEntityContext.class);
- assertNull(peerCtx);
- final var authnStateCtx = inboundMsgCtx.getSubcontext(AuthnRequestStateDataContext.class);
- assertNull(authnStateCtx);
-
- }
-
-}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list