[java-plugin-shibd-oidc] branch main updated: Make some progress to agent driven authn request features
Phil Smart
philip.smart at jisc.ac.uk
Fri Sep 12 16:34:04 UTC 2025
This is an automated email from the git hooks/post-receive script.
philsmart pushed a commit to branch main
in repository java-plugin-shibd-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-plugin-shibd-oidc.git;a=commit;h=5782e7f9f446ab7bb4446af9bcb7819832960e52
The following commit(s) were added to refs/heads/main by this push:
new 5782e7f Make some progress to agent driven authn request features
5782e7f is described below
commit 5782e7f9f446ab7bb4446af9bcb7819832960e52
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Sep 12 17:34:02 2025 +0100
Make some progress to agent driven authn request features
- Just trying this out.
---
.../messaging/AbstractAgentMessageHandler.java | 111 +++++++++++++++++++++
.../AbstractAgentRequestMessageHandler.java | 101 +++++++++++++++++++
.../AbstractApplicationMessageHandler.java | 69 +++++++++++++
.../sp/oidc/profile/OIDCInitiatorConstants.java | 57 +++++++++++
.../sp/oidc/flows/OIDCAuthenticationFlowTest.java | 25 +++++
...CAuthenticationRequestActionMessageHandler.java | 33 +++++-
.../impl/AddForceAuthenticationHandler.java | 16 +++
7 files changed, 407 insertions(+), 5 deletions(-)
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
new file mode 100644
index 0000000..deac1fe
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractAgentMessageHandler.java
@@ -0,0 +1,111 @@
+/*
+ * 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
new file mode 100644
index 0000000..243ff3d
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractAgentRequestMessageHandler.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.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
new file mode 100644
index 0000000..2796923
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/messaging/AbstractApplicationMessageHandler.java
@@ -0,0 +1,69 @@
+/*
+ * 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 agent 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/profile/OIDCInitiatorConstants.java b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java
new file mode 100644
index 0000000..46ade91
--- /dev/null
+++ b/sp-oidc-api/src/main/java/net/shibboleth/sp/oidc/profile/OIDCInitiatorConstants.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.saml.saml2.core.AuthnContextClassRef;
+import org.opensaml.saml.saml2.core.NameIDPolicy;
+import org.opensaml.saml.saml2.metadata.NameIDFormat;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * Constants for OIDC session initiator operations.
+ *
+ * TODO these are not all correct for OIDC
+ */
+public final class OIDCInitiatorConstants {
+
+ /** ForceAuthn input parameter. */
+ @Nonnull @NotEmpty public static final String FORCE_AUTHN = "forceAuthn";
+
+ /** IsPassive input parameter. */
+ @Nonnull @NotEmpty public static final String IS_PASSIVE = "isPassive";
+
+ /** authnContextClassRef input parameter. */
+ @Nonnull @NotEmpty public static final String AUTHN_CONTEXT_CLASS_REF = AuthnContextClassRef.DEFAULT_ELEMENT_LOCAL_NAME;
+
+ /** authnContextComparison input parameter. */
+ @Nonnull @NotEmpty public static final String AUTHN_CONTEXT_COMPARISON = "authnContextComparison";
+
+ /** AttributeConsumingServiceIndex input parameter. */
+ @Nonnull @NotEmpty public static final String ATTRIBUTE_INDEX = "attributeIndex";
+
+ /** NameIDFormat input parameter. */
+ @Nonnull @NotEmpty public static final String NAMEID_FORMAT = NameIDFormat.DEFAULT_ELEMENT_LOCAL_NAME;
+
+ /** SPNameQualifier input parameter. */
+ @Nonnull @NotEmpty public static final String SP_NAME_QUALIFIER = NameIDPolicy.SP_NAME_QUALIFIER_ATTRIB_NAME;
+
+ /** Private constructor. */
+ private OIDCInitiatorConstants() {
+
+ }
+}
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
index cb77560..6b13737 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCAuthenticationFlowTest.java
@@ -40,6 +40,7 @@ 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.profile.OIDCInitiatorConstants;
import net.shibboleth.sp.profile.InitiatorConstants;
import net.shibboleth.sp.profile.SPConstants;
import net.shibboleth.sp.profile.impl.IssueCorrelationCookie;
@@ -95,6 +96,30 @@ public class OIDCAuthenticationFlowTest extends AbstractSPFlowTest {
final AuthorizationRequest req = validateOutputMessage(result);
}
+ /**
+ * Basic flow test with forceAuthn.
+ *
+ * @throws IOException on error
+ * @throws MessageDecodingException
+ */
+ @Test
+ public void testForceAuthnFromAgent() 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);
+ input.addmember(SPConstants.TARGET).unsafe_string(RESOURCE_URL);
+ input.addmember(OIDCInitiatorConstants.FORCE_AUTHN).integer(1);
+ setApplicationRequest("test-oidc-application", input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+
+ final AuthorizationRequest req = validateOutputMessage(result);
+ }
+
/**
* Decode an encoded response and run sanity checks against it.
*
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AbstractOIDCAuthenticationRequestActionMessageHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AbstractOIDCAuthenticationRequestActionMessageHandler.java
index fd97208..1c1fb0f 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AbstractOIDCAuthenticationRequestActionMessageHandler.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AbstractOIDCAuthenticationRequestActionMessageHandler.java
@@ -22,7 +22,6 @@ import javax.annotation.Nullable;
import org.opensaml.messaging.context.MessageContext;
import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.messaging.handler.AbstractMessageHandler;
import org.opensaml.messaging.handler.MessageHandlerException;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
@@ -41,6 +40,8 @@ import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
import net.shibboleth.shared.logic.Constraint;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.oidc.messaging.AbstractApplicationMessageHandler;
/**
* An abstract message handler that runs inside an {@link WebFlowMessageHandlerAdaptor}
@@ -49,7 +50,7 @@ import net.shibboleth.shared.primitive.NonnullSupplier;
* <p>The {@link MessageContext} will either be INBOUND or OUTBOUND depending on the direction defined
* by the calling {@link WebFlowMessageHandlerAdaptor} action.</p>
*/
-public abstract class AbstractOIDCAuthenticationRequestActionMessageHandler extends AbstractMessageHandler {
+public abstract class AbstractOIDCAuthenticationRequestActionMessageHandler extends AbstractApplicationMessageHandler {
/** Lookup function for parent ProfileRequestContext. */
@Nonnull private static final ParentProfileRequestContextLookup<MessageContext> PRC_LOOKUP
@@ -77,11 +78,13 @@ public abstract class AbstractOIDCAuthenticationRequestActionMessageHandler exte
/** Current HTTP request, if available. */
@Nullable private NonnullSupplier<HttpServletRequest> httpServletRequestSupplier;
+ /** Input message from agent. */
+ @NonnullBeforeExec private DDF input;
+
/** Constructor.*/
protected AbstractOIDCAuthenticationRequestActionMessageHandler() {
providerMetadataLookupStrategy = new ChildContextLookup<>(OIDCProviderMetadataContext.class).compose(
- new ChildContextLookup<>(OIDCPeerEntityContext.class));
-
+ new ChildContextLookup<>(OIDCPeerEntityContext.class));
relyingPartyContextLookupStrategy = new ChildContextLookup<>(RelyingPartyContext.class);
}
@@ -219,8 +222,23 @@ public abstract class AbstractOIDCAuthenticationRequestActionMessageHandler exte
return Predicates.compose(predicate::test, PRC_LOOKUP::apply);
}
+
+ /**
+ * Get the input message from the agent.
+ *
+ * @return input message
+ */
+ @NonnullBeforeExec public DDF getInput() {
+ return input;
+ }
+
+
@Override
protected boolean doPreInvoke(@Nonnull final MessageContext messageContext) throws MessageHandlerException {
+
+ if (!super.doPreInvoke(messageContext)) {
+ return false;
+ }
if (!(messageContext.getMessage() instanceof OIDCAuthenticationRequest)) {
throw new MessageHandlerException("Message was not an authentication request");
@@ -247,7 +265,12 @@ public abstract class AbstractOIDCAuthenticationRequestActionMessageHandler exte
throw new MessageHandlerException("Profile configuration could not found");
}
- return super.doPreInvoke(messageContext);
+ input = ensureAgentRequestContext().getInput();
+ if (input == null) {
+ throw new MessageHandlerException("No input message");
+ }
+
+ return true;
}
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AddForceAuthenticationHandler.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AddForceAuthenticationHandler.java
index 6b4f0a6..6e1025c 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AddForceAuthenticationHandler.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/messaging/impl/AddForceAuthenticationHandler.java
@@ -26,6 +26,7 @@ import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.openid.connect.sdk.Prompt;
import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.oidc.profile.OIDCInitiatorConstants;
/**
* An action that sets the 'prompt' parameter to 'login' and max_age to 0 seconds, iff force authn was requested by the
@@ -39,6 +40,21 @@ public class AddForceAuthenticationHandler extends AbstractOIDCAuthenticationReq
@Override protected void doInvoke(@Nonnull final MessageContext messageContext)
throws MessageHandlerException {
+ final Integer forceAuthn = getInput().getmember(OIDCInitiatorConstants.FORCE_AUTHN).integer();
+
+ if (forceAuthn != null) {
+ // TODO disallow feature if set but not allowed
+ try {
+ getAuthenticationRequest().setPrompt(Prompt.parse(Prompt.Type.LOGIN.toString()));
+ getAuthenticationRequest().setMaxAge(Duration.ofSeconds(0));
+ } catch (final ParseException e) {
+ // This should never happen
+ throw new MessageHandlerException("Unable to honour force-authn, "
+ + "setting prompt to force-login as failed", e);
+ }
+ return;
+ }
+ // Else try from Profile config
if (getProfileConfiguration().isForceAuthn(lookupProfileRequestContext(messageContext))) {
log.trace("{} Setting prompt=login and max_age=0 (ForceAuthn) for OIDC AuthnRequest", getLogPrefix());
try {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list