From cdd25260d71faa1e54610a0bb4fada7809762fe3 Mon Sep 17 00:00:00 2001
From: "Studer Simon, I253 extern" <simon.studer@post.ch>
Date: Mon, 17 Aug 2020 14:34:35 +0200
Subject: [PATCH 1/2] Add config directive for SameSite cookie flag.

---
 README             |  5 +++++
 src/mod_auth_cas.c | 30 +++++++++++++++++++++++-------
 src/mod_auth_cas.h |  6 ++++--
 3 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/README b/README
index c5c2e07..80385e9 100644
--- a/README
+++ b/README
@@ -275,6 +275,11 @@ Directive:	CASCookieDomain
 Default:	NULL
 Description:	Specify the value for the 'Domain=' parameter in the Set-Cookie header.
 
+Directive:	CASCookieSameSite
+Default:	NULL
+Description:	Specify the value for the 'SameSite=' parameter in the Set-Cookie header.
+		Allowed values are 'None', 'Lax', and 'Strict'.
+
 Directive:	CASCookieHttpOnly
 Default:	On
 Description:	Set the optional 'HttpOnly' flag for cookies issues by mod_auth_cas.
diff --git a/src/mod_auth_cas.c b/src/mod_auth_cas.c
index 327ca48..e34da59 100644
--- a/src/mod_auth_cas.c
+++ b/src/mod_auth_cas.c
@@ -115,6 +115,7 @@ void *cas_create_server_config(apr_pool_t *pool, server_rec *svr)
 	c->CASIdleTimeout = CAS_DEFAULT_COOKIE_IDLE_TIMEOUT;
 	c->CASCacheCleanInterval = CAS_DEFAULT_CACHE_CLEAN_INTERVAL;
 	c->CASCookieDomain = CAS_DEFAULT_COOKIE_DOMAIN;
+	c->CASCookieSameSite = CAS_DEFAULT_COOKIE_SAMESITE;
 	c->CASGatewayCookieDomain = CAS_DEFAULT_GATEWAY_COOKIE_DOMAIN;
 	c->CASCookieHttpOnly = CAS_DEFAULT_COOKIE_HTTPONLY;
 	c->CASSSOEnabled = CAS_DEFAULT_SSO_ENABLED;
@@ -152,6 +153,7 @@ void *cas_merge_server_config(apr_pool_t *pool, void *BASE, void *ADD)
 	c->CASIdleTimeout = (add->CASIdleTimeout != CAS_DEFAULT_COOKIE_IDLE_TIMEOUT ? add->CASIdleTimeout : base->CASIdleTimeout);
 	c->CASCacheCleanInterval = (add->CASCacheCleanInterval != CAS_DEFAULT_CACHE_CLEAN_INTERVAL ? add->CASCacheCleanInterval : base->CASCacheCleanInterval);
 	c->CASCookieDomain = (add->CASCookieDomain != CAS_DEFAULT_COOKIE_DOMAIN ? add->CASCookieDomain : base->CASCookieDomain);
+	c->CASCookieSameSite = (add->CASCookieSameSite != CAS_DEFAULT_COOKIE_SAMESITE ? add->CASCookieSameSite : base->CASCookieSameSite);
 	c->CASGatewayCookieDomain = (add->CASGatewayCookieDomain != CAS_DEFAULT_GATEWAY_COOKIE_DOMAIN ? add->CASGatewayCookieDomain : base->CASGatewayCookieDomain);
 	c->CASCookieHttpOnly = (add->CASCookieHttpOnly != CAS_DEFAULT_COOKIE_HTTPONLY ? add->CASCookieHttpOnly : base->CASCookieHttpOnly);
 	c->CASSSOEnabled = (add->CASSSOEnabled != CAS_DEFAULT_SSO_ENABLED ? add->CASSSOEnabled : base->CASSSOEnabled);
@@ -370,6 +372,15 @@ const char *cfg_readCASParameter(cmd_parms *cmd, void *cfg, const char *value)
 			}
 			c->CASCookieDomain = apr_pstrdup(cmd->pool, value);
 		break;
+		case cmd_cookie_samesite:
+			if (!((apr_strnatcasecmp(value, "None") == 0)
+					|| (apr_strnatcasecmp(value, "Lax") == 0)
+					|| (apr_strnatcasecmp(value, "Strict") == 0)))
+			{
+				return(apr_psprintf(cmd->pool, "MOD_AUTH_CAS: Invalid argument to CASCookieSameSite - must be 'None', 'Lax', or 'Strict'"));
+			}
+			c->CASCookieSameSite = apr_pstrdup(cmd->pool, value);
+		break;
 		case cmd_gateway_cookie_domain:
 			limit = strlen(value);
 			for(sz = 0; sz < limit; sz++) {
@@ -781,9 +792,9 @@ char *getCASCookie(request_rec *r, char *cookieName)
 	return rv;
 }
 
-void setCASCookie(request_rec *r, char *cookieName, char *cookieValue, apr_byte_t secure, apr_time_t expireTime, char *cookieDomain)
+void setCASCookie(request_rec *r, char *cookieName, char *cookieValue, apr_byte_t secure, apr_time_t expireTime, char *cookieDomain, char *cookieSameSite)
 {
-	char *headerString, *currentCookies, *pathPrefix = "", *expireTimeString = NULL, *errString, *domainString = "";
+	char *headerString, *currentCookies, *pathPrefix = "", *expireTimeString = NULL, *errString, *domainString = "", *sameSiteString = "";
 	cas_cfg *c = ap_get_module_config(r->server->module_config, &auth_cas_module);
 	apr_status_t retVal;
 
@@ -805,13 +816,17 @@ void setCASCookie(request_rec *r, char *cookieName, char *cookieValue, apr_byte_
 	if(NULL != cookieDomain) {
 		domainString = apr_psprintf(r->pool, ";Domain=%s", cookieDomain);
 	}
-	headerString = apr_psprintf(r->pool, "%s=%s%s;Path=%s%s%s%s%s",
+	if(NULL != cookieSameSite) {
+		sameSiteString = apr_psprintf(r->pool, ";SameSite=%s", cookieSameSite);
+	}
+	headerString = apr_psprintf(r->pool, "%s=%s%s;Path=%s%s%s%s%s%s",
 		cookieName,
 		cookieValue,
 		(secure ? ";Secure" : ""),
 		pathPrefix,
 		urlEncode(r, getCASScope(r), " "),
 		(cookieDomain != NULL ? domainString : ""),
+		(cookieSameSite != NULL ? sameSiteString : ""),
 		(c->CASCookieHttpOnly != FALSE ? "; HttpOnly" : ""),
 		(NULL == expireTimeString) ? "" : apr_psprintf(r->pool, "; expires=%s", expireTimeString));
 
@@ -2183,7 +2198,7 @@ int cas_authenticate(request_rec *r)
 		if(cookieString == NULL) { /* they have not made a gateway trip yet */
 			if(c->CASDebug)
 				ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Gateway initial access (%s)", r->parsed_uri.path);
-			setCASCookie(r, d->CASGatewayCookie, "TRUE", ssl, CAS_SESSION_EXPIRE_SESSION_SCOPE_TIMEOUT, c->CASGatewayCookieDomain);
+			setCASCookie(r, d->CASGatewayCookie, "TRUE", ssl, CAS_SESSION_EXPIRE_SESSION_SCOPE_TIMEOUT, c->CASGatewayCookieDomain, c->CASCookieSameSite);
 			redirectRequest(r, c);
 			return HTTP_MOVED_TEMPORARILY;
 		} else {
@@ -2208,10 +2223,10 @@ int cas_authenticate(request_rec *r)
 			if(cookieString == NULL)
 				return HTTP_INTERNAL_SERVER_ERROR;
 
-			setCASCookie(r, (ssl ? d->CASSecureCookie : d->CASCookie), cookieString, ssl, CAS_SESSION_EXPIRE_SESSION_SCOPE_TIMEOUT, c->CASCookieDomain);
+			setCASCookie(r, (ssl ? d->CASSecureCookie : d->CASCookie), cookieString, ssl, CAS_SESSION_EXPIRE_SESSION_SCOPE_TIMEOUT, c->CASCookieDomain, c->CASCookieSameSite);
 			/* remove gateway cookie so they can reauthenticate later */
 			if (getCASCookie(r, d->CASGatewayCookie)) {
-				setCASCookie(r, d->CASGatewayCookie, "TRUE", ssl, CAS_SESSION_EXPIRE_COOKIE_NOW, c->CASGatewayCookieDomain);
+				setCASCookie(r, d->CASGatewayCookie, "TRUE", ssl, CAS_SESSION_EXPIRE_COOKIE_NOW, c->CASGatewayCookieDomain, c->CASCookieSameSite);
 			}
 			r->user = remoteUser;
 			if(d->CASAuthNHeader != NULL)
@@ -2288,7 +2303,7 @@ int cas_authenticate(request_rec *r)
 		} else {
 			/* maybe the cookie expired, have the user get a new service ticket */
 			redirectRequest(r, c);
-			setCASCookie(r, (ssl ? d->CASSecureCookie : d->CASCookie), "", ssl, CAS_SESSION_EXPIRE_COOKIE_NOW, c->CASCookieDomain);
+			setCASCookie(r, (ssl ? d->CASSecureCookie : d->CASCookie), "", ssl, CAS_SESSION_EXPIRE_COOKIE_NOW, c->CASCookieDomain, c->CASCookieSameSite);
 			return HTTP_MOVED_TEMPORARILY;
 		}
 	}
@@ -2895,6 +2910,7 @@ const command_rec cas_cmds [] = {
 	AP_INIT_TAKE1("CASCookiePath", cfg_readCASParameter, (void *) cmd_cookie_path, RSRC_CONF, "Path to store the CAS session cookies in (must end in trailing /)"),
 	AP_INIT_TAKE1("CASCookieEntropy", cfg_readCASParameter, (void *) cmd_cookie_entropy, RSRC_CONF, "Number of random bytes to use when generating a session cookie (larger values may result in slow cookie generation)"),
 	AP_INIT_TAKE1("CASCookieDomain", cfg_readCASParameter, (void *) cmd_cookie_domain, RSRC_CONF, "Specify domain header for mod_auth_cas cookie"),
+	AP_INIT_TAKE1("CASCookieSameSite", cfg_readCASParameter, (void *) cmd_cookie_samesite, RSRC_CONF, "Specify SameSite flag header for mod_auth_cas cookie"),
 	AP_INIT_TAKE1("CASGatewayCookieDomain", cfg_readCASParameter, (void *) cmd_gateway_cookie_domain, RSRC_CONF, "Specify domain header for mod_auth_cas gateway cookie"),
 	AP_INIT_TAKE1("CASCookieHttpOnly", cfg_readCASParameter, (void *) cmd_cookie_httponly, RSRC_CONF, "Enable 'HttpOnly' flag for mod_auth_cas cookie (may break RFC compliance)"),
 	AP_INIT_TAKE1("CASCookie", ap_set_string_slot, (void *) APR_OFFSETOF(cas_dir_cfg, CASCookie), ACCESS_CONF|OR_AUTHCFG, "Define the cookie name for HTTP sessions"),
diff --git a/src/mod_auth_cas.h b/src/mod_auth_cas.h
index 7650aa3..703c8a3 100644
--- a/src/mod_auth_cas.h
+++ b/src/mod_auth_cas.h
@@ -90,6 +90,7 @@
 #define CAS_DEFAULT_ROOT_PROXIED_AS_URL NULL
 #define CAS_DEFAULT_COOKIE_ENTROPY 32
 #define CAS_DEFAULT_COOKIE_DOMAIN NULL
+#define CAS_DEFAULT_COOKIE_SAMESITE NULL
 #define CAS_DEFAULT_COOKIE_HTTPONLY 1
 #define CAS_DEFAULT_COOKIE_TIMEOUT 7200 /* 2 hours */
 #define CAS_DEFAULT_COOKIE_IDLE_TIMEOUT 3600 /* 1 hour */
@@ -134,6 +135,7 @@ typedef struct cas_cfg {
 	char *CASCertificatePath;
 	char *CASCookiePath;
 	char *CASCookieDomain;
+	char *CASCookieSameSite;
 	char *CASGatewayCookieDomain;
 	char *CASAttributeDelimiter;
 	char *CASAttributePrefix;
@@ -175,7 +177,7 @@ typedef struct cas_curl_buffer {
 typedef enum {
 	cmd_version, cmd_debug, cmd_validate_depth, cmd_ca_path, cmd_cookie_path,
 	cmd_loginurl, cmd_validateurl, cmd_proxyurl, cmd_cookie_entropy, cmd_session_timeout,
-	cmd_idle_timeout, cmd_cache_interval, cmd_cookie_domain, cmd_cookie_httponly,
+	cmd_idle_timeout, cmd_cache_interval, cmd_cookie_domain, cmd_cookie_samesite, cmd_cookie_httponly,
 	cmd_sso, cmd_validate_saml, cmd_attribute_delimiter, cmd_attribute_prefix,
 	cmd_root_proxied_as, cmd_authoritative, cmd_preserve_ticket, cmd_gateway_cookie_domain
 } valid_cmds;
@@ -208,7 +210,7 @@ char *getCASPath(request_rec *r);
 void CASSAMLLogout(request_rec *r, char *body);
 apr_status_t cas_in_filter(ap_filter_t *f, apr_bucket_brigade *bb, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes);
 void deleteCASCacheFile(request_rec *r, char *cookieName);
-void setCASCookie(request_rec *r, char *cookieName, char *cookieValue, apr_byte_t secure, apr_time_t expireTime, char *cookieDomain);
+void setCASCookie(request_rec *r, char *cookieName, char *cookieValue, apr_byte_t secure, apr_time_t expireTime, char *cookieDomain, char *cookieSameSite);
 char *escapeString(const request_rec *r, const char *str);
 char *urlEncode(const request_rec *r, const char *str, const char *charsToEncode);
 char *getCASGateway(request_rec *r);
-- 
2.39.5

