[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