[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