[java-plugin-shibd] branch main updated: Revocation check operation.
Codeberg
noreply at shibboleth.net
Fri Oct 23 21:08:04 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/bbb049a8b28a38f976b98b35003112fb9188a072
The following commit(s) were added to refs/heads/main by this push:
new bbb049a Revocation check operation.
bbb049a is described below
commit bbb049a8b28a38f976b98b35003112fb9188a072
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Fri Oct 23 17:07:41 2026 -0400
Revocation check operation.
---
.../idp/flows/sp/revocation/revocation-beans.xml | 18 ++++
.../idp/flows/sp/revocation/revocation-flow.xml | 26 +++++
.../sp/profile/impl/AbstractRevocationAction.java | 117 +++++++++++++++++++++
.../sp/profile/impl/CheckRevocation.java | 117 +++++++++++++++++++++
.../shibboleth/sp/profile/impl/RevokeSession.java | 63 ++---------
5 files changed, 286 insertions(+), 55 deletions(-)
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/revocation/revocation-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/revocation/revocation-beans.xml
new file mode 100644
index 0000000..d4515fe
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/revocation/revocation-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/revocation" />
+ <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.revocation:Revocation}" />
+
+ <bean id="shibboleth.metrics.ProfileCounter" class="java.lang.String" c:_0="net.shibboleth.sp.profiles.revocation" />
+
+ <bean id="CheckRevocation" class="net.shibboleth.sp.profile.impl.CheckRevocation" 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/revocation/revocation-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/revocation/revocation-flow.xml
new file mode 100644
index 0000000..40de88c
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/revocation/revocation-flow.xml
@@ -0,0 +1,26 @@
+<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/abstract">
+
+ <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="DoOperation">
+ <evaluate expression="CheckRevocation" />
+ <evaluate expression="'proceed'" />
+
+ <!-- Branch to parent flow to send response. -->
+ <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/revocation/revocation-beans.xml" />
+
+</flow>
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/AbstractRevocationAction.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/AbstractRevocationAction.java
new file mode 100644
index 0000000..5f68605
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/AbstractRevocationAction.java
@@ -0,0 +1,117 @@
+/*
+ * 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 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.opensaml.storage.StorageService;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.profile.AbstractApplicationAction;
+
+/**
+ * Base class for actions involved with session revocation.
+ *
+ * @event {@link EventIds#INVALID_MESSAGE}
+ * @pre <pre>AgentRequestContext.getIntput().isstruct()</pre>
+ */
+public abstract class AbstractRevocationAction 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";
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractRevocationAction.class);
+
+ /** Storage for revocation list. */
+ @Nullable private StorageService storageService;
+
+ /** Session ID from input message. */
+ @NonnullBeforeExec private String key;
+
+ /**
+ * Gets the storage service for revocation list.
+ *
+ * @return storage service, if installed
+ */
+ @Nullable protected StorageService getStorageService() {
+ return storageService;
+ }
+
+ /**
+ * Set storage service for revocation list.
+ *
+ * @param service revocation storage
+ */
+ public void setStorageService(@Nullable final StorageService service) {
+ checkSetterPreconditions();
+
+ storageService = service;
+ }
+
+ /**
+ * Gets the session ID from the input message.
+ *
+ * @return session ID
+ */
+ @NonnullBeforeExec @NotEmpty protected String getSessionID() {
+ return key;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ final DDF 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;
+ }
+
+ return true;
+ }
+
+ /**
+ * Compute agent-specific storage context.
+ *
+ * @return storage context with agent qualifier
+ */
+ @Nonnull protected String getStorageContext() {
+ return STORAGE_CONTEXT + '!' + ensureAgent().getId() + '!' + ensureApplication().getApplicationId();
+ }
+
+}
\ No newline at end of file
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/CheckRevocation.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/CheckRevocation.java
new file mode 100644
index 0000000..c6af9a2
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/CheckRevocation.java
@@ -0,0 +1,117 @@
+/*
+ * 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 javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.storage.StorageRecord;
+import org.opensaml.storage.StorageService;
+import org.slf4j.Logger;
+
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.sp.ddf.DDF;
+
+/**
+ * A profile action to check the revocation of a session in a revocation cache.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ *
+ * @pre <pre>AgentRequestContext.getOutput().isstruct()</pre>
+ */
+public class CheckRevocation extends AbstractRevocationAction {
+
+ /** Output "status" member. */
+ @Nonnull @NotEmpty public static final String RESULT = "result";
+
+ /** Success "status" value. */
+ public static final int REVOKED = 1;
+
+ /** Partial "status" value. */
+ public static final int NOT_REVOKED = 0;
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(CheckRevocation.class);
+
+ /** Cached output object. */
+ @NonnullBeforeExec private DDF output;
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (getStorageService() == null) {
+ throw new ComponentInitializationException("StorageService cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ output = ensureAgentRequestContext().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) {
+ output.addmember(RESULT).integer(checkRevocation() ? REVOKED : NOT_REVOKED);
+ }
+
+ /**
+ * Store a revocation record if configured to do so.
+ *
+ * @return true iff revocation succeeded or was not configured
+ */
+ private boolean checkRevocation() {
+
+ final StorageService localStorage = getStorageService();
+ assert localStorage != null;
+
+ try {
+ final StorageRecord<String> record = localStorage.read(getStorageContext(), getSessionID());
+ if (record == null) {
+ log.debug("No revocation record for session ({})", getLogPrefix(), getSessionID());
+ return false;
+ }
+ log.info("{} Found revocation record for session ({})", getLogPrefix(), getSessionID());
+ return true;
+ } catch (final IOException e) {
+ log.error("{} Error checking for revocation record for session ({}), signaling revocation",
+ getLogPrefix(), getSessionID(), e);
+ return true;
+ }
+ }
+
+}
\ No newline at end of file
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
index 809878d..aabfc18 100644
--- 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
@@ -19,7 +19,6 @@ 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;
@@ -37,7 +36,6 @@ 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;
/**
@@ -45,20 +43,12 @@ import net.shibboleth.sp.profile.SPConstants;
* 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";
+public class RevokeSession extends AbstractRevocationAction {
/** Input member for revocation lifetime. */
@Nonnull @NotEmpty public static final String LIFETIME = "lifetime";
@@ -81,18 +71,12 @@ public class RevokeSession extends AbstractApplicationAction {
/** 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;
@@ -113,17 +97,6 @@ public class RevokeSession extends AbstractApplicationAction {
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.
*
@@ -145,20 +118,9 @@ public class RevokeSession extends AbstractApplicationAction {
}
final AgentRequestContext agentContext = ensureAgentRequestContext();
-
+
+ // We know this exists due to base class.
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) {
@@ -205,19 +167,19 @@ public class RevokeSession extends AbstractApplicationAction {
*/
private boolean doRevocation(@Nonnull final ProfileRequestContext profileRequestContext) {
- final StorageService localStorage = storageService;
+ final StorageService localStorage = getStorageService();
if (localStorage == null) {
return true;
}
try {
- if (localStorage.create(getStorageContext(), key, "1", getStorageLifetime())) {
- log.debug("{} Added revocation record for session ({})", getLogPrefix(), key);
+ if (localStorage.create(getStorageContext(), getSessionID(), "1", getStorageLifetime())) {
+ log.debug("{} Added revocation record for session ({})", getLogPrefix(), getSessionID());
return true;
}
- log.error("{} Detected existing revocation record for session ({})", getLogPrefix(), key);
+ log.error("{} Detected existing revocation record for session ({})", getLogPrefix(), getSessionID());
} catch (final IOException e) {
- log.error("{} Error creating revocation record for session ({})", getLogPrefix(), key, e);
+ log.error("{} Error creating revocation record for session ({})", getLogPrefix(), getSessionID(), e);
}
return false;
@@ -243,13 +205,4 @@ public class RevokeSession extends AbstractApplicationAction {
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