[java-plugin-shibd] 02/02: Implement an admin logout framework and a revocation cache.
Codeberg
noreply at shibboleth.net
Wed Oct 21 19:56:06 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.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd/commit/f8d44bcdb2b579e75b2a180d7a7b34079e2acb95
commit f8d44bcdb2b579e75b2a180d7a7b34079e2acb95
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Wed Oct 21 14:05:36 2026 -0400
Implement an admin logout framework and a revocation cache.
---
.../flows/sp/logout-admin/logout-admin-beans.xml | 18 ++
.../flows/sp/logout-admin/logout-admin-flow.xml | 45 ++++
.../shibboleth/sp/flows/LogoutAdminFlowTest.java | 114 +++++++++
.../main/java/net/shibboleth/sp/Application.java | 9 +
.../shibboleth/sp/BasicProtocolSupportService.java | 20 ++
.../net/shibboleth/sp/ProtocolSupportService.java | 10 +
.../net/shibboleth/sp/impl/BasicApplication.java | 59 +++++
.../sp/impl/ProtocolSupportServiceManager.java | 4 +-
.../shibboleth/sp/profile/impl/RevokeSession.java | 255 +++++++++++++++++++++
9 files changed, 532 insertions(+), 2 deletions(-)
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-admin/logout-admin-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-admin/logout-admin-beans.xml
new file mode 100644
index 0000000..086dff0
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-admin/logout-admin-beans.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+ default-init-method="initialize" default-destroy-method="destroy">
+
+ <bean id="shibboleth.sp.profileId" class="java.lang.String" c:_0="http://shibboleth.net/ns/profiles/sp/logout-admin" />
+ <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.logout-admin:LogoutAdmin}" />
+
+ <bean id="shibboleth.metrics.ProfileCounter" class="java.lang.String" c:_0="net.shibboleth.sp.profiles.logout-admin" />
+
+ <bean id="RevokeSession" class="net.shibboleth.sp.profile.impl.RevokeSession" scope="prototype"
+ p:storageService="#{getObject('%{sp.revocation.StorageService:}'.trim())}" />
+
+</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-admin/logout-admin-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-admin/logout-admin-flow.xml
new file mode 100644
index 0000000..9b30abb
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/logout-admin/logout-admin-flow.xml
@@ -0,0 +1,45 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+ parent="sp/application">
+
+ <action-state id="InitializeProfileRequestContext">
+ <evaluate expression="InitializeProfileRequestContext" />
+ <evaluate expression="'proceed'" />
+
+ <!-- Branch to parent flow for authentication. -->
+ <transition on="proceed" to="AuthenticationSetup" />
+ </action-state>
+
+ <!-- Resume flow operation after set up by parent. -->
+ <action-state id="DoApplicationOperation">
+ <on-entry>
+ <evaluate expression="new net.shibboleth.shared.collection.ReflectionSafeIterator(opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.sp.context.AgentRequestContext)).getApplication().getLogoutAdmins(opensamlProfileRequestContext).iterator())" result="flowScope.LogoutAdminIterator" />
+ </on-entry>
+ <evaluate expression="'proceed'" />
+
+ <!-- Branch to child flow for actual work. -->
+ <transition on="proceed" to="CheckIterator" />
+ </action-state>
+
+ <decision-state id="CheckIterator">
+ <if test="LogoutAdminIterator.hasNext()" then="CallAdminFlow" else="RevokeSession" />
+ </decision-state>
+
+ <subflow-state id="CallAdminFlow" subflow="sp/logout/admin/#{LogoutAdminIterator.next()}">
+ <input name="calledAsSubflow" value="true" />
+ <transition on="ReselectFlow" to="CheckIterator" />
+ <transition to="RevokeSession" />
+ </subflow-state>
+
+ <action-state id="RevokeSession">
+ <evaluate expression="RevokeSession" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="EncodeAgentResponse" />
+ </action-state>
+
+ <!-- The file really exists in this directory, but it's referenced from extending flow-directories -->
+ <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/sp/logout-admin/logout-admin-beans.xml" />
+
+</flow>
diff --git a/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutAdminFlowTest.java b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutAdminFlowTest.java
new file mode 100644
index 0000000..a52304f
--- /dev/null
+++ b/sp-conf-impl/src/test/java/net/shibboleth/sp/flows/LogoutAdminFlowTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.flows;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.EventIds;
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.profile.SPConstants;
+import net.shibboleth.sp.profile.impl.RevokeSession;
+
+/**
+ * Unit test for the SP logout-admin flow (basics only).
+ */
+public class LogoutAdminFlowTest extends AbstractSPFlowTest {
+
+ /** Flow ID. */
+ @Nonnull public static final String FLOW_ID = "sp/logout-admin";
+
+ protected LogoutAdminFlowTest() {
+ super(FLOW_ID);
+ }
+
+ /**
+ * Test flow with no application specified.
+ * @throws IOException
+ */
+ @Test
+ public void testNoApplication() throws IOException {
+ setDefaultAuth();
+ setRequest("POST", new DDF(null));
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
+ }
+
+ /**
+ * Test flow with invalid application ID.
+ * @throws IOException
+ */
+ @Test
+ public void testBadApplication() throws IOException {
+ setDefaultAuth();
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(SPConstants.APPLICATION).string("foo");
+ setRequest("POST", input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
+ }
+
+ /**
+ * Test flow with no initiator subflows installed.
+ * @throws IOException
+ */
+ @Test
+ public void testNoInput() throws IOException {
+ setDefaultAuth();
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(SPConstants.APPLICATION).string(APPLICATION_ID);
+ setRequest("POST", input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ assertOutputMessageEvent(result, EventIds.INVALID_MESSAGE);
+ }
+
+ /**
+ * Test flow with revocation.
+ * @throws IOException
+ */
+ @Test
+ public void testSuccess() throws IOException {
+ setDefaultAuth();
+
+ final DDF input = new DDF(null).structure();
+ input.addmember(RevokeSession.SESSION_ID).string("ABCDEF");
+ input.addmember(SPConstants.APPLICATION).string(APPLICATION_ID);
+ setRequest("POST", input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+
+ final DDF output = assertOutputMessageSuccess(result);
+ assert output != null;
+ Assert.assertEquals(output.getmember(RevokeSession.STATUS).integer(), 1);
+ }
+
+}
\ No newline at end of file
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/Application.java b/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
index 7fd0233..84a12bb 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/Application.java
@@ -114,6 +114,15 @@ public interface Application extends RelyingPartyConfiguration, RelyingPartyConf
* @return ordered list of unprefixed flow IDs
*/
@Nonnull List<String> getLogoutConsumers(@Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Gets an ordered list of administrative logout flows to attempt for this application.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return ordered list of unprefixed flow IDs
+ */
+ @Nonnull List<String> getLogoutAdmins(@Nullable final ProfileRequestContext profileRequestContext);
/**
* Gets the {@link StateManager} to use for thie application.
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/BasicProtocolSupportService.java b/sp-server-api/src/main/java/net/shibboleth/sp/BasicProtocolSupportService.java
index ce13d31..cf88960 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/BasicProtocolSupportService.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/BasicProtocolSupportService.java
@@ -50,6 +50,9 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
/** Logout Consumer flow IDs. */
@Nonnull private List<String> logoutConsumers;
+
+ /** Logout Admin flow IDs. */
+ @Nonnull private List<String> logoutAdmins;
/** Non-metadata-driven verified configurations. */
@Nonnull private Collection<ProfileConfiguration> profileConfigurations;
@@ -69,6 +72,7 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
tokenConsumers = CollectionSupport.emptyList();
logoutInitiators = CollectionSupport.emptyList();
logoutConsumers = CollectionSupport.emptyList();
+ logoutAdmins = CollectionSupport.emptyList();
profileConfigurations = CollectionSupport.emptySet();
metadataDrivenProfileConfigurations = CollectionSupport.emptySet();
unverifiedProfileConfigurations = CollectionSupport.emptySet();
@@ -154,6 +158,22 @@ public class BasicProtocolSupportService extends AbstractIdentifiableInitializab
logoutConsumers = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
}
+
+ /** {@inheritDoc} */
+ @Nonnull @Unmodifiable @NotLive public List<String> getLogoutAdmins() {
+ return logoutAdmins;
+ }
+
+ /**
+ * Sets the ordered list of admin logout flow IDs to install.
+ *
+ * @param flows ordered list of unprefixed admin logout flow IDs
+ */
+ public void setLogoutAdmins(@Nullable final List<String> flows) {
+ checkSetterPreconditions();
+
+ logoutAdmins = CollectionSupport.copyToList(StringSupport.normalizeStringCollection(flows));
+ }
/** {@inheritDoc} */
@Nonnull @Unmodifiable @NotLive public Collection<ProfileConfiguration> getDefaultProfileConfigurations(
diff --git a/sp-server-api/src/main/java/net/shibboleth/sp/ProtocolSupportService.java b/sp-server-api/src/main/java/net/shibboleth/sp/ProtocolSupportService.java
index 7c428f6..1413a29 100644
--- a/sp-server-api/src/main/java/net/shibboleth/sp/ProtocolSupportService.java
+++ b/sp-server-api/src/main/java/net/shibboleth/sp/ProtocolSupportService.java
@@ -76,6 +76,16 @@ public interface ProtocolSupportService extends IdentifiedComponent, Initializab
*/
@Nonnull @Unmodifiable @NotLive List<String> getLogoutConsumers();
+ /**
+ * Gets a default-ordered list of admin logout flows supported by this protocol plugin.
+ *
+ * <p>The default order should reflect what "most" deployers would expect to use when adding
+ * support for this plugin to their system.</p>
+ *
+ * @return ordered list of unprefixed flow IDs
+ */
+ @Nonnull @Unmodifiable @NotLive List<String> getLogoutAdmins();
+
/**
* Gets the collection of default {@link ProfileConfiguration} objects supplied by this protocol plugin.
*
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
index 32097bf..b139b53 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/BasicApplication.java
@@ -113,6 +113,9 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
/** Logout consumer list lookup strategy. */
@NonnullAfterInit private Function<ProfileRequestContext,List<String>> logoutConsumerLookupStrategy;
+
+ /** Logout admin list lookup strategy. */
+ @NonnullAfterInit private Function<ProfileRequestContext,List<String>> logoutAdminLookupStrategy;
/** State management. */
@Nullable private StateManager stateManager;
@@ -463,6 +466,50 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
logoutConsumerLookupStrategy = Constraint.isNotNull(strategy, "Logout consumers lookup strategy cannot be null");
}
+
+ /** {@inheritDoc} */
+ @Nonnull public List<String> getLogoutAdmins(@Nullable final ProfileRequestContext profileRequestContext) {
+ checkComponentActive();
+
+ final List<String> ret = logoutAdminLookupStrategy.apply(profileRequestContext);
+ if (ret != null && !ret.isEmpty()) {
+ return ret;
+ }
+
+ if (parent != null && allowInheritance) {
+ return parent.getLogoutAdmins(profileRequestContext);
+ }
+
+ return CollectionSupport.emptyList();
+ }
+
+ /**
+ * Sets the ordered collection of unprefixed admin logout flow IDs to use for this application.
+ *
+ * @param admins ordered collection of unprefixed admin logout flow IDs
+ */
+ public void setLogoutAdmins(@Nullable final Collection<String> admins) {
+ checkSetterPreconditions();
+
+ if (admins != null) {
+ logoutAdminLookupStrategy = FunctionSupport.constant(
+ CollectionSupport.copyToList(StringSupport.normalizeStringCollection(admins)));
+ } else {
+ logoutAdminLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ }
+ }
+
+ /**
+ * Sets the lookup strategy for the ordered collection of unprefixed admin logout flow IDs
+ * to use for this application.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setLogoutAdminsLookupStrategy(@Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ checkSetterPreconditions();
+
+ logoutAdminLookupStrategy = Constraint.isNotNull(strategy, "Logout admins lookup strategy cannot be null");
+ }
/** {@inheritDoc} */
@Nonnull public StateManager getStateManager() {
@@ -723,6 +770,9 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
if (logoutConsumerLookupStrategy == null) {
logoutConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
}
+ if (logoutAdminLookupStrategy == null) {
+ logoutAdminLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ }
return;
}
@@ -773,6 +823,15 @@ public class BasicApplication extends BasicRelyingPartyConfiguration implements
}
logoutConsumerLookupStrategy = FunctionSupport.constant(CollectionSupport.copyToList(consumers));
}
+
+ if (logoutAdminLookupStrategy == null) {
+ final ArrayList<String> admins = new ArrayList<>();
+ for (final ProtocolSupportService service : protocolServiceManager.getComponents()) {
+ assert service != null;
+ admins.addAll(StringSupport.normalizeStringCollection(service.getLogoutAdmins()));
+ }
+ logoutAdminLookupStrategy = FunctionSupport.constant(CollectionSupport.copyToList(admins));
+ }
}
/** {@inheritDoc} */
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/ProtocolSupportServiceManager.java b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/ProtocolSupportServiceManager.java
index f3a7dbc..b31f7e7 100644
--- a/sp-server-impl/src/main/java/net/shibboleth/sp/impl/ProtocolSupportServiceManager.java
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/impl/ProtocolSupportServiceManager.java
@@ -53,9 +53,9 @@ public class ProtocolSupportServiceManager extends IdentifiedComponentManager<Pr
for (final ProtocolSupportService service : getComponents()) {
assert service != null;
log.info("Installed SP Hub support for protocol {}: initiators ({}), consumers ({}), "
- + "logout-initiators ({}), logout-consumers ({})", service.getId(),
+ + "logout-initiators ({}), logout-consumers ({}), logout-admins ({})", service.getId(),
service.getSessionInitiators(), service.getTokenConsumers(),
- service.getLogoutInitiators(), service.getLogoutConsumers());
+ service.getLogoutInitiators(), service.getLogoutConsumers(), service.getLogoutAdmins());
}
}
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/RevokeSession.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/RevokeSession.java
new file mode 100644
index 0000000..809878d
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/RevokeSession.java
@@ -0,0 +1,255 @@
+/*
+ * 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.profile.impl;
+
+import java.io.IOException;
+import java.time.Duration;
+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.EventContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.CurrentOrPreviousEventLookup;
+import org.opensaml.storage.StorageService;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.context.AgentRequestContext;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.profile.AbstractApplicationAction;
+import net.shibboleth.sp.profile.SPConstants;
+
+/**
+ * A profile action to optionally record the revocation of a session in a revocation cache
+ * and signal back an administrative logout result to the Agent.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MESSAGE}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ *
+ * @pre <pre>AgentRequestContext.getIntput().isstruct()</pre>
+ * @pre <pre>AgentRequestContext.getOutput().isstruct()</pre>
+ * @post optionally a revocation record is stored
+ */
+public class RevokeSession extends AbstractApplicationAction {
+
+ /** Storage context for revocation. */
+ @Nonnull @NotEmpty public static final String STORAGE_CONTEXT = "net.shibboleth.sp.sessions.revocation";
+
+ /** Input member for session ID. */
+ @Nonnull @NotEmpty public static final String SESSION_ID = "session.id";
+
+ /** Input member for revocation lifetime. */
+ @Nonnull @NotEmpty public static final String LIFETIME = "lifetime";
+
+ /** Output "status" member. */
+ @Nonnull @NotEmpty public static final String STATUS = "status";
+
+ /** Success "status" value. */
+ public static final int STATUS_SUCCESS = 1;
+
+ /** Partial "status" value. */
+ public static final int STATUS_PARTIAL = 0;
+
+ /** Failed "status" value. */
+ public static final int STATUS_FAILED = -1;
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(RevokeSession.class);
+
+ /** Strategy function for access to {@link EventContext} to check. */
+ @Nonnull private Function<ProfileRequestContext,EventContext> eventContextLookupStrategy;
+
+ /** Storage for revocation list. */
+ @Nullable private StorageService storageService;
+
+ /** Upper bound on agent-supplied storage lifetime. */
+ @Nonnull private Duration maxStorageLifetime;
+
+ /** Input message. */
+ @NonnullBeforeExec private DDF input;
+
+ /** Session ID from message. */
+ @NonnullBeforeExec private String key;
+
+ /** Cached output object. */
+ @NonnullBeforeExec private DDF output;
+
+ /** Constructor. */
+ public RevokeSession() {
+ eventContextLookupStrategy = new CurrentOrPreviousEventLookup();
+ maxStorageLifetime = Duration.ofDays(1);
+ }
+
+ /**
+ * Set lookup strategy for {@link EventContext} to base resulting status on.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEventContextLookupStrategy(@Nonnull final Function<ProfileRequestContext,EventContext> strategy) {
+ checkSetterPreconditions();
+
+ eventContextLookupStrategy = Constraint.isNotNull(strategy, "EventContext lookup strategy cannot be null");
+ }
+
+ /**
+ * Set storage service for revocation list.
+ *
+ * @param service revocation storage
+ */
+ public void setStorageService(@Nullable final StorageService service) {
+ checkSetterPreconditions();
+
+ storageService = service;
+ }
+
+ /**
+ * Sets the maximum storage life for revocation records that an Agent may request.
+ *
+ * <p>Defaults to 1 day.</p>
+ *
+ * @param timeout maximum storage lifetime
+ */
+ public void setMaxStorageTimeout(@Nonnull @Positive final Duration timeout) {
+ Constraint.isFalse(timeout == null || timeout.isZero() || timeout.isNegative(),
+ "Max storage lifetime must be positive");
+ maxStorageLifetime = timeout;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ final AgentRequestContext agentContext = ensureAgentRequestContext();
+
+ input = ensureAgentRequestContext().getInput();
+ if (input == null || !input.isstruct()) {
+ log.warn("{} Invalid or missing input message", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return false;
+ }
+
+ key = input.getmember(SESSION_ID).string();
+ if (key == null || key.isEmpty()) {
+ log.warn("{} Invalid or missing {} parameter", getLogPrefix(), SESSION_ID);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return false;
+ }
+
+ output = agentContext.getOutput();
+ if (output == null) {
+ log.error("{} Output message structure was missing", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ // If revocation fails, we just signal failure using the status field.
+ if (!doRevocation(profileRequestContext)) {
+ output.addmember(STATUS).integer(STATUS_FAILED);
+ return;
+ }
+
+ // Otherwise signal status based on active event if any.
+ final EventContext eventCtx = eventContextLookupStrategy.apply(profileRequestContext);
+ final Object event = eventCtx != null ? eventCtx.getEvent() : null;
+ if (event != null) {
+ if (EventIds.PROCEED_EVENT_ID.equals(event.toString()) ||
+ AuthnEventIds.NO_POTENTIAL_FLOW.equals(event.toString())) {
+ output.addmember(STATUS).integer(STATUS_SUCCESS);
+ } else {
+ output.addmember(STATUS).integer(STATUS_PARTIAL);
+ }
+ output.addmember(SPConstants.EVENT).string(event.toString());
+ } else {
+ output.addmember(STATUS).integer(STATUS_SUCCESS);
+ }
+ }
+
+ /**
+ * Store a revocation record if configured to do so.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return true iff revocation succeeded or was not configured
+ */
+ private boolean doRevocation(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final StorageService localStorage = storageService;
+ if (localStorage == null) {
+ return true;
+ }
+
+ try {
+ if (localStorage.create(getStorageContext(), key, "1", getStorageLifetime())) {
+ log.debug("{} Added revocation record for session ({})", getLogPrefix(), key);
+ return true;
+ }
+ log.error("{} Detected existing revocation record for session ({})", getLogPrefix(), key);
+ } catch (final IOException e) {
+ log.error("{} Error creating revocation record for session ({})", getLogPrefix(), key, e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Get Agent-supplied storage lifetime in seconds, bounded by configured maximum.
+ *
+ * @return effective lifetime in seconds
+ */
+ @Nonnull private Long getStorageLifetime() {
+
+ Long st = input.getmember(LIFETIME).longinteger();
+ if (st != null) {
+ if (st <= maxStorageLifetime.toSeconds()) {
+ return st;
+ }
+
+ log.warn("{} Agent-supplied {} value exceeded configured maximum ({})", getLogPrefix(), LIFETIME,
+ maxStorageLifetime);
+ }
+
+ return maxStorageLifetime.toSeconds();
+ }
+
+ /**
+ * Compute agent-specific storage context.
+ *
+ * @return storage context with agent qualifier
+ */
+ @Nonnull private String getStorageContext() {
+ return STORAGE_CONTEXT + '!' + ensureAgent().getId() + '!' + ensureApplication().getApplicationId();
+ }
+
+}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list