[java-shib-shared] branch main updated: JSH-59 - Conditionally add setAttribute support to CookieManager

Scott Cantor cantor.2 at osu.edu
Tue Apr 8 16:46:26 UTC 2025


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository java-shib-shared.

View the commit online:
http://git.shibboleth.net/view/?p=java-shib-shared.git;a=commit;h=22e70ab34c3cc6ea25322df402b9617d236217d4

The following commit(s) were added to refs/heads/main by this push:
     new 22e70ab3 JSH-59 - Conditionally add setAttribute support to CookieManager
22e70ab3 is described below

commit 22e70ab34c3cc6ea25322df402b9617d236217d4
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Apr 8 12:46:23 2025 -0400

    JSH-59 - Conditionally add setAttribute support to CookieManager
    
    https://shibboleth.atlassian.net/browse/JSSH-59
---
 .../net/shibboleth/shared/net/CookieManager.java   | 127 +++++++++++++++++++++
 .../shared/primitive/ReflectionSupport.java        |  54 +++++++++
 2 files changed, 181 insertions(+)

diff --git a/shib-networking/src/main/java/net/shibboleth/shared/net/CookieManager.java b/shib-networking/src/main/java/net/shibboleth/shared/net/CookieManager.java
index 6ae7e674..9eec0764 100644
--- a/shib-networking/src/main/java/net/shibboleth/shared/net/CookieManager.java
+++ b/shib-networking/src/main/java/net/shibboleth/shared/net/CookieManager.java
@@ -15,6 +15,8 @@
 package net.shibboleth.shared.net;
 
 import java.time.Duration;
+import java.util.Map;
+import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -24,10 +26,16 @@ import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.AbstractInitializableComponent;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.PredicateSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.NonnullSupplier;
+import net.shibboleth.shared.primitive.ReflectionSupport;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
@@ -36,8 +44,14 @@ import net.shibboleth.shared.primitive.StringSupport;
  * <p>This bean centralizes settings related to cookie creation and access,
  * and is parameterized by name so that multiple cookies may be managed with
  * common properties.</p>
+ * 
+ * <p>Some of the features depend on Servlet API >= 6.0 and will be conditionally
+ * bypassed otherwise.</p>
  */
 public class CookieManager extends AbstractInitializableComponent {
+    
+    /** Whether we're on a platform with {@link Cookie#setAttribute(String, String)}. */
+    private final boolean hasSetAttribute;
 
     /** Path of cookie. */
     @Nullable private String cookiePath;
@@ -60,11 +74,23 @@ public class CookieManager extends AbstractInitializableComponent {
     /** Maximum age in seconds, or -1 for session. */
     private int maxAge;
     
+    /** SameSite attribute. */
+    @Nullable private String sameSite;
+    
+    /** Condition controlling application of SameSite. */
+    @Nonnull private Predicate<HttpServletRequest> sameSiteCondition;
+    
+    /** Additional cookie attributes. */
+    @Nonnull private Map<String,String> cookieAttributes;
+    
     /** Constructor. */
     public CookieManager() {
         httpOnly = true;
         secure = true;
         maxAge = -1;
+        sameSiteCondition = PredicateSupport.alwaysTrue();
+        cookieAttributes = CollectionSupport.emptyMap();
+        hasSetAttribute = ReflectionSupport.getMethod(Cookie.class, "setAttribute", String.class, String.class) != null;
     }
 
     /**
@@ -219,6 +245,86 @@ public class CookieManager extends AbstractInitializableComponent {
         }
     }
 
+    /**
+     * Gets the SameSite attribute.
+     * 
+     * @return the SameSite attribute
+     * 
+     * @since 9.2.0
+     */
+    @Nullable @NotEmpty public String getSameSite() {
+        return sameSite;
+    }
+
+    /**
+     * Sets the SameSite attribute.
+     * 
+     * <p>Defaults to non-existent (which is not the same as "None").</p>
+     * 
+     * @param value value to set
+     * 
+     * @since 9.2.0
+     */
+    public void setSameSite(@Nullable final String value) {
+        checkSetterPreconditions();
+        
+        sameSite = StringSupport.trimOrNull(value);
+    }
+    
+    /**
+     * Gets the condition controlling application of SameSite.
+     * 
+     * @return condition
+     * 
+     * @since 9.2.0
+     */
+    @Nonnull public Predicate<HttpServletRequest> getSameSiteCondition() {
+        return sameSiteCondition;
+    }
+    
+    /**
+     * Sets the condition controlling application of SameSite.
+     * 
+     * <p>Defaults to true.</p>
+     * 
+     * @param condition condition to set
+     * 
+     * @since 9.2.0
+     */
+    public void setSameSiteCondition(@Nonnull final Predicate<HttpServletRequest> condition) {
+        checkSetterPreconditions();
+        
+        sameSiteCondition = Constraint.isNotNull(condition, "SameSite condition cannot be null");
+    }
+    
+    /**
+     * Gets additional attributes to apply to the cookie.
+     * 
+     * @return additional attributes
+     * 
+     * @since 9.2.0
+     */
+    @Nonnull @NotLive @Unmodifiable public Map<String,String> getCookieAttributes() {
+        return cookieAttributes;
+    }
+    
+    /**
+     * Sets additional attributes to apply to the cookie.
+     * 
+     * @param attributes attributes to apply
+     * 
+     * @since 9.2.0
+     */
+    public void setCookieAttributes(@Nullable final Map<String,String> attributes) {
+        checkSetterPreconditions();
+        
+        if (attributes != null) {
+            cookieAttributes = CollectionSupport.copyToMap(attributes);
+        } else {
+            cookieAttributes = CollectionSupport.emptyMap();
+        }
+    }
+
     /** {@inheritDoc} */
     protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
@@ -226,6 +332,11 @@ public class CookieManager extends AbstractInitializableComponent {
         if (httpRequestSupplier == null || httpResponseSupplier == null) {
             throw new ComponentInitializationException("Servlet request and response must be set");
         }
+        
+        if (!hasSetAttribute && (sameSite != null || !cookieAttributes.isEmpty())) {
+            LoggerFactory.getLogger(CookieManager.class).info(
+                    "Running on Servlet API < 6.0.0, some features are degraded");
+        }
     }
 
     /**
@@ -259,6 +370,14 @@ public class CookieManager extends AbstractInitializableComponent {
         cookie.setSecure(secure);
         cookie.setHttpOnly(httpOnly);
         cookie.setMaxAge(overrideMaxAge);
+        if (hasSetAttribute) {
+            if (sameSiteCondition.test(getHttpServletRequest())) {
+                cookie.setAttribute("SameSite", sameSite);
+            }
+            cookieAttributes.forEach((n,v) -> {
+                cookie.setAttribute(n, v);
+                });
+        }
         
         getHttpServletResponse().addCookie(cookie);
     }
@@ -279,6 +398,14 @@ public class CookieManager extends AbstractInitializableComponent {
         cookie.setSecure(secure);
         cookie.setHttpOnly(httpOnly);
         cookie.setMaxAge(0);
+        if (hasSetAttribute) {
+            if (sameSiteCondition.test(getHttpServletRequest())) {
+                cookie.setAttribute("SameSite", sameSite);
+            }
+            cookieAttributes.forEach((n,v) -> {
+                cookie.setAttribute(n, v);
+                });
+        }
         
         getHttpServletResponse().addCookie(cookie);
     }
diff --git a/shib-support/src/main/java/net/shibboleth/shared/primitive/ReflectionSupport.java b/shib-support/src/main/java/net/shibboleth/shared/primitive/ReflectionSupport.java
new file mode 100644
index 00000000..0275ebc0
--- /dev/null
+++ b/shib-support/src/main/java/net/shibboleth/shared/primitive/ReflectionSupport.java
@@ -0,0 +1,54 @@
+/*
+ * 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.shared.primitive;
+
+import java.lang.reflect.Method;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Utility functions related to reflection.
+ * 
+ * @since 9.2.0
+ */
+public final class ReflectionSupport {
+
+    /**
+     * Gets a method via reflection with a given name and parameter types on a class.
+     * 
+     * @param claz class of object
+     * @param methodName name of method
+     * @param parameterTypes types of parameters, if any
+     * 
+     * @return the matching {@link Method} or null
+     */
+    @Nullable public static Method getMethod(@Nonnull final Class<?> claz, @Nonnull final String methodName,
+            @Nullable final Class<?>... parameterTypes) {
+        try {
+            // Attempt to get the method
+            return claz.getMethod(methodName, parameterTypes);
+        } catch (final NoSuchMethodException|SecurityException e) {
+        }
+        
+        return null;
+    }
+
+    
+    /** Private constructor. */
+    private ReflectionSupport() {
+        
+    }
+}
\ 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