[java-plugin-shibd] branch main updated: WIP flow for session cache mgmt.
Codeberg
noreply at shibboleth.net
Mon Dec 1 20:49:01 UTC 2025
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/2ba3b6a882c81c40e56b31d36088d49803989490
The following commit(s) were added to refs/heads/main by this push:
new 2ba3b6a WIP flow for session cache mgmt.
2ba3b6a is described below
commit 2ba3b6a882c81c40e56b31d36088d49803989490
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Mon Dec 1 15:48:50 2025 -0500
WIP flow for session cache mgmt.
---
.../flows/sp/session-cache/session-cache-beans.xml | 18 +
.../flows/sp/session-cache/session-cache-flow.xml | 26 +
.../shibboleth/idp/module/conf/sp/sp.properties | 8 +-
.../sp/profile/impl/DoSessionCacheOperation.java | 542 +++++++++++++++++++++
4 files changed, 589 insertions(+), 5 deletions(-)
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-beans.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-beans.xml
new file mode 100644
index 0000000..bfc56f5
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-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/parse-request-map" />
+ <bean id="shibboleth.sp.loggingId" class="java.lang.String" c:_0="%{sp.service.logging.storage:SPAgent.Storage}" />
+
+ <bean id="DoSessionCacheOperation"
+ class="net.shibboleth.sp.profile.impl.DoSessionCacheOperation" scope="prototype"
+ p:maxStorageTimeout="%{sp.session.maxStorageTimeout:P1D}"
+ p:storageService-ref="#{'%{sp.session.storageService}'.trim()}" />
+
+</beans>
diff --git a/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-flow.xml b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-flow.xml
new file mode 100644
index 0000000..8d1c7d2
--- /dev/null
+++ b/sp-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/session-cache/session-cache-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="DoSessionCacheOperation" />
+ <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/session-cache/session-cache-beans.xml" />
+
+</flow>
diff --git a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
index 005759d..158b33d 100644
--- a/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
+++ b/sp-conf-impl/src/main/resources/net/shibboleth/idp/module/conf/sp/sp.properties
@@ -10,11 +10,6 @@ sp.issuer = https://sp.example.org
#sp.service.agents.failFast = false
sp.service.agents.checkInterval = PT5M
-# Set to StorageService to use for remoted storage data if in use.
-#sp.storageService =
-# Set to DataSealer to use for remoted data encryption.
-#sp.dataSealer = shibboleth.DataSealer
-
# Default precedence/set of Session Initiator and Token Consumer flows to attempt
#sp.application.sessionInitiators =
#sp.application.tokenConsumers =
@@ -54,6 +49,9 @@ sp.service.agents.checkInterval = PT5M
#sp.discoveryURL =
#sp.discoveryURLFunction =
+# Settings for Hub-mediated session storage
+#sp.session.storageService =
+#sp.session.maxStorageTimeout = P1D
###############################
# Agent Authentication Settings
diff --git a/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DoSessionCacheOperation.java b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DoSessionCacheOperation.java
new file mode 100644
index 0000000..fe84111
--- /dev/null
+++ b/sp-server-impl/src/main/java/net/shibboleth/sp/profile/impl/DoSessionCacheOperation.java
@@ -0,0 +1,542 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+
+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.opensaml.storage.VersionMismatchException;
+import org.slf4j.Logger;
+
+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.annotation.constraint.Positive;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType;
+import net.shibboleth.sp.ddf.DDF;
+import net.shibboleth.sp.profile.AbstractAgentAction;
+
+/**
+ * Action that implements a remote API for SP agents to mamage sessions remotely.
+ *
+ * <p>This is implememnted internally with a {@link StorageService} but implements
+ * the operations somewhat more efficiently with more awareness of the purpose.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#INVALID_MESSAGE}
+ * @event {@link EventIds#IO_ERROR}
+ * @event {@link EventIds#MESSAGE_PROC_ERROR}
+ * @event {@link #INVALID_SESSION}
+ * @event {@link #EXPIRED_SESSION}
+ * @event {@link #VERSION_MISMATCH}
+ */
+public class DoSessionCacheOperation extends AbstractAgentAction {
+
+ /** Storage context for sessions. */
+ @Nonnull @NotEmpty public static final String STORAGE_CONTEXT = "net.shibboleth.sp.sessions";
+
+ /** Custom event for invalid session. */
+ @Nonnull @NotEmpty public static final String INVALID_SESSION = "InvalidSession";
+
+ /** Custom event for expired session. */
+ @Nonnull @NotEmpty public static final String EXPIRED_SESSION = "ExpiredSession";
+
+ /** Custom event for version mismatch on conditional update. */
+ @Nonnull @NotEmpty public static final String VERSION_MISMATCH = "VersionMismatch";
+
+ /** Input member for operation. */
+ @Nonnull @NotEmpty public static final String OP = "op";
+
+ /** Input member for session data. */
+ @Nonnull @NotEmpty public static final String SESSION = "session";
+
+ /** Member for session timeout. */
+ @Nonnull @NotEmpty public static final String STORAGE_TIMEOUT = "storage_timeout";
+
+ /** Input member for session key. */
+ @Nonnull @NotEmpty public static final String KEY = "key";
+
+ /** Member for session lifetime. */
+ @Nonnull @NotEmpty public static final String LIFETIME = "lifetime";
+
+ /** Member for session timeout. */
+ @Nonnull @NotEmpty public static final String TIMEOUT = "timeout";
+
+ /** Member for session creation timestamp. */
+ @Nonnull @NotEmpty public static final String TS = "ts";
+
+ /** Member for session version. */
+ @Nonnull @NotEmpty public static final String VERSION = "ver";
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DoSessionCacheOperation.class);
+
+ /** {@link StorageService} to operate on. */
+ @NonnullAfterInit private StorageService storageService;
+
+ /** Identifier generation. */
+ @NonnullAfterInit private IdentifierGenerationStrategy identifierStrategy;
+
+ /** Upper bound on agent-supplied storage timeout. */
+ @Nonnull private Duration maxStorageTimeout;
+
+ /** Input message. */
+ @NonnullBeforeExec private DDF input;
+
+ /** Constructor. */
+ public DoSessionCacheOperation() {
+ maxStorageTimeout = Duration.ofDays(1);
+ }
+
+ /**
+ * Sets the {@link StorageService} to use.
+ *
+ * @param storage storage service
+ */
+ public void setStorageService(@Nonnull final StorageService storage) {
+ checkSetterPreconditions();
+
+ storageService = Constraint.isNotNull(storage, "StorageService cannot be null");
+ }
+
+ /**
+ * Set {@link IdentifierGenerationStrategy} to use.
+ *
+ * <p>Defaults to a secure random source that produces 16 byte values.</p>
+ *
+ * @param strategy identifier generator strategy
+ */
+ public void setIdentifierGenerationStrategy(@Nonnull final IdentifierGenerationStrategy strategy) {
+ checkSetterPreconditions();
+
+ identifierStrategy = Constraint.isNotNull(strategy, "IdentifierGenerationStrategy cannot be null");
+ }
+
+ /**
+ * Sets the maximum storage life for session 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 timeout must be positive");
+ maxStorageTimeout = timeout;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (storageService == null) {
+ throw new ComponentInitializationException("StorageService cannot be null");
+ }
+
+ if (identifierStrategy == null) {
+ identifierStrategy = IdentifierGenerationStrategy.getInstance(ProviderType.SECURE);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ input = ensureAgentRequestContext().getInput();
+ if (input == null || !input.isstruct()) {
+ log.warn("{} Invalid or missing input message", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ try {
+ final String op = input.getmember(OP).string();
+
+ if ("C".equals(op)) {
+ doCreate(profileRequestContext);
+ } else if ("R".equals(op)) {
+ doRead(profileRequestContext);
+ } else if ("U".equals(op)) {
+ doUpdate(profileRequestContext);
+ } else if ("T".equals(op)) {
+ doTouch(profileRequestContext);
+ } else if ("D".equals(op)) {
+ doDelete(profileRequestContext);
+ } else {
+ log.warn("{} Invalid operation: {}", getLogPrefix(), op);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ }
+ } catch (final IOException e) {
+ log.error("{} I/O error responding to request", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ }
+ }
+
+ /**
+ * Perform create operation.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @throws IOException if an error is raised
+ */
+ private void doCreate(@Nonnull final ProfileRequestContext profileRequestContext) throws IOException {
+
+ // Input:
+ // "storage_timeout" - the storage timeout in seconds
+ // "session" - structure to store
+
+ // Output:
+ // "key" - session key
+
+ DDF data = input.getmember(SESSION);
+ if (!data.isstruct()) {
+ log.warn("{} Missing required '{}' structure member", getLogPrefix(), SESSION);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ Long exp = Instant.now().plusSeconds(getStorageTimeout()).toEpochMilli();
+
+ // TODO: Encode/encrypt/etc.?
+ String value;
+ try (final ByteArrayOutputStream sink = new ByteArrayOutputStream()) {
+ data.serialize(sink);
+ value = sink.toString(StandardCharsets.UTF_8);
+ }
+
+ assert value != null;
+
+ int attempts = 0;
+ do {
+ final String key = identifierStrategy.generateIdentifier(false);
+ if (storageService.create(STORAGE_CONTEXT, ensureAgent().getId() + '!' + key, value, exp)) {
+ log.debug("{} Created session record ({}), expiration ({})", getLogPrefix(), key, Instant.ofEpochMilli(exp));
+ DDF out = new DDF(null);
+ out.addmember(KEY).string(key);
+ ensureAgentRequestContext().setOutput(out);
+ return;
+ } else {
+ log.warn("{} Session record ({}) exists", getLogPrefix(), key);
+ }
+ } while (++attempts < 3);
+
+ log.warn("{} Unable to generate usable session key after 3 attempts", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.MESSAGE_PROC_ERROR);
+ }
+
+ /**
+ * Perform read operation.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @throws IOException if an error is raised
+ */
+ private void doRead(@Nonnull final ProfileRequestContext profileRequestContext) throws IOException {
+
+ // Input:
+ // "key" - session key
+ // "storage_timeout" - the storage timeout in seconds
+ // "lifetime" - session lifetime policy to apply
+ // "timeout" - session timeout policy to apply
+
+ // Output:
+ // "session" - session structure
+
+ final String key = input.getmember(KEY).string();
+ if (key == null) {
+ log.warn("{} Missing required '{}' structure member", getLogPrefix(), KEY);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ final StorageRecord<?> record = storageService.read(STORAGE_CONTEXT, ensureAgent().getId() + '!' + key);
+ if (record == null) {
+ // Send back an empty response.
+ log.debug("{} No session found ({})", getLogPrefix(), key);
+ final DDF output = new DDF();
+ ensureAgentRequestContext().setOutput(output);
+ return;
+ }
+
+ Long lastAccess = record.getExpiration();
+ if (lastAccess == null) {
+ log.error("{} Session record ({}) had no expiration", getLogPrefix(), key);
+ ActionSupport.buildEvent(profileRequestContext, INVALID_SESSION);
+ return;
+ }
+
+ final Instant now = Instant.now();
+
+ final Integer timeout = input.getmember(TIMEOUT).integer();
+ if (timeout != null && timeout > 0) {
+ // Recover last access time by backdating record expiration.
+ lastAccess -= getStorageTimeout();
+ if (Instant.ofEpochMilli(lastAccess).plusSeconds(timeout).isBefore(now)) {
+ log.info("{} Session record ({}) timed out, last use was {}, timeout policy was {}",
+ getLogPrefix(), key, Instant.ofEpochMilli(lastAccess), timeout);
+ storageService.delete(STORAGE_CONTEXT, ensureAgent().getId() + '!' + key);
+ ActionSupport.buildEvent(profileRequestContext, EXPIRED_SESSION);
+ return;
+ }
+ }
+
+ DDF sessionData;
+ try (final ByteArrayInputStream source =
+ new ByteArrayInputStream(record.getValue().getBytes(StandardCharsets.UTF_8))) {
+ sessionData = DDF.deserialize(source);
+ }
+ assert sessionData != null;
+
+ // We have a deserialized session, need to enforce policies if specified.
+
+ final Integer lifetime = input.getmember(LIFETIME).integer();
+ if (lifetime != null && lifetime > 0) {
+ final Long ts = sessionData.getmember(TS).longinteger();
+ if (ts == null) {
+ log.error("{} Session record ({}) missing creation timestamp", getLogPrefix(), key);
+ storageService.delete(STORAGE_CONTEXT, ensureAgent().getId() + '!' + key);
+ ActionSupport.buildEvent(profileRequestContext, INVALID_SESSION);
+ return;
+ }
+
+ if (Instant.ofEpochSecond(ts).plusSeconds(lifetime).isBefore(now)) {
+ log.info("{} Session record ({}) expired, created {}, lifetime policy was {}",
+ getLogPrefix(), key, Instant.ofEpochSecond(ts), lifetime);
+ storageService.delete(STORAGE_CONTEXT, ensureAgent().getId() + '!' + key);
+ ActionSupport.buildEvent(profileRequestContext, EXPIRED_SESSION);
+ return;
+ }
+ }
+
+ final DDF output = new DDF().structure();
+ output.add(sessionData);
+ ensureAgentRequestContext().setOutput(output);
+ }
+
+ /**
+ * Perform update operation.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @throws IOException if an error is raised
+ */
+ private void doUpdate(@Nonnull final ProfileRequestContext profileRequestContext) throws IOException {
+
+ // Input:
+ // "key" - session key
+ // "ver" - existing assumed session version
+ // "storage_timeout" - the storage timeout in seconds
+ // "session" - structure to store
+
+ // Output:
+ // "ver" - new version if update succeeded
+
+ final String key = input.getmember(KEY).string();
+ Long version = input.getmember(VERSION).longinteger();
+ if (key == null || version == null || version <= 0) {
+ log.warn("{} Missing required '{}' or '{}' structure member", getLogPrefix(), KEY, VERSION);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ final DDF data = input.getmember(SESSION);
+ if (!data.isstruct()) {
+ log.warn("{} Missing required '{}' structure member", getLogPrefix(), SESSION);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ final Long st = getStorageTimeout();
+ final Long exp = Instant.now().plusSeconds(st).toEpochMilli();
+
+ // TODO: Encode/encrypt/etc.?
+ String value;
+ try (final ByteArrayOutputStream sink = new ByteArrayOutputStream()) {
+ data.serialize(sink);
+ value = sink.toString(StandardCharsets.UTF_8);
+ }
+
+ assert value != null;
+
+ try {
+ version = storageService.updateWithVersion(version, STORAGE_CONTEXT, ensureAgent().getId() + '!' + key,
+ value, exp);
+ if (version != null) {
+ log.debug("{} Updated session ({}) to version ({})", getLogPrefix(), key, version);
+ final DDF output = new DDF().structure();
+ output.addmember(VERSION).longinteger(version);
+ ensureAgentRequestContext().setOutput(output);
+ } else {
+ // Send back an empty response.
+ log.debug("{} No session found ({})", getLogPrefix(), key);
+ final DDF output = new DDF();
+ ensureAgentRequestContext().setOutput(output);
+ }
+ } catch (final VersionMismatchException e) {
+ log.info("{} Version mismatch for session ({}). requested update with older version ({})", getLogPrefix(),
+ key, version);
+ ActionSupport.buildEvent(profileRequestContext, VERSION_MISMATCH);
+ }
+ }
+
+ /**
+ * Perform touch operation to update expiration (and thus time of last access).
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @throws IOException if an error is raised
+ */
+ private void doTouch(@Nonnull final ProfileRequestContext profileRequestContext) throws IOException {
+
+ // Input:
+ // "key" - session key
+ // "storage_timeout" - the storage timeout in seconds
+ // "timeout" - session timeout policy to apply
+
+ // Output:
+ // "ver" - current version, indicates record was found and updated
+
+ final String key = input.getmember(KEY).string();
+ if (key == null) {
+ log.warn("{} Missing required '{}' structure member", getLogPrefix(), KEY);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ // Read back record to check for timeout.
+
+ final StorageRecord<?> record = storageService.read(STORAGE_CONTEXT, ensureAgent().getId() + '!' + key);
+ if (record == null) {
+ // Send back an empty response.
+ log.debug("{} No session found ({})", getLogPrefix(), key);
+ final DDF output = new DDF();
+ ensureAgentRequestContext().setOutput(output);
+ return;
+ }
+
+ Long lastAccess = record.getExpiration();
+ if (lastAccess == null) {
+ log.error("{} Session record ({}) had no expiration", getLogPrefix(), key);
+ ActionSupport.buildEvent(profileRequestContext, INVALID_SESSION);
+ return;
+ }
+
+ final Long st = getStorageTimeout();
+ final Instant now = Instant.now();
+
+ final Integer timeout = input.getmember(TIMEOUT).integer();
+ if (timeout != null && timeout > 0) {
+ // Recover last access time by backdating record expiration.
+ lastAccess -= st;
+ if (Instant.ofEpochMilli(lastAccess).plusSeconds(timeout).isBefore(now)) {
+ log.info("{} Session record ({}) timed out, last use was {}, timeout policy was {}",
+ getLogPrefix(), key, Instant.ofEpochMilli(lastAccess), timeout);
+ storageService.delete(STORAGE_CONTEXT, ensureAgent().getId() + '!' + key);
+ ActionSupport.buildEvent(profileRequestContext, EXPIRED_SESSION);
+ return;
+ }
+ }
+
+ // Bump the expiration and return the version as a success indicator.
+
+ if (storageService.updateExpiration(STORAGE_CONTEXT, ensureAgent().getId() + '!' + key,
+ now.plusSeconds(st).toEpochMilli())) {
+ final DDF output = new DDF();
+ output.addmember(VERSION).longinteger(record.getVersion());
+ ensureAgentRequestContext().setOutput(output);
+ } else {
+ // Record disappeared, so send back an empty response.
+ log.debug("{} Session record ({}) disappeared before update?", getLogPrefix(), key);
+ final DDF output = new DDF();
+ ensureAgentRequestContext().setOutput(output);
+ }
+ }
+
+ /**
+ * Perform delete operation.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @throws IOException if an error is raised
+ */
+ private void doDelete(@Nonnull final ProfileRequestContext profileRequestContext) throws IOException {
+
+ // Input:
+ // "key" - session key to delete
+
+ // Output: None
+
+ final String key = input.getmember(KEY).string();
+ if (key == null) {
+ log.warn("{} Key required for delete operation", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
+ return;
+ }
+
+ if (storageService.delete(STORAGE_CONTEXT, ensureAgent().getId() + '!' + key)) {
+ log.debug("{} Deleted session record ({})", getLogPrefix(), key);
+ } else {
+ log.debug("{} No session record ({})", getLogPrefix(), key);
+ }
+ }
+
+ /**
+ * Get Agent-supplied storage timeout in seconds, bounded by configured maximum.
+ *
+ * @return effective timeout in seconds
+ */
+ @Nonnull Long getStorageTimeout() {
+
+ Long st = input.getmember(STORAGE_TIMEOUT).longinteger();
+ if (st != null) {
+ if (st <= maxStorageTimeout.toSeconds()) {
+ return st;
+ }
+
+ log.warn("{} Agent-supplied {} value exceeded configured maximum ({})", getLogPrefix(), STORAGE_TIMEOUT,
+ maxStorageTimeout);
+ }
+
+ return maxStorageTimeout.toSeconds();
+ }
+
+}
\ 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