[cpp-sp] branch master updated: CPPOST-112 - Clean up message correlation in relevant policy rules

Scott Cantor cantor.2 at osu.edu
Tue Feb 11 13:00:57 EST 2020


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

scantor pushed a commit to branch master
in repository cpp-sp.

View the commit online:
http://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=b45b3419236223fb368ddee1d83df42ef201e4ce

The following commit(s) were added to refs/heads/master by this push:
       new  b45b341   CPPOST-112 - Clean up message correlation in relevant policy rules
b45b341 is described below

commit b45b3419236223fb368ddee1d83df42ef201e4ce
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Feb 11 12:59:31 2020 -0500

    CPPOST-112 - Clean up message correlation in relevant policy rules
    
    https://issues.shibboleth.net/jira/browse/CPPOST-112
    
    Refactor cookie handling.
    Adjust various bits to enable correlation checking.
    Turn off bearer rule correlation checks by default.
---
 adfs/adfs.cpp                                    |  17 +++-
 plugins/AttributeResolverHandler.cpp             |   4 +-
 schemas/shibboleth-2.0-native-sp-config.xsd      |  96 +++++++++---------
 schemas/shibboleth-3.0-native-sp-config.xsd      |  98 ++++++++++---------
 shibsp/AbstractSPRequest.cpp                     |  48 +++++++++
 shibsp/AbstractSPRequest.h                       |   2 +
 shibsp/Application.cpp                           |  23 +++++
 shibsp/Application.h                             |  13 +++
 shibsp/handler/AbstractHandler.h                 |   2 +-
 shibsp/handler/RemotedHandler.h                  |  26 ++++-
 shibsp/handler/impl/AbstractHandler.cpp          | 118 ++++++++++-------------
 shibsp/handler/impl/AdminLogoutInitiator.cpp     |   4 +-
 shibsp/handler/impl/AssertionConsumerService.cpp |   6 +-
 shibsp/handler/impl/AssertionLookup.cpp          |   4 +-
 shibsp/handler/impl/ExternalAuthHandler.cpp      |   6 +-
 shibsp/handler/impl/LocalLogoutInitiator.cpp     |   4 +-
 shibsp/handler/impl/MetadataGenerator.cpp        |   2 +-
 shibsp/handler/impl/RemotedHandler.cpp           |  77 ++++++++++++++-
 shibsp/handler/impl/SAML2ArtifactResolution.cpp  |   6 +-
 shibsp/handler/impl/SAML2Logout.cpp              |   6 +-
 shibsp/handler/impl/SAML2LogoutInitiator.cpp     |   4 +-
 shibsp/handler/impl/SAML2NameIDMgmt.cpp          |   6 +-
 shibsp/handler/impl/SAML2SessionInitiator.cpp    |   2 +-
 shibsp/handler/impl/Shib1SessionInitiator.cpp    |   2 +-
 shibsp/handler/impl/StatusHandler.cpp            |   4 +-
 shibsp/impl/StorageServiceSessionCache.cpp       | 100 +++++--------------
 shibsp/impl/StorageServiceSessionCache.h         |   4 +-
 shibsp/impl/XMLApplication.cpp                   |   9 ++
 28 files changed, 414 insertions(+), 279 deletions(-)

diff --git a/adfs/adfs.cpp b/adfs/adfs.cpp
index 1933867..6eb4ff7 100644
--- a/adfs/adfs.cpp
+++ b/adfs/adfs.cpp
@@ -99,7 +99,12 @@ namespace {
             return m_ns.get();
         }
 
-        XMLObject* decode(string& relayState, const GenericRequest& genericRequest, SecurityPolicy& policy) const;
+        XMLObject* decode(
+            string& relayState,
+            const GenericRequest& genericRequest,
+            const GenericResponse* genericResponse,
+            SecurityPolicy& policy
+        ) const;
 
     protected:
         void extractMessageDetails(
@@ -473,7 +478,7 @@ void ADFSSessionInitiator::receive(DDF& in, ostream& out)
     DDFJanitor jout(ret);
 
     // Wrap the outgoing object with a Response facade.
-    scoped_ptr<HTTPResponse> http(getResponse(ret));
+    scoped_ptr<HTTPResponse> http(getResponse(*app, ret));
 
     string relayState(in["RelayState"].string() ? in["RelayState"].string() : "");
 
@@ -574,7 +579,9 @@ pair<bool,long> ADFSSessionInitiator::doRequest(
 
 #ifndef SHIBSP_LITE
 
-XMLObject* ADFSDecoder::decode(string& relayState, const GenericRequest& genericRequest, SecurityPolicy& policy) const
+XMLObject* ADFSDecoder::decode(
+    string& relayState, const GenericRequest& genericRequest, const GenericResponse*, SecurityPolicy& policy
+    ) const
 {
 #ifdef _DEBUG
     xmltooling::NDC ndc("decode");
@@ -894,12 +901,12 @@ void ADFSLogoutInitiator::receive(DDF& in, ostream& out)
     }
 
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
 
     // Set up a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     Session* session = nullptr;
     try {
diff --git a/plugins/AttributeResolverHandler.cpp b/plugins/AttributeResolverHandler.cpp
index d12a38f..5b5b65a 100644
--- a/plugins/AttributeResolverHandler.cpp
+++ b/plugins/AttributeResolverHandler.cpp
@@ -235,8 +235,8 @@ void AttributeResolverHandler::receive(DDF& in, ostream& out)
     // Wrap a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
 
     // Since we're remoted, the result should either be a throw, a false/0 return,
     // which we just return as an empty structure, or a response/redirect,
diff --git a/schemas/shibboleth-2.0-native-sp-config.xsd b/schemas/shibboleth-2.0-native-sp-config.xsd
index 82c8565..50ae8db 100644
--- a/schemas/shibboleth-2.0-native-sp-config.xsd
+++ b/schemas/shibboleth-2.0-native-sp-config.xsd
@@ -33,8 +33,14 @@
     <list itemType="conf:string"/>
   </simpleType>
 
+  <simpleType name="anyURI">
+    <restriction base="anyURI">
+      <minLength value="1"/>
+    </restriction>
+  </simpleType>
+
   <simpleType name="listOfURIs">
-    <list itemType="anyURI"/>
+    <list itemType="conf:anyURI"/>
   </simpleType>
 
   <simpleType name="bindingBoolean">
@@ -76,7 +82,7 @@
           <sequence>
             <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
           </sequence>
-          <attribute name="path" type="anyURI" use="required"/>
+          <attribute name="path" type="conf:anyURI" use="required"/>
           <attribute name="fatal" type="boolean"/>
           <anyAttribute namespace="##any" processContents="lax"/>
         </complexType>
@@ -146,7 +152,7 @@
       <element name="Extensions" type="conf:ExtensionsType" minOccurs="0"/>
       <any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
     </sequence>
-    <attribute name="logger" type="anyURI"/>
+    <attribute name="logger" type="conf:anyURI"/>
     <attribute name="tranLogFormat" type="conf:string"/>
     <attribute name="tranLogFiller" type="conf:string"/>
     <attribute name="catchAll" type="boolean"/>
@@ -186,7 +192,7 @@
       </element>
       <any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
     </sequence>
-    <attribute name="logger" type="anyURI"/>
+    <attribute name="logger" type="conf:anyURI"/>
     <attribute name="unsetHeaderValue" type="conf:string"/>
     <attribute name="checkSpoofing" type="boolean"/>
     <attribute name="spoofKey" type="conf:string"/>
@@ -243,28 +249,28 @@
     <attribute name="authType" type="conf:string"/>
     <attribute name="requireSession" type="boolean"/>
     <attribute name="requireSessionWith" type="conf:string"/>
-    <attribute name="requireLogoutWith" type="anyURI"/>
+    <attribute name="requireLogoutWith" type="conf:anyURI"/>
     <attribute name="exportAssertion" type="boolean"/>
     <attribute name="exportStdVars" type="boolean"/>
     <attribute name="exportCookie" type="boolean"/>
     <attribute name="exportDuplicateValues" type="boolean"/>
     <attribute name="redirectToSSL" type="unsignedInt"/>
-    <attribute name="entityID" type="anyURI"/>
-    <attribute name="discoveryURL" type="anyURI"/>
+    <attribute name="entityID" type="conf:anyURI"/>
+    <attribute name="discoveryURL" type="conf:anyURI"/>
     <attribute name="discoveryPolicy" type="conf:string"/>
     <attribute name="isPassive" type="boolean"/>
     <attribute name="returnOnError" type="boolean"/>
     <attribute name="forceAuthn" type="boolean"/>
     <attribute name="authnContextClassRef" type="conf:listOfURIs"/>
     <attribute name="authnContextComparison" type="samlp:AuthnContextComparisonType"/>
-    <attribute name="NameIDFormat" type="anyURI"/>
+    <attribute name="NameIDFormat" type="conf:anyURI"/>
     <attribute name="SPNameQualifier" type="conf:string"/>
-    <attribute name="redirectErrors" type="anyURI"/>
-    <attribute name="sessionError" type="anyURI"/>
-    <attribute name="metadataError" type="anyURI"/>
-    <attribute name="accessError" type="anyURI"/>
-    <attribute name="sslError" type="anyURI"/>
-    <attribute name="target" type="anyURI"/>
+    <attribute name="redirectErrors" type="conf:anyURI"/>
+    <attribute name="sessionError" type="conf:anyURI"/>
+    <attribute name="metadataError" type="conf:anyURI"/>
+    <attribute name="accessError" type="conf:anyURI"/>
+    <attribute name="sslError" type="conf:anyURI"/>
+    <attribute name="target" type="conf:anyURI"/>
     <attribute name="acsIndex" type="unsignedShort"/>
     <attribute name="REMOTE_ADDR" type="conf:string"/>
     <attribute name="encoding" type="conf:string"/>
@@ -410,7 +416,7 @@
       </choice>
     </sequence>
     <attribute name="id" type="conf:string" fixed="default"/>
-    <attribute name="entityID" type="anyURI" use="required"/>
+    <attribute name="entityID" type="conf:anyURI" use="required"/>
     <attributeGroup ref="conf:ApplicationGroup"/>
     <attributeGroup ref="conf:RelyingPartyGroup"/>
     <anyAttribute namespace="##other" processContents="lax"/>
@@ -436,14 +442,14 @@
       </choice>
     </sequence>
     <attribute name="id" type="conf:string" use="required"/>
-    <attribute name="entityID" type="anyURI"/>
+    <attribute name="entityID" type="conf:anyURI"/>
     <attributeGroup ref="conf:ApplicationGroup"/>
     <attributeGroup ref="conf:RelyingPartyGroup"/>
     <anyAttribute namespace="##other" processContents="lax"/>
   </complexType>
 
   <attributeGroup name="ApplicationGroup">
-    <attribute name="homeURL" type="anyURI"/>
+    <attribute name="homeURL" type="conf:anyURI"/>
     <attribute name="policyId" type="conf:string"/>
     <attribute name="REMOTE_USER" type="conf:listOfStrings"/>
     <attribute name="unsetHeaders" type="conf:listOfStrings"/>
@@ -457,10 +463,10 @@
     <attribute name="authUsername" type="conf:string"/>
     <attribute name="authPassword" type="conf:string"/>
     <attribute name="signing" type="conf:bindingBoolean"/>
-    <attribute name="signingAlg" type="anyURI"/>
-    <attribute name="digestAlg" type="anyURI"/>
+    <attribute name="signingAlg" type="conf:anyURI"/>
+    <attribute name="digestAlg" type="conf:anyURI"/>
     <attribute name="encryption" type="conf:bindingBoolean"/>
-    <attribute name="encryptionAlg" type="anyURI"/>
+    <attribute name="encryptionAlg" type="conf:anyURI"/>
     <attribute name="keyName" type="conf:string"/>
     <attribute name="artifactEndpointIndex" type="unsignedShort"/>
     <attribute name="chunkedEncoding" type="boolean"/>
@@ -470,11 +476,11 @@
     <attribute name="requireConfidentiality" type="boolean"/>
     <attribute name="requireTransportAuth" type="boolean"/>
     <attribute name="requireSignedAssertions" type="boolean"/>
-    <attribute name="sessionHook" type="anyURI"/>
+    <attribute name="sessionHook" type="conf:anyURI"/>
     <attribute name="artifactByFilesystem" type="boolean"/>
     <attribute name="authnContextClassRef" type="conf:listOfURIs"/>
     <attribute name="authnContextComparison" type="samlp:AuthnContextComparisonType"/>
-    <attribute name="NameIDFormat" type="anyURI"/>
+    <attribute name="NameIDFormat" type="conf:anyURI"/>
     <attribute name="SPNameQualifier" type="conf:string"/>
     <attribute name="requestDelegation" type="boolean"/>
   </attributeGroup>
@@ -494,7 +500,7 @@
               <attribute name="policyId" type="conf:string"/>
               <attribute name="ignoreNoPassive" type="boolean"/>
               <attribute name="discoveryProtocol" type="conf:string"/>
-              <attribute name="discoveryURL" type="anyURI"/>
+              <attribute name="discoveryURL" type="conf:anyURI"/>
               <attributeGroup ref="conf:SessionInitiatorGroup"/>
             </extension>
           </simpleContent>
@@ -535,7 +541,7 @@
         <element ref="conf:Handler"/>
       </choice>
     </sequence>
-    <attribute name="handlerURL" type="anyURI"/>
+    <attribute name="handlerURL" type="conf:anyURI"/>
     <attribute name="handlerSSL" type="boolean"/>
     <attribute name="exportLocation" type="conf:string"/>
     <attribute name="exportACL" type="conf:listOfStrings"/>
@@ -592,21 +598,21 @@
     </annotation>
     <attribute name="relayState" type="conf:string"/>
     <attribute name="entityIDParam" type="conf:string"/>
-    <attribute name="entityID" type="anyURI"/>
+    <attribute name="entityID" type="conf:anyURI"/>
     <attribute name="outgoingBindings" type="conf:listOfURIs"/>
     <attribute name="preservedOptions" type="conf:listOfStrings"/>
-    <attribute name="template" type="anyURI"/>
+    <attribute name="template" type="conf:anyURI"/>
     <attribute name="postArtifact" type="boolean"/>
     <attribute name="acsByIndex" type="boolean"/>
     <attribute name="isPassive" type="boolean"/>
     <attribute name="returnOnError" type="boolean"/>
     <attribute name="forceAuthn" type="boolean"/>
-    <attribute name="authnContextClassRef" type="anyURI"/>
+    <attribute name="authnContextClassRef" type="conf:anyURI"/>
     <attribute name="authnContextComparison" type="samlp:AuthnContextComparisonType"/>
-    <attribute name="NameIDFormat" type="anyURI"/>
+    <attribute name="NameIDFormat" type="conf:anyURI"/>
     <attribute name="SPNameQualifier" type="conf:string"/>
     <attribute name="requestDelegation" type="boolean"/>
-    <attribute name="target" type="anyURI"/>
+    <attribute name="target" type="conf:anyURI"/>
     <attribute name="discoveryPolicy" type="conf:string"/>
     <attribute name="signing" type="conf:bindingBoolean"/>
     <attribute name="encryption" type="conf:bindingBoolean"/>
@@ -624,10 +630,10 @@
           <sequence>
             <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
           </sequence>
-          <attribute name="Location" type="anyURI"/>
+          <attribute name="Location" type="conf:anyURI"/>
           <attribute name="id" type="conf:string"/>
           <attribute name="isDefault" type="boolean"/>
-          <attribute name="URL" type="anyURI"/>
+          <attribute name="URL" type="conf:anyURI"/>
           <attribute name="acsIndex" type="unsignedShort"/>
           <attribute name="defaultACSIndex" type="unsignedShort"/>  <!-- deprecated -->
           <attributeGroup ref="conf:SessionInitiatorGroup"/>
@@ -642,7 +648,7 @@
     </annotation>
     <attribute name="relayState" type="conf:string"/>
     <attribute name="outgoingBindings" type="conf:listOfURIs"/>
-    <attribute name="template" type="anyURI"/>
+    <attribute name="template" type="conf:anyURI"/>
     <attribute name="postArtifact" type="boolean"/>
     <attribute name="signing" type="conf:bindingBoolean"/>
     <attribute name="encryption" type="conf:bindingBoolean"/>    
@@ -659,7 +665,7 @@
           <sequence>
             <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
           </sequence>
-          <attribute name="Location" type="anyURI"/>
+          <attribute name="Location" type="conf:anyURI"/>
           <attributeGroup ref="conf:LogoutInitiatorGroup"/>
         </restriction>
       </complexContent>
@@ -676,7 +682,7 @@
           <sequence>
             <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
           </sequence>
-          <attribute name="Location" type="anyURI" use="required"/>
+          <attribute name="Location" type="conf:anyURI" use="required"/>
           <attribute name="acl" type="conf:listOfStrings"/>
           <anyAttribute namespace="##any" processContents="lax"/>
         </restriction>
@@ -691,14 +697,14 @@
     <sequence>
       <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
     </sequence>
-    <attribute name="redirectErrors" type="anyURI"/>
-    <attribute name="session" type="anyURI"/>
-    <attribute name="metadata" type="anyURI"/>
-    <attribute name="access" type="anyURI"/>
-    <attribute name="ssl" type="anyURI"/>
-    <attribute name="localLogout" type="anyURI"/>
-    <attribute name="globalLogout" type="anyURI"/>
-    <attribute name="partialLogout" type="anyURI"/>
+    <attribute name="redirectErrors" type="conf:anyURI"/>
+    <attribute name="session" type="conf:anyURI"/>
+    <attribute name="metadata" type="conf:anyURI"/>
+    <attribute name="access" type="conf:anyURI"/>
+    <attribute name="ssl" type="conf:anyURI"/>
+    <attribute name="localLogout" type="conf:anyURI"/>
+    <attribute name="globalLogout" type="conf:anyURI"/>
+    <attribute name="partialLogout" type="conf:anyURI"/>
     <anyAttribute namespace="##any" processContents="lax"/>
   </complexType>
 
@@ -712,7 +718,7 @@
     <attribute name="Name" type="conf:string"/>
     <attribute name="type" type="conf:string"/>
     <attributeGroup ref="conf:RelyingPartyGroup"/>
-    <attribute name="entityID" type="anyURI"/>
+    <attribute name="entityID" type="conf:anyURI"/>
     <anyAttribute namespace="##any" processContents="lax"/>
   </complexType>
 
@@ -729,7 +735,7 @@
         </restriction>
       </simpleType>
     </attribute>
-    <attribute name="Location" type="anyURI" use="required"/>
+    <attribute name="Location" type="conf:anyURI" use="required"/>
     <anyAttribute namespace="##any" processContents="lax"/>
   </complexType>
 
@@ -826,7 +832,7 @@
         <element ref="conf:TransportOption" minOccurs="0" maxOccurs="unbounded"/>
         <element ref="ds:Signature" minOccurs="0"/>
       </sequence>
-      <attribute name="logger" type="anyURI"/>
+      <attribute name="logger" type="conf:anyURI"/>
       <attribute name="clockSkew" type="unsignedInt"/>
       <attribute name="unsafeChars" type="conf:string"/>
       <attribute name="allowedSchemes" type="conf:listOfStrings"/>
diff --git a/schemas/shibboleth-3.0-native-sp-config.xsd b/schemas/shibboleth-3.0-native-sp-config.xsd
index 2a8a274..564c389 100644
--- a/schemas/shibboleth-3.0-native-sp-config.xsd
+++ b/schemas/shibboleth-3.0-native-sp-config.xsd
@@ -33,8 +33,14 @@
     <list itemType="conf:string"/>
   </simpleType>
 
+  <simpleType name="anyURI">
+    <restriction base="anyURI">
+      <minLength value="1"/>
+    </restriction>
+  </simpleType>
+
   <simpleType name="listOfURIs">
-    <list itemType="anyURI"/>
+    <list itemType="conf:anyURI"/>
   </simpleType>
 
   <simpleType name="bindingBoolean">
@@ -84,7 +90,7 @@
           <sequence>
             <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
           </sequence>
-          <attribute name="path" type="anyURI" use="required"/>
+          <attribute name="path" type="conf:anyURI" use="required"/>
           <attribute name="fatal" type="boolean"/>
           <anyAttribute namespace="##any" processContents="lax"/>
         </complexType>
@@ -170,7 +176,7 @@
       <element name="Extensions" type="conf:ExtensionsType" minOccurs="0"/>
       <any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
     </sequence>
-    <attribute name="logger" type="anyURI"/>
+    <attribute name="logger" type="conf:anyURI"/>
     <attribute name="tranLogFormat" type="conf:string"/>
     <attribute name="tranLogFiller" type="conf:string"/>
     <attribute name="catchAll" type="boolean"/>
@@ -221,7 +227,7 @@
       </element>
       <any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
     </sequence>
-    <attribute name="logger" type="anyURI"/>
+    <attribute name="logger" type="conf:anyURI"/>
     <attribute name="unsetHeaderValue" type="conf:string"/>
     <attribute name="checkSpoofing" type="boolean"/>
     <attribute name="spoofKey" type="conf:string"/>
@@ -277,29 +283,29 @@
     <attribute name="authType" type="conf:string"/>
     <attribute name="requireSession" type="boolean"/>
     <attribute name="requireSessionWith" type="conf:string"/>
-    <attribute name="requireLogoutWith" type="anyURI"/>
+    <attribute name="requireLogoutWith" type="conf:anyURI"/>
     <attribute name="exportAssertion" type="boolean"/>
     <attribute name="exportStdVars" type="boolean"/>
     <attribute name="exportCookie" type="boolean"/>
     <attribute name="exportDuplicateValues" type="boolean"/>
     <attribute name="redirectToSSL" type="unsignedInt"/>
-    <attribute name="entityID" type="anyURI"/>
-    <attribute name="entityIDSelf" type="anyURI"/>
-    <attribute name="discoveryURL" type="anyURI"/>
+    <attribute name="entityID" type="conf:anyURI"/>
+    <attribute name="entityIDSelf" type="conf:anyURI"/>
+    <attribute name="discoveryURL" type="conf:anyURI"/>
     <attribute name="discoveryPolicy" type="conf:string"/>
     <attribute name="isPassive" type="boolean"/>
     <attribute name="returnOnError" type="boolean"/>
     <attribute name="forceAuthn" type="boolean"/>
     <attribute name="authnContextClassRef" type="conf:listOfURIs"/>
     <attribute name="authnContextComparison" type="samlp:AuthnContextComparisonType"/>
-    <attribute name="NameIDFormat" type="anyURI"/>
+    <attribute name="NameIDFormat" type="conf:anyURI"/>
     <attribute name="SPNameQualifier" type="conf:string"/>
-    <attribute name="redirectErrors" type="anyURI"/>
-    <attribute name="sessionError" type="anyURI"/>
-    <attribute name="metadataError" type="anyURI"/>
-    <attribute name="accessError" type="anyURI"/>
-    <attribute name="sslError" type="anyURI"/>
-    <attribute name="target" type="anyURI"/>
+    <attribute name="redirectErrors" type="conf:anyURI"/>
+    <attribute name="sessionError" type="conf:anyURI"/>
+    <attribute name="metadataError" type="conf:anyURI"/>
+    <attribute name="accessError" type="conf:anyURI"/>
+    <attribute name="sslError" type="conf:anyURI"/>
+    <attribute name="target" type="conf:anyURI"/>
     <attribute name="acsIndex" type="unsignedShort"/>
     <attribute name="REMOTE_ADDR" type="conf:string"/>
     <attribute name="encoding" type="conf:string"/>
@@ -445,7 +451,7 @@
       </choice>
     </sequence>
     <attribute name="id" type="conf:string" fixed="default"/>
-    <attribute name="entityID" type="anyURI" use="required"/>
+    <attribute name="entityID" type="conf:anyURI" use="required"/>
     <attributeGroup ref="conf:ApplicationGroup"/>
     <attributeGroup ref="conf:RelyingPartyGroup"/>
     <anyAttribute namespace="##other" processContents="lax"/>
@@ -472,7 +478,7 @@
       </choice>
     </sequence>
     <attribute name="id" type="conf:string" use="required"/>
-    <attribute name="entityID" type="anyURI"/>
+    <attribute name="entityID" type="conf:anyURI"/>
     <attributeGroup ref="conf:ApplicationGroup"/>
     <attributeGroup ref="conf:RelyingPartyGroup"/>
     <anyAttribute namespace="##other" processContents="lax"/>
@@ -487,7 +493,7 @@
   </complexType>
   
   <attributeGroup name="ApplicationGroup">
-    <attribute name="homeURL" type="anyURI"/>
+    <attribute name="homeURL" type="conf:anyURI"/>
     <attribute name="policyId" type="conf:string"/>
     <attribute name="REMOTE_USER" type="conf:listOfStrings"/>
     <attribute name="unsetHeaders" type="conf:listOfStrings"/>
@@ -501,10 +507,10 @@
     <attribute name="authUsername" type="conf:string"/>
     <attribute name="authPassword" type="conf:string"/>
     <attribute name="signing" type="conf:bindingBoolean"/>
-    <attribute name="signingAlg" type="anyURI"/>
-    <attribute name="digestAlg" type="anyURI"/>
+    <attribute name="signingAlg" type="conf:anyURI"/>
+    <attribute name="digestAlg" type="conf:anyURI"/>
     <attribute name="encryption" type="conf:bindingBoolean"/>
-    <attribute name="encryptionAlg" type="anyURI"/>
+    <attribute name="encryptionAlg" type="conf:anyURI"/>
     <attribute name="keyName" type="conf:string"/>
     <attribute name="artifactEndpointIndex" type="unsignedShort"/>
     <attribute name="chunkedEncoding" type="boolean"/>
@@ -514,11 +520,11 @@
     <attribute name="requireConfidentiality" type="boolean"/>
     <attribute name="requireTransportAuth" type="boolean"/>
     <attribute name="requireSignedAssertions" type="boolean"/>
-    <attribute name="sessionHook" type="anyURI"/>
+    <attribute name="sessionHook" type="conf:anyURI"/>
     <attribute name="artifactByFilesystem" type="boolean"/>
     <attribute name="authnContextClassRef" type="conf:listOfURIs"/>
     <attribute name="authnContextComparison" type="samlp:AuthnContextComparisonType"/>
-    <attribute name="NameIDFormat" type="anyURI"/>
+    <attribute name="NameIDFormat" type="conf:anyURI"/>
     <attribute name="SPNameQualifier" type="conf:string"/>
     <attribute name="requestDelegation" type="boolean"/>
   </attributeGroup>
@@ -538,7 +544,7 @@
               <attribute name="policyId" type="conf:string"/>
               <attribute name="ignoreNoPassive" type="boolean"/>
               <attribute name="discoveryProtocol" type="conf:string"/>
-              <attribute name="discoveryURL" type="anyURI"/>
+              <attribute name="discoveryURL" type="conf:anyURI"/>
               <attributeGroup ref="conf:SessionInitiatorGroup"/>
             </extension>
           </simpleContent>
@@ -579,7 +585,7 @@
         <element ref="conf:Handler"/>
       </choice>
     </sequence>
-    <attribute name="handlerURL" type="anyURI"/>
+    <attribute name="handlerURL" type="conf:anyURI"/>
     <attribute name="handlerSSL" type="boolean"/>
     <attribute name="exportLocation" type="conf:string"/>
     <attribute name="exportACL" type="conf:listOfStrings"/>
@@ -638,21 +644,21 @@
     </annotation>
     <attribute name="relayState" type="conf:string"/>
     <attribute name="entityIDParam" type="conf:string"/>
-    <attribute name="entityID" type="anyURI"/>
+    <attribute name="entityID" type="conf:anyURI"/>
     <attribute name="outgoingBindings" type="conf:listOfURIs"/>
     <attribute name="preservedOptions" type="conf:listOfStrings"/>
-    <attribute name="template" type="anyURI"/>
+    <attribute name="template" type="conf:anyURI"/>
     <attribute name="postArtifact" type="boolean"/>
     <attribute name="acsByIndex" type="boolean"/>
     <attribute name="isPassive" type="boolean"/>
     <attribute name="returnOnError" type="boolean"/>
     <attribute name="forceAuthn" type="boolean"/>
-    <attribute name="authnContextClassRef" type="anyURI"/>
+    <attribute name="authnContextClassRef" type="conf:anyURI"/>
     <attribute name="authnContextComparison" type="samlp:AuthnContextComparisonType"/>
-    <attribute name="NameIDFormat" type="anyURI"/>
+    <attribute name="NameIDFormat" type="conf:anyURI"/>
     <attribute name="SPNameQualifier" type="conf:string"/>
     <attribute name="requestDelegation" type="boolean"/>
-    <attribute name="target" type="anyURI"/>
+    <attribute name="target" type="conf:anyURI"/>
     <attribute name="discoveryPolicy" type="conf:string"/>
     <attribute name="signing" type="conf:bindingBoolean"/>
     <attribute name="encryption" type="conf:bindingBoolean"/>
@@ -670,10 +676,10 @@
           <sequence>
             <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
           </sequence>
-          <attribute name="Location" type="anyURI"/>
+          <attribute name="Location" type="conf:anyURI"/>
           <attribute name="id" type="conf:string"/>
           <attribute name="isDefault" type="boolean"/>
-          <attribute name="URL" type="anyURI"/>
+          <attribute name="URL" type="conf:anyURI"/>
           <attribute name="acsIndex" type="unsignedShort"/>
           <attributeGroup ref="conf:SessionInitiatorGroup"/>
         </restriction>
@@ -687,7 +693,7 @@
     </annotation>
     <attribute name="relayState" type="conf:string"/>
     <attribute name="outgoingBindings" type="conf:listOfURIs"/>
-    <attribute name="template" type="anyURI"/>
+    <attribute name="template" type="conf:anyURI"/>
     <attribute name="postArtifact" type="boolean"/>
     <attribute name="signing" type="conf:bindingBoolean"/>
     <attribute name="encryption" type="conf:bindingBoolean"/>    
@@ -704,7 +710,7 @@
           <sequence>
             <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
           </sequence>
-          <attribute name="Location" type="anyURI"/>
+          <attribute name="Location" type="conf:anyURI"/>
           <attributeGroup ref="conf:LogoutInitiatorGroup"/>
         </restriction>
       </complexContent>
@@ -721,7 +727,7 @@
           <sequence>
             <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
           </sequence>
-          <attribute name="Location" type="anyURI" use="required"/>
+          <attribute name="Location" type="conf:anyURI" use="required"/>
           <attribute name="acl" type="conf:listOfStrings"/>
           <anyAttribute namespace="##any" processContents="lax"/>
         </restriction>
@@ -736,14 +742,14 @@
     <sequence>
       <any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
     </sequence>
-    <attribute name="redirectErrors" type="anyURI"/>
-    <attribute name="session" type="anyURI"/>
-    <attribute name="metadata" type="anyURI"/>
-    <attribute name="access" type="anyURI"/>
-    <attribute name="ssl" type="anyURI"/>
-    <attribute name="localLogout" type="anyURI"/>
-    <attribute name="globalLogout" type="anyURI"/>
-    <attribute name="partialLogout" type="anyURI"/>
+    <attribute name="redirectErrors" type="conf:anyURI"/>
+    <attribute name="session" type="conf:anyURI"/>
+    <attribute name="metadata" type="conf:anyURI"/>
+    <attribute name="access" type="conf:anyURI"/>
+    <attribute name="ssl" type="conf:anyURI"/>
+    <attribute name="localLogout" type="conf:anyURI"/>
+    <attribute name="globalLogout" type="conf:anyURI"/>
+    <attribute name="partialLogout" type="conf:anyURI"/>
     <anyAttribute namespace="##any" processContents="lax"/>
   </complexType>
 
@@ -757,7 +763,7 @@
     <attribute name="Name" type="conf:string"/>
     <attribute name="type" type="conf:string"/>
     <attributeGroup ref="conf:RelyingPartyGroup"/>
-    <attribute name="entityID" type="anyURI"/>
+    <attribute name="entityID" type="conf:anyURI"/>
     <anyAttribute namespace="##any" processContents="lax"/>
   </complexType>
 
@@ -774,7 +780,7 @@
         </restriction>
       </simpleType>
     </attribute>
-    <attribute name="Location" type="anyURI" use="required"/>
+    <attribute name="Location" type="conf:anyURI" use="required"/>
     <anyAttribute namespace="##any" processContents="lax"/>
   </complexType>
 
@@ -869,7 +875,7 @@
         <element ref="conf:TransportOption" minOccurs="0" maxOccurs="unbounded"/>
         <element ref="ds:Signature" minOccurs="0"/>
       </sequence>
-      <attribute name="logger" type="anyURI"/>
+      <attribute name="logger" type="conf:anyURI"/>
       <attribute name="clockSkew" type="unsignedInt"/>
       <attribute name="unsafeChars" type="conf:string"/>
       <attribute name="allowedSchemes" type="conf:listOfStrings"/>
diff --git a/shibsp/AbstractSPRequest.cpp b/shibsp/AbstractSPRequest.cpp
index 68f6b23..5bc3d67 100644
--- a/shibsp/AbstractSPRequest.cpp
+++ b/shibsp/AbstractSPRequest.cpp
@@ -343,6 +343,54 @@ void AbstractSPRequest::setAuthType(const char* authtype)
 
 }
 
+const char* AbstractSPRequest::getCookie(const char* name) const
+{
+    pair<bool, bool> sameSiteFallback = pair<bool, bool>(false, false);
+    const PropertySet* props = getApplication().getPropertySet("Sessions");
+    if (props) {
+        sameSiteFallback = props->getBool("sameSiteFallback");
+    }
+    return HTTPRequest::getCookie(name, sameSiteFallback.first && sameSiteFallback.second);
+}
+
+void AbstractSPRequest::setCookie(const char* name, const char* value, time_t expires, samesite_t sameSite)
+{
+    static const char* defProps="; path=/; HttpOnly";
+    static const char* sslProps="; path=/; secure; HttpOnly";
+
+    const char* cookieProps = defProps;
+    pair<bool,bool> sameSiteFallback = pair<bool,bool>(false, false);
+
+    const PropertySet* props = getApplication().getPropertySet("Sessions");
+    if (props) {
+        if (sameSite == SAMESITE_NONE) {
+            sameSiteFallback = props->getBool("sameSiteFallback");
+        }
+
+        pair<bool, const char*> p = props->getString("cookieProps");
+        if (p.first) {
+            if (!strcmp(p.second, "https"))
+                cookieProps = sslProps;
+            else if (strcmp(p.second, "http"))
+                cookieProps = p.second;
+        }
+    }
+
+    if (cookieProps) {
+        string decoratedValue(value ? value : "");
+        if (!value) {
+            decoratedValue += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
+        }
+        decoratedValue += cookieProps;
+        HTTPResponse::setCookie(name, decoratedValue.c_str(), expires, sameSite,
+            sameSiteFallback.first && sameSiteFallback.second);
+    }
+    else {
+        HTTPResponse::setCookie(name, value, expires, sameSite,
+            sameSiteFallback.first && sameSiteFallback.second);
+    }
+}
+
 void AbstractSPRequest::log(SPLogLevel level, const std::string& msg) const
 {
     reinterpret_cast<Category*>(m_log)->log(
diff --git a/shibsp/AbstractSPRequest.h b/shibsp/AbstractSPRequest.h
index cc6144c..48a2e28 100644
--- a/shibsp/AbstractSPRequest.h
+++ b/shibsp/AbstractSPRequest.h
@@ -75,7 +75,9 @@ namespace shibsp {
         std::vector<const char*>::size_type getParameters(const char* name, std::vector<const char*>& values) const;
         const char* getHandlerURL(const char* resource=nullptr) const;
         std::string getSecureHeader(const char* name) const;
+        const char* getCookie(const char* name) const;
         void setAuthType(const char* authtype);
+        void setCookie(const char* name, const char* value, time_t expires = 0, samesite_t sameSite = SAMESITE_ABSENT);
         void log(SPLogLevel level, const std::string& msg) const;
         bool isPriorityEnabled(SPLogLevel level) const;
 
diff --git a/shibsp/Application.cpp b/shibsp/Application.cpp
index bc33bca..5ab8507 100644
--- a/shibsp/Application.cpp
+++ b/shibsp/Application.cpp
@@ -60,8 +60,31 @@ const char* Application::getId() const
     return ret.first ? ret.second : "default";
 }
 
+string Application::getCookieName(const char* prefix, time_t* lifetime) const
+{
+    if (lifetime)
+        *lifetime = 0;
+    if (!prefix)
+        prefix = "";
+    const PropertySet* props = getPropertySet("Sessions");
+    if (props) {
+        if (lifetime) {
+            pair<bool,unsigned int> lt = props->getUnsignedInt("cookieLifetime");
+            if (lt.first)
+                *lifetime = lt.second;
+        }
+        pair<bool,const char*> p = props->getString("cookieName");
+        if (p.first)
+            return string(prefix) + p.second;
+    }
+
+    return string(prefix) + getHash();
+}
+
 pair<string,const char*> Application::getCookieNameProps(const char* prefix, time_t* lifetime) const
 {
+    // TODO: remove in V4 if it ever drops.
+
     static const char* defProps="; path=/; HttpOnly";
     static const char* sslProps="; path=/; secure; HttpOnly";
 
diff --git a/shibsp/Application.h b/shibsp/Application.h
index 076bfd8..7ba3d73 100644
--- a/shibsp/Application.h
+++ b/shibsp/Application.h
@@ -125,6 +125,19 @@ namespace shibsp {
         virtual const char* getHash() const=0;
 
         /**
+        * @Deprecated
+        *
+        * Returns the cookies name to use for this Application.
+        *
+        * @param prefix    a value to prepend to the base cookie name
+        * @param lifetime  if non-null, will be populated with a suggested lifetime for the cookie, or 0 if session-bound
+        * @return  the assigned cookie name to use
+        */
+        virtual std::string getCookieName(const char* prefix, time_t* lifetime=nullptr) const;
+
+        /**
+         * @Deprecated
+         *
          * Returns the name and cookie properties to use for this Application.
          *
          * @param prefix    a value to prepend to the base cookie name
diff --git a/shibsp/handler/AbstractHandler.h b/shibsp/handler/AbstractHandler.h
index 567f022..cef160f 100644
--- a/shibsp/handler/AbstractHandler.h
+++ b/shibsp/handler/AbstractHandler.h
@@ -248,7 +248,7 @@ namespace shibsp {
         virtual ~AbstractHandler();
 
     private:
-        std::pair<std::string,const char*> getPostCookieNameProps(const Application& app, const char* relayState) const;
+        std::string getPostCookieName(const Application& app, const char* relayState) const;
         DDF getPostData(const Application& application, const xmltooling::HTTPRequest& request) const;
     };
 
diff --git a/shibsp/handler/RemotedHandler.h b/shibsp/handler/RemotedHandler.h
index f8085d7..a22a5ec 100644
--- a/shibsp/handler/RemotedHandler.h
+++ b/shibsp/handler/RemotedHandler.h
@@ -98,17 +98,41 @@ namespace shibsp {
         /**
          * Builds a new request instance around a remoted data object.
          * 
+         * @param app the application
          * @param in    the dataflow object containing the remoted request
          * @return  a call-specific request object based on the input, to be freed by the caller 
          */
-        xmltooling::HTTPRequest* getRequest(DDF& in) const;
+        xmltooling::HTTPRequest* getRequest(const Application& app, DDF& in) const;
         
         /**
          * Builds a new response instance around an outgoing data object.
          * 
+         * @param app   the application
          * @param out   the dataflow object to be returned by the caller
          * @return  a call-specific response object, to be freed by the caller 
          */
+        xmltooling::HTTPResponse* getResponse(const Application& app, DDF& out) const;
+
+        /**
+        * @Deprecated
+        *
+        * Builds a new request instance around a remoted data object.
+        * 
+        * @param app the application
+        * @param in    the dataflow object containing the remoted request
+        * @return  a call-specific request object based on the input, to be freed by the caller 
+        */
+        xmltooling::HTTPRequest* getRequest(DDF& in) const;
+
+        /**
+        * @Deprecated
+        *
+        * Builds a new response instance around an outgoing data object.
+        * 
+        * @param app   the application
+        * @param out   the dataflow object to be returned by the caller
+        * @return  a call-specific response object, to be freed by the caller 
+        */
         xmltooling::HTTPResponse* getResponse(DDF& out) const;
 
         /** Message address for remote half. */
diff --git a/shibsp/handler/impl/AbstractHandler.cpp b/shibsp/handler/impl/AbstractHandler.cpp
index 361e38e..e3800e2 100644
--- a/shibsp/handler/impl/AbstractHandler.cpp
+++ b/shibsp/handler/impl/AbstractHandler.cpp
@@ -186,54 +186,60 @@ void Handler::cleanRelayState(
     const Application& application, const xmltooling::HTTPRequest& request, xmltooling::HTTPResponse& response
     ) const
 {
-    const PropertySet* sessionprop = application.getPropertySet("Sessions");
-
-    // Only cookie-based relay state requires cleaning.
     pair<bool,const char*> mech = getString("relayState");
     if (!mech.first) {
         // Check for setting on Sessions element.
+        const PropertySet* sessionprop = application.getPropertySet("Sessions");
         if (sessionprop) {
             mech = sessionprop->getString("relayState");
         }
     }
-    if (!mech.first || !mech.second || strncmp(mech.second, "cookie", 6))
-        return;
 
-    int maxCookies = 20,purgedCookies = 0;
-    mech.second += 6;
-    if (*mech.second == ':' && isdigit(*(++mech.second))) {
-        maxCookies = atoi(mech.second);
-        if (maxCookies == 0)
-            maxCookies = 20;
-    }
+    int maxRSCookies = 20,purgedRSCookies = 0;
+    int maxOSCookies = 20,purgedOSCookies = 0;
 
-    pair<bool, bool> sameSiteFallback =
-        sessionprop ? sessionprop->getBool("sameSiteFallback") : pair<bool,bool>(false, false);
-
-    string exp;
+    if (mech.first && !strncmp(mech.second, "cookie", 6)) {
+        mech.second += 6;
+        if (*mech.second == ':' && isdigit(*(++mech.second))) {
+            maxRSCookies = maxOSCookies = atoi(mech.second);
+            if (maxRSCookies == 0) {
+                maxRSCookies = maxOSCookies = 20;
+            }
+        }
+    }
 
     // Walk the list of cookies backwards by name.
     const map<string,string>& cookies = request.getCookies();
     for (map<string,string>::const_reverse_iterator i = cookies.rbegin(); i != cookies.rend(); ++i) {
         // Process relay state cookies only.
         if (starts_with(i->first, "_shibstate_")) {
-            if (maxCookies > 0) {
+            if (maxRSCookies > 0) {
                 // Keep it, but count it against the limit.
-                --maxCookies;
+                --maxRSCookies;
             }
             else {
                 // We're over the limit, so everything here and older gets cleaned up.
-                if (exp.empty())
-                    exp = string(application.getCookieNameProps("_shibstate_").second) + "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
-                response.setCookie(i->first.c_str(), exp.c_str(), HTTPResponse::SAMESITE_NONE,
-                    sameSiteFallback.first && sameSiteFallback.second);
-                ++purgedCookies;
+                response.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
+                ++purgedRSCookies;
+            }
+        }
+        else if (starts_with(i->first, "_opensaml_req_")) {
+            if (maxOSCookies > 0) {
+                // Keep it, but count it against the limit.
+                --maxOSCookies;
+            }
+            else {
+                // We're over the limit, so everything here and older gets cleaned up.
+                response.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
+                ++purgedOSCookies;
             }
         }
     }
 
-    if (purgedCookies > 0)
-        log(SPRequest::SPDebug, string("purged ") + lexical_cast<string>(purgedCookies) + " stale relay state cookie(s) from client");
+    if (purgedRSCookies > 0)
+        log(SPRequest::SPDebug, string("purged ") + lexical_cast<string>(purgedRSCookies) + " stale relay state cookie(s) from client");
+    if (purgedOSCookies > 0)
+        log(SPRequest::SPDebug, string("purged ") + lexical_cast<string>(purgedOSCookies) + " stale request correlation cookie(s) from client");
 }
 
 void Handler::preserveRelayState(const Application& application, HTTPResponse& response, string& relayState) const
@@ -242,12 +248,11 @@ void Handler::preserveRelayState(const Application& application, HTTPResponse& r
     if (relayState.empty())
         return;
 
-    const PropertySet* sessionprop = application.getPropertySet("Sessions");
-
     // No setting means just pass state by value.
     pair<bool,const char*> mech = getString("relayState");
     if (!mech.first) {
         // Check for setting on Sessions element.
+        const PropertySet* sessionprop = application.getPropertySet("Sessions");
         if (sessionprop)
             mech = sessionprop->getString("relayState");
     }
@@ -258,17 +263,14 @@ void Handler::preserveRelayState(const Application& application, HTTPResponse& r
         // Here we store the state in a cookie and send a fixed
         // value so we can recognize it on the way back.
         if (relayState.find("cookie:") != 0 && relayState.find("ss:") != 0) {
-            pair<bool, bool> sameSiteFallback =
-                sessionprop ? sessionprop->getBool("sameSiteFallback") : pair<bool,bool>(false, false);
-            pair<string,const char*> shib_cookie = application.getCookieNameProps("_shibstate_");
-            string stateval = XMLToolingConfig::getConfig().getURLEncoder()->encode(relayState.c_str()) + shib_cookie.second;
             // Generate a random key for the cookie name instead of the fixed name.
             string rsKey;
             generateRandomHex(rsKey, 4);
             rsKey = lexical_cast<string>(time(nullptr)) + '_' + rsKey;
-            shib_cookie.first = "_shibstate_" + rsKey;
-            response.setCookie(shib_cookie.first.c_str(), stateval.c_str(), HTTPResponse::SAMESITE_NONE,
-                sameSiteFallback.first && sameSiteFallback.second);
+            string shib_cookie_name = "_shibstate_" + rsKey;
+            response.setCookie(shib_cookie_name.c_str(),
+                XMLToolingConfig::getConfig().getURLEncoder()->encode(relayState.c_str()).c_str(),
+                0, HTTPResponse::SAMESITE_NONE);
             relayState = "cookie:" + rsKey;
         }
     }
@@ -392,14 +394,9 @@ void Handler::recoverRelayState(
     if (strstr(state,"cookie:") == state) {
         state += 7;
         if (*state) {
-            const PropertySet* sessionprop = application.getPropertySet("Sessions");
-            pair<bool, bool> sameSiteFallback =
-                sessionprop ? sessionprop->getBool("sameSiteFallback") : pair<bool,bool>(false, false);
-
             // Pull the value from the "relay state" cookie.
-            pair<string,const char*> relay_cookie = application.getCookieNameProps("_shibstate_");
-            relay_cookie.first = string("_shibstate_") + state;
-            state = request.getCookie(relay_cookie.first.c_str(), sameSiteFallback.first && sameSiteFallback.second);
+            string relay_cookie = string("_shibstate_") + state;
+            state = request.getCookie(relay_cookie.c_str());
             if (state && *state) {
                 // URL-decode the value.
                 char* rscopy = strdup(state);
@@ -407,10 +404,7 @@ void Handler::recoverRelayState(
                 relayState = rscopy;
                 free(rscopy);
                 if (clear) {
-                    string exp(relay_cookie.second);
-                    exp += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
-                    response.setCookie(relay_cookie.first.c_str(), exp.c_str(), HTTPResponse::SAMESITE_NONE,
-                        sameSiteFallback.first && sameSiteFallback.second);
+                    response.setCookie(relay_cookie.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
                 }
                 request.absolutize(relayState);
                 return;
@@ -661,13 +655,10 @@ void AbstractHandler::preservePostData(
             postkey = string(mech.second-3) + ':' + out.string();
         }
 
-        pair<string,const char*> shib_cookie = getPostCookieNameProps(application, relayState);
-
-        pair<bool,bool> sameSiteFallback = props ? props->getString("sameSiteFallback") : pair<bool,bool>(false, false);
+        string shib_cookie = getPostCookieName(application, relayState);
 
         // Purge any cookies in excess of 25.
         int maxCookies = 20,purgedCookies = 0;
-        string exp;
 
         // Walk the list of cookies backwards by name.
         const map<string,string>& cookies = request.getCookies();
@@ -680,10 +671,7 @@ void AbstractHandler::preservePostData(
                 }
                 else {
                     // We're over the limit, so everything here and older gets cleaned up.
-                    if (exp.empty())
-                        exp = string(shib_cookie.second) + "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
-                    response.setCookie(i->first.c_str(), exp.c_str(), HTTPResponse::SAMESITE_NONE,
-                        sameSiteFallback.first && sameSiteFallback.second);
+                    response.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
                     ++purgedCookies;
                 }
             }
@@ -693,9 +681,7 @@ void AbstractHandler::preservePostData(
             log(SPRequest::SPDebug, string("purged ") + lexical_cast<string>(purgedCookies) + " stale POST preservation cookie(s) from client");
 
         // Set a cookie with key info.
-        postkey += shib_cookie.second;
-        response.setCookie(shib_cookie.first.c_str(), postkey.c_str(), HTTPResponse::SAMESITE_NONE,
-            sameSiteFallback.first && sameSiteFallback.second);
+        response.setCookie(shib_cookie.c_str(), postkey.c_str(), 0, HTTPResponse::SAMESITE_NONE);
     }
     else {
         postData.destroy();
@@ -707,20 +693,15 @@ DDF AbstractHandler::recoverPostData(
     const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState
     ) const
 {
-    const PropertySet* props = application.getPropertySet("Sessions");
-    pair<bool,bool> sameSiteFallback = props ? props->getString("sameSiteFallback") : pair<bool,bool>(false, false);
+    string shib_cookie = getPostCookieName(application, relayState);
 
     // First we need the post recovery cookie.
-    pair<string,const char*> shib_cookie = getPostCookieNameProps(application, relayState);
-    const char* cookie = request.getCookie(shib_cookie.first.c_str(), sameSiteFallback.first && sameSiteFallback.second);
+    const char* cookie = request.getCookie(shib_cookie.c_str());
     if (!cookie || !*cookie)
         return DDF();
 
     // Clear the cookie.
-    string exp(shib_cookie.second);
-    exp += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
-    response.setCookie(shib_cookie.first.c_str(), exp.c_str(), HTTPResponse::SAMESITE_NONE,
-        sameSiteFallback.first && sameSiteFallback.second);
+    response.setCookie(shib_cookie.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
 
     // Look for StorageService-backed state of the form "ss:SSID:key".
     const char* state = cookie;
@@ -809,21 +790,20 @@ long AbstractHandler::sendPostResponse(
     return httpResponse.sendResponse(str);
 }
 
-pair<string,const char*> AbstractHandler::getPostCookieNameProps(const Application& app, const char* relayState) const
+string AbstractHandler::getPostCookieName(const Application& app, const char* relayState) const
 {
     // Decorates the name of the cookie with the relay state key, if any.
     // Doing so gives a better assurance that the recovered data really
     // belongs to the relayed request.
-    pair<string,const char*> shib_cookie=app.getCookieNameProps("_shibpost_");
     if (strstr(relayState, "cookie:") == relayState) {
-        shib_cookie.first = string("_shibpost_") + (relayState + 7);
+        return string("_shibpost_") + (relayState + 7);
     }
     else if (strstr(relayState, "ss:") == relayState) {
         const char* pch = strchr(relayState + 3, ':');
         if (pch)
-            shib_cookie.first = string("_shibpost_") + (pch + 1);
+            return string("_shibpost_") + (pch + 1);
     }
-    return shib_cookie;
+    return app.getCookieName("_shibpost_");
 }
 
 DDF AbstractHandler::getPostData(const Application& application, const HTTPRequest& request) const
diff --git a/shibsp/handler/impl/AdminLogoutInitiator.cpp b/shibsp/handler/impl/AdminLogoutInitiator.cpp
index 2be781b..7a1c5ba 100644
--- a/shibsp/handler/impl/AdminLogoutInitiator.cpp
+++ b/shibsp/handler/impl/AdminLogoutInitiator.cpp
@@ -170,12 +170,12 @@ void AdminLogoutInitiator::receive(DDF& in, ostream& out)
     }
 
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
 
     // Set up a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     // Since we're remoted, the result should either be a throw, which we pass on,
     // a false/0 return, which we just return as an empty structure, or a response/redirect,
diff --git a/shibsp/handler/impl/AssertionConsumerService.cpp b/shibsp/handler/impl/AssertionConsumerService.cpp
index a375f25..a896af6 100644
--- a/shibsp/handler/impl/AssertionConsumerService.cpp
+++ b/shibsp/handler/impl/AssertionConsumerService.cpp
@@ -138,12 +138,12 @@ void AssertionConsumerService::receive(DDF& in, ostream& out)
     }
 
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
 
     // Wrap a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     // Since we're remoted, the result should either be a throw, a false/0 return,
     // which we just return as an empty structure, or a response/redirect,
@@ -178,7 +178,7 @@ pair<bool,long> AssertionConsumerService::processMessage(
     scoped_ptr<XMLObject> msg;
     try {
         // Decode the message and process it in a protocol-specific way.
-        msg.reset(m_decoder->decode(relayState, httpRequest, *(policy.get())));
+        msg.reset(m_decoder->decode(relayState, httpRequest, &httpResponse, *(policy.get())));
         if (!msg)
             throw BindingException("Failed to decode an SSO protocol response.");
         implementProtocol(application, httpRequest, httpResponse, *policy, nullptr, *msg);
diff --git a/shibsp/handler/impl/AssertionLookup.cpp b/shibsp/handler/impl/AssertionLookup.cpp
index 8747f35..280c6a7 100644
--- a/shibsp/handler/impl/AssertionLookup.cpp
+++ b/shibsp/handler/impl/AssertionLookup.cpp
@@ -136,13 +136,13 @@ void AssertionLookup::receive(DDF& in, ostream& out)
     }
 
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
     //m_log.debug("found %d client certificates", req->getClientCertificates().size());
 
     // Wrap a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     // Since we're remoted, the result should either be a throw, a false/0 return,
     // which we just return as an empty structure, or a response/redirect,
diff --git a/shibsp/handler/impl/ExternalAuthHandler.cpp b/shibsp/handler/impl/ExternalAuthHandler.cpp
index 07ff3bf..82f749f 100644
--- a/shibsp/handler/impl/ExternalAuthHandler.cpp
+++ b/shibsp/handler/impl/ExternalAuthHandler.cpp
@@ -192,7 +192,7 @@ pair<bool,long> ExternalAuth::run(SPRequest& request, bool isHandler) const
             headers.push_back("Cookie");
             DDF in = wrap(request, &headers);
             DDFJanitor jin(in);
-            scoped_ptr<HTTPRequest> fakedreq(getRequest(in));
+            scoped_ptr<HTTPRequest> fakedreq(getRequest(request.getApplication(), in));
             return processMessage(request.getApplication(), *fakedreq, request, in);
         }
         else {
@@ -226,12 +226,12 @@ void ExternalAuth::receive(DDF& in, ostream& out)
     }
 
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
 
     // Wrap a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     // Since we're remoted, the result should either be a throw, a false/0 return,
     // which we just return as an empty structure, or a response/redirect,
diff --git a/shibsp/handler/impl/LocalLogoutInitiator.cpp b/shibsp/handler/impl/LocalLogoutInitiator.cpp
index 6a48ab2..0eda632 100644
--- a/shibsp/handler/impl/LocalLogoutInitiator.cpp
+++ b/shibsp/handler/impl/LocalLogoutInitiator.cpp
@@ -145,12 +145,12 @@ void LocalLogoutInitiator::receive(DDF& in, ostream& out)
     }
 
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
 
     // Set up a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     Session* session = nullptr;
     try {
diff --git a/shibsp/handler/impl/MetadataGenerator.cpp b/shibsp/handler/impl/MetadataGenerator.cpp
index 4b57d80..674f6b1 100644
--- a/shibsp/handler/impl/MetadataGenerator.cpp
+++ b/shibsp/handler/impl/MetadataGenerator.cpp
@@ -415,7 +415,7 @@ void MetadataGenerator::receive(DDF& in, ostream& out)
     // Wrap a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     // Since we're remoted, the result should either be a throw, a false/0 return,
     // which we just return as an empty structure, or a response/redirect,
diff --git a/shibsp/handler/impl/RemotedHandler.cpp b/shibsp/handler/impl/RemotedHandler.cpp
index 2b7ba68..2fd941e 100644
--- a/shibsp/handler/impl/RemotedHandler.cpp
+++ b/shibsp/handler/impl/RemotedHandler.cpp
@@ -66,6 +66,7 @@ namespace shibsp {
 #endif
         public HTTPRequest
     {
+        const Application* m_app;
         DDF& m_input;
         mutable scoped_ptr<CGIParser> m_parser;
         mutable vector<XSECCryptoX509*> m_certs;
@@ -74,7 +75,7 @@ namespace shibsp {
         mutable gss_name_t m_gssname;
 #endif
     public:
-        RemotedRequest(DDF& input) : m_input(input), m_parser(nullptr)
+        RemotedRequest(const Application* app, DDF& input) : m_app(app), m_input(input), m_parser(nullptr)
 #ifdef SHIBSP_HAVE_GSSAPI
             , m_gssctx(GSS_C_NO_CONTEXT), m_gssname(GSS_C_NO_NAME)
 #endif
@@ -153,19 +154,29 @@ namespace shibsp {
             DDF s = m_input["headers"][name];
             return s.string() ? s.string() : "";
         }
+        const char* getCookie(const char* name) const {
+            pair<bool,bool> sameSiteFallback = pair<bool,bool>(false, false);
+            const PropertySet* props = m_app ? m_app->getPropertySet("Sessions") : nullptr;
+            if (props) {
+                sameSiteFallback = props->getBool("sameSiteFallback");
+            }
+            return HTTPRequest::getCookie(name, sameSiteFallback.first && sameSiteFallback.second);
+        }
     };
 
     class SHIBSP_DLLLOCAL RemotedResponse : public virtual HTTPResponse 
     {
+        const Application* m_app;
         DDF& m_output;
     public:
-        RemotedResponse(DDF& output) : m_output(output) {}
+        RemotedResponse(const Application* app, DDF& output) : m_app(app), m_output(output) {}
         virtual ~RemotedResponse() {}
        
         // GenericResponse
         long sendResponse(std::istream& inputStream, long status);
         
         // HTTPResponse
+        void setCookie(const char* name, const char* value, time_t expires = 0, samesite_t sameSite = SAMESITE_ABSENT);
         void setResponseHeader(const char* name, const char* value, bool replace=false);
         long sendRedirect(const char* url);
     };
@@ -279,6 +290,44 @@ gss_name_t RemotedRequest::getGSSName() const
 }
 #endif
 
+void RemotedResponse::setCookie(const char* name, const char* value, time_t expires, samesite_t sameSite)
+{
+    static const char* defProps="; path=/; HttpOnly";
+    static const char* sslProps="; path=/; secure; HttpOnly";
+
+    const char* cookieProps = defProps;
+    pair<bool,bool> sameSiteFallback = pair<bool,bool>(false, false);
+
+    const PropertySet* props = m_app ? m_app->getPropertySet("Sessions") : nullptr;
+    if (props) {
+        if (sameSite == SAMESITE_NONE) {
+            sameSiteFallback = props->getBool("sameSiteFallback");
+        }
+
+        pair<bool, const char*> p = props->getString("cookieProps");
+        if (p.first) {
+            if (!strcmp(p.second, "https"))
+                cookieProps = sslProps;
+            else if (strcmp(p.second, "http"))
+                cookieProps = p.second;
+        }
+    }
+
+    if (cookieProps) {
+        string decoratedValue(value ? value : "");
+        if (!value) {
+            decoratedValue += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
+        }
+        decoratedValue += cookieProps;
+        HTTPResponse::setCookie(name, decoratedValue.c_str(), expires, sameSite,
+            sameSiteFallback.first && sameSiteFallback.second);
+    }
+    else {
+        HTTPResponse::setCookie(name, value, expires, sameSite,
+            sameSiteFallback.first && sameSiteFallback.second);
+    }
+}
+
 long RemotedResponse::sendResponse(std::istream& in, long status)
 {
     string msg;
@@ -517,8 +566,9 @@ pair<bool,long> RemotedHandler::unwrap(SPRequest& request, DDF& out) const
 
 HTTPRequest* RemotedHandler::getRequest(DDF& in) const
 {
+    // TODO: remove in V4
 #ifndef SHIBSP_LITE
-    return new RemotedRequest(in);
+    return new RemotedRequest(nullptr, in);
 #else
     throw ConfigurationException("Cannot process message using lite version of shibsp library.");
 #endif
@@ -526,8 +576,27 @@ HTTPRequest* RemotedHandler::getRequest(DDF& in) const
 
 HTTPResponse* RemotedHandler::getResponse(DDF& out) const
 {
+    // TODO: remove in V4
+#ifndef SHIBSP_LITE
+    return new RemotedResponse(nullptr, out);
+#else
+    throw ConfigurationException("Cannot process message using lite version of shibsp library.");
+#endif
+}
+
+HTTPRequest* RemotedHandler::getRequest(const Application& app, DDF& in) const
+{
+#ifndef SHIBSP_LITE
+    return new RemotedRequest(&app, in);
+#else
+    throw ConfigurationException("Cannot process message using lite version of shibsp library.");
+#endif
+}
+
+HTTPResponse* RemotedHandler::getResponse(const Application& app, DDF& out) const
+{
 #ifndef SHIBSP_LITE
-    return new RemotedResponse(out);
+    return new RemotedResponse(&app, out);
 #else
     throw ConfigurationException("Cannot process message using lite version of shibsp library.");
 #endif
diff --git a/shibsp/handler/impl/SAML2ArtifactResolution.cpp b/shibsp/handler/impl/SAML2ArtifactResolution.cpp
index 98e1d2d..d7e755c 100644
--- a/shibsp/handler/impl/SAML2ArtifactResolution.cpp
+++ b/shibsp/handler/impl/SAML2ArtifactResolution.cpp
@@ -221,13 +221,13 @@ void SAML2ArtifactResolution::receive(DDF& in, ostream& out)
     }
     
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
     //m_log.debug("found %d client certificates", req->getClientCertificates().size());
 
     // Wrap a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
         
     try {
         // Since we're remoted, the result should either be a throw, a false/0 return,
@@ -283,7 +283,7 @@ pair<bool,long> SAML2ArtifactResolution::processMessage(const Application& appli
     
     // Decode the message and verify that it's a secured ArtifactResolve request.
     string relayState;
-    scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, httpRequest, *policy));
+    scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, httpRequest, &httpResponse, *policy));
     if (!msg)
         throw BindingException("Failed to decode a SAML request.");
     const ArtifactResolve* req = dynamic_cast<const ArtifactResolve*>(msg.get());
diff --git a/shibsp/handler/impl/SAML2Logout.cpp b/shibsp/handler/impl/SAML2Logout.cpp
index fb03d1a..e6a6c93 100644
--- a/shibsp/handler/impl/SAML2Logout.cpp
+++ b/shibsp/handler/impl/SAML2Logout.cpp
@@ -244,12 +244,12 @@ void SAML2Logout::receive(DDF& in, ostream& out)
     }
 
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
 
     // Wrap a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     // Since we're remoted, the result should either be a throw, which we pass on,
     // a false/0 return, which we just return as an empty structure, or a response/redirect,
@@ -362,7 +362,7 @@ pair<bool,long> SAML2Logout::doRequest(const Application& application, HTTPReque
 
     // Decode the message.
     string relayState;
-    scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, request, *policy));
+    scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, request, &response, *policy));
     const LogoutRequest* logoutRequest = dynamic_cast<LogoutRequest*>(msg.get());
     if (logoutRequest) {
         if (!policy->isAuthenticated())
diff --git a/shibsp/handler/impl/SAML2LogoutInitiator.cpp b/shibsp/handler/impl/SAML2LogoutInitiator.cpp
index 1c09539..02cb220 100644
--- a/shibsp/handler/impl/SAML2LogoutInitiator.cpp
+++ b/shibsp/handler/impl/SAML2LogoutInitiator.cpp
@@ -250,12 +250,12 @@ void SAML2LogoutInitiator::receive(DDF& in, ostream& out)
     }
 
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
 
     // Set up a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     Session* session = nullptr;
     try {
diff --git a/shibsp/handler/impl/SAML2NameIDMgmt.cpp b/shibsp/handler/impl/SAML2NameIDMgmt.cpp
index 68cb35f..2e8956a 100644
--- a/shibsp/handler/impl/SAML2NameIDMgmt.cpp
+++ b/shibsp/handler/impl/SAML2NameIDMgmt.cpp
@@ -225,12 +225,12 @@ void SAML2NameIDMgmt::receive(DDF& in, ostream& out)
     }
 
     // Unpack the request.
-    scoped_ptr<HTTPRequest> req(getRequest(in));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
 
     // Wrap a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     // Since we're remoted, the result should either be a throw, which we pass on,
     // a false/0 return, which we just return as an empty structure, or a response/redirect,
@@ -261,7 +261,7 @@ pair<bool,long> SAML2NameIDMgmt::doRequest(const Application& application, HTTPR
 
     // Decode the message.
     string relayState;
-    scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, request, *policy));
+    scoped_ptr<XMLObject> msg(m_decoder->decode(relayState, request, &response, *policy));
     const ManageNameIDRequest* mgmtRequest = dynamic_cast<ManageNameIDRequest*>(msg.get());
     if (mgmtRequest) {
         if (!policy->isAuthenticated())
diff --git a/shibsp/handler/impl/SAML2SessionInitiator.cpp b/shibsp/handler/impl/SAML2SessionInitiator.cpp
index 8c2d0cc..ecde0b1 100644
--- a/shibsp/handler/impl/SAML2SessionInitiator.cpp
+++ b/shibsp/handler/impl/SAML2SessionInitiator.cpp
@@ -529,7 +529,7 @@ void SAML2SessionInitiator::receive(DDF& in, ostream& out)
     DDFJanitor jout(ret);
 
     // Wrap the outgoing object with a Response facade.
-    scoped_ptr<HTTPResponse> http(getResponse(ret));
+    scoped_ptr<HTTPResponse> http(getResponse(*app, ret));
 
     auto_ptr_XMLCh index(in["acsIndex"].string());
     auto_ptr_XMLCh bind(in["acsBinding"].string());
diff --git a/shibsp/handler/impl/Shib1SessionInitiator.cpp b/shibsp/handler/impl/Shib1SessionInitiator.cpp
index c4aed4c..cbdd7a7 100644
--- a/shibsp/handler/impl/Shib1SessionInitiator.cpp
+++ b/shibsp/handler/impl/Shib1SessionInitiator.cpp
@@ -253,7 +253,7 @@ void Shib1SessionInitiator::receive(DDF& in, ostream& out)
     DDFJanitor jout(ret);
 
     // Wrap the outgoing object with a Response facade.
-    scoped_ptr<HTTPResponse> http(getResponse(ret));
+    scoped_ptr<HTTPResponse> http(getResponse(*app, ret));
 
     string relayState(in["RelayState"].string() ? in["RelayState"].string() : "");
 
diff --git a/shibsp/handler/impl/StatusHandler.cpp b/shibsp/handler/impl/StatusHandler.cpp
index 1f27caf..45851cc 100644
--- a/shibsp/handler/impl/StatusHandler.cpp
+++ b/shibsp/handler/impl/StatusHandler.cpp
@@ -363,8 +363,8 @@ void StatusHandler::receive(DDF& in, ostream& out)
     // Wrap a response shim.
     DDF ret(nullptr);
     DDFJanitor jout(ret);
-    scoped_ptr<HTTPRequest> req(getRequest(in));
-    scoped_ptr<HTTPResponse> resp(getResponse(ret));
+    scoped_ptr<HTTPRequest> req(getRequest(*app, in));
+    scoped_ptr<HTTPResponse> resp(getResponse(*app, ret));
 
     // Since we're remoted, the result should either be a throw, a false/0 return,
     // which we just return as an empty structure, or a response/redirect,
diff --git a/shibsp/impl/StorageServiceSessionCache.cpp b/shibsp/impl/StorageServiceSessionCache.cpp
index 3f86880..897efd8 100644
--- a/shibsp/impl/StorageServiceSessionCache.cpp
+++ b/shibsp/impl/StorageServiceSessionCache.cpp
@@ -284,11 +284,7 @@ string SSCache::active(const Application& app, const HTTPRequest& request)
             return session_id;
     }
 
-    const PropertySet* props = app.getPropertySet("Sessions");
-    pair<bool,bool> sameSiteFallback = props ? props->getBool("sameSiteFallback") : pair<bool,bool>(false, false);
-
-    pair<string, const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
-    const char* session_id = request.getCookie(shib_cookie.first.c_str(), sameSiteFallback.first && sameSiteFallback.second);
+    const char* session_id = request.getCookie(app.getCookieName("_shibsession_").c_str());
     return (session_id ? session_id : "");
 }
 
@@ -559,25 +555,9 @@ void SSCache::insert(
         httpResponse.setResponseHeader(m_outboundHeader.c_str(), key.get());
 
     time_t cookieLifetime = 0;
-    pair<string,const char*> shib_cookie = app.getCookieNameProps("_shibsession_", &cookieLifetime);
-    string k(key.get());
-    k += shib_cookie.second;
-
-    if (cookieLifetime > 0) {
-        cookieLifetime += now;
-#ifndef HAVE_GMTIME_R
-        ptime=gmtime(&cookieLifetime);
-#else
-        ptime=gmtime_r(&cookieLifetime,&res);
-#endif
-        char cookietimebuf[64];
-        strftime(cookietimebuf,64,"; expires=%a, %d %b %Y %H:%M:%S GMT",ptime);
-        k += cookietimebuf;
-    }
-
-    pair<bool,HTTPResponse::samesite_t> sameSitePolicy = getSameSitePolicy(app);
-
-    httpResponse.setCookie(shib_cookie.first.c_str(), k.c_str(), sameSitePolicy.second, sameSitePolicy.first);
+    string shib_cookie = app.getCookieName("_shibsession_", &cookieLifetime);
+    HTTPResponse::samesite_t sameSitePolicy = getSameSitePolicy(app);
+    httpResponse.setCookie(shib_cookie.c_str(), key.get(), cookieLifetime, sameSitePolicy);
     sessionID = key.get();
 
     // See if we need to persist the session data itself to a cookie for cross-node recovery.
@@ -591,8 +571,8 @@ void SSCache::persist(
     HTTPResponse& httpResponse,
     DDF& session,
     time_t expires,
-    pair<bool,HTTPResponse::samesite_t>& sameSitePolicy
-) const
+    HTTPResponse::samesite_t sameSitePolicy
+    ) const
 {
 #ifdef _DEBUG
     xmltooling::NDC ndc("persist");
@@ -631,21 +611,8 @@ void SSCache::persist(
         sealed = XMLToolingConfig::getConfig().getURLEncoder()->encode(sealed.c_str());
 
         time_t cookieLifetime;
-        pair<string,const char*> shib_cookie = app.getCookieNameProps("_shibsealed_", &cookieLifetime);
-        sealed += shib_cookie.second;
-        if (cookieLifetime > 0) {
-            cookieLifetime += time(nullptr);
-#ifndef HAVE_GMTIME_R
-            struct tm* ptime = gmtime(&cookieLifetime);
-#else
-            struct tm res;
-            struct tm* ptime = gmtime_r(&cookieLifetime, &res);
-#endif
-            char cookietimebuf[64];
-            strftime(cookietimebuf, 64, "; expires=%a, %d %b %Y %H:%M:%S GMT", ptime);
-            sealed += cookietimebuf;
-        }
-        httpResponse.setCookie(shib_cookie.first.c_str(), sealed.c_str(), sameSitePolicy.second, sameSitePolicy.first);
+        string shib_cookie = app.getCookieName("_shibsealed_", &cookieLifetime);
+        httpResponse.setCookie(shib_cookie.c_str(), sealed.c_str(), cookieLifetime, sameSitePolicy);
     }
     catch (const std::exception& e) {
         m_log.error("failed to wrap session (%s) with DataSealer: %s", session.name(), e.what());
@@ -908,28 +875,24 @@ LogoutEvent* SSCache::newLogoutEvent(const Application& app) const
 
 #endif
 
-pair<bool,HTTPResponse::samesite_t> SSCache::getSameSitePolicy(const Application& app) const
+HTTPResponse::samesite_t SSCache::getSameSitePolicy(const Application& app) const
 {
-    HTTPResponse::samesite_t ss = HTTPResponse::SAMESITE_ABSENT;
-    pair<bool,bool> sameSiteFallback = pair<bool,bool>(false, false);
-
     const PropertySet* props = app.getPropertySet("Sessions");
     if (props) {
-        sameSiteFallback = props->getBool("sameSiteFallback");
         pair<bool,const char*> sameSiteSession = props->getString("sameSiteSession");
         if (sameSiteSession.first) {
             if (!strcmp(sameSiteSession.second, "None")) {
-                ss = HTTPResponse::SAMESITE_NONE;
+                return HTTPResponse::SAMESITE_NONE;
             }
             else if (!strcmp(sameSiteSession.second, "Lax")) {
-                ss = HTTPResponse::SAMESITE_LAX;
+                return HTTPResponse::SAMESITE_LAX;
             }
             else if (!strcmp(sameSiteSession.second, "Strict")) {
-                ss = HTTPResponse::SAMESITE_STRICT;
+                return HTTPResponse::SAMESITE_STRICT;
             }
         }
     }
-    return pair<bool,HTTPResponse::samesite_t>(sameSiteFallback.first && sameSiteFallback.second, ss);
+    return HTTPResponse::SAMESITE_ABSENT;
 }
 
 Session* SSCache::_find(const Application& app, const char* key, const char* recovery, const char* client_addr, time_t* timeout)
@@ -1126,10 +1089,8 @@ Session* SSCache::find(const Application& app, HTTPRequest& request, const char*
     if (id.empty())
         return nullptr;
 
-    pair<bool,HTTPResponse::samesite_t> sameSitePolicy = getSameSitePolicy(app);
-
-    pair<string, const char*> shib_cookie = app.getCookieNameProps("_shibsealed_");
-    const char* c = request.getCookie(shib_cookie.first.c_str(), sameSitePolicy.first);
+    HTTPResponse::samesite_t sameSitePolicy = getSameSitePolicy(app);
+    const char* c = request.getCookie(app.getCookieName("_shibsealed_").c_str());
 
     try {
         Session* session = _find(app, id.c_str(), c, client_addr, timeout);
@@ -1140,12 +1101,8 @@ Session* SSCache::find(const Application& app, HTTPRequest& request, const char*
         if (response) {
             if (!m_outboundHeader.empty())
                 response->setResponseHeader(m_outboundHeader.c_str(), nullptr);
-            pair<string,const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
-            string exp(shib_cookie.second);
-            exp += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
-            response->setCookie(shib_cookie.first.c_str(), exp.c_str(), sameSitePolicy.second, sameSitePolicy.first);
-            shib_cookie = app.getCookieNameProps("_shibsealed_");
-            response->setCookie(shib_cookie.first.c_str(), exp.c_str(), sameSitePolicy.second, sameSitePolicy.first);
+            response->setCookie(app.getCookieName("_shibsession_").c_str(), nullptr, 0, sameSitePolicy);
+            response->setCookie(app.getCookieName("_shibsealed_").c_str(), nullptr, 0, sameSitePolicy);
         }
     }
     catch (const std::exception&) {
@@ -1153,12 +1110,8 @@ Session* SSCache::find(const Application& app, HTTPRequest& request, const char*
         if (response) {
             if (!m_outboundHeader.empty())
                 response->setResponseHeader(m_outboundHeader.c_str(), nullptr);
-            pair<string,const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
-            string exp(shib_cookie.second);
-            exp += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
-            response->setCookie(shib_cookie.first.c_str(), exp.c_str(), sameSitePolicy.second, sameSitePolicy.first);
-            shib_cookie = app.getCookieNameProps("_shibsealed_");
-            response->setCookie(shib_cookie.first.c_str(), exp.c_str(), sameSitePolicy.second, sameSitePolicy.first);
+            response->setCookie(app.getCookieName("_shibsession_").c_str(), nullptr, 0, sameSitePolicy);
+            response->setCookie(app.getCookieName("_shibsealed_").c_str(), nullptr, 0, sameSitePolicy);
         }
         throw;
     }
@@ -1292,14 +1245,12 @@ void SSCache::remove(const Application& app, const HTTPRequest& request, HTTPRes
     xmltooling::NDC ndc("remove");
 #endif
     string session_id;
-    pair<string,const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
-
-    pair<bool,HTTPResponse::samesite_t> sameSitePolicy = getSameSitePolicy(app);
+    string shib_cookie = app.getCookieName("_shibsession_");
 
     if (!m_inboundHeader.empty())
         session_id = request.getHeader(m_inboundHeader.c_str());
     if (session_id.empty()) {
-        const char* c = request.getCookie(shib_cookie.first.c_str(), sameSitePolicy.first);
+        const char* c = request.getCookie(shib_cookie.c_str());
         if (c && *c)
             session_id = c;
     }
@@ -1308,12 +1259,9 @@ void SSCache::remove(const Application& app, const HTTPRequest& request, HTTPRes
         if (response) {
             if (!m_outboundHeader.empty())
                 response->setResponseHeader(m_outboundHeader.c_str(), nullptr);
-            string exp(shib_cookie.second);
-            exp += "; expires=Mon, 01 Jan 2001 00:00:00 GMT";
-            response->setCookie(shib_cookie.first.c_str(), exp.c_str(), sameSitePolicy.second, sameSitePolicy.first);
-
-            shib_cookie = app.getCookieNameProps("_shibsealed_");
-            response->setCookie(shib_cookie.first.c_str(), exp.c_str(), sameSitePolicy.second, sameSitePolicy.first);
+            HTTPResponse::samesite_t sameSitePolicy = getSameSitePolicy(app);
+            response->setCookie(shib_cookie.c_str(), nullptr, 0, sameSitePolicy);
+            response->setCookie(app.getCookieName("_shibsealed_").c_str(), nullptr, 0, sameSitePolicy);
         }
         remove(app, session_id.c_str(), revocationExp);
     }
diff --git a/shibsp/impl/StorageServiceSessionCache.h b/shibsp/impl/StorageServiceSessionCache.h
index 09b3430..d447018 100644
--- a/shibsp/impl/StorageServiceSessionCache.h
+++ b/shibsp/impl/StorageServiceSessionCache.h
@@ -165,7 +165,7 @@ namespace shibsp {
         // handle potentially inexact address comparisons
         bool compareAddresses(const char* client_addr, const char* session_addr) const;
 
-        std::pair<bool,xmltooling::HTTPResponse::samesite_t> getSameSitePolicy(const Application& app) const;
+        xmltooling::HTTPResponse::samesite_t getSameSitePolicy(const Application& app) const;
 
         // management of buffered sessions
         void dormant(const char* key);
@@ -178,7 +178,7 @@ namespace shibsp {
             xmltooling::HTTPResponse& httpResponse,
             DDF& session,
             time_t expires,
-            std::pair<bool,xmltooling::HTTPResponse::samesite_t>& sameSitePolicy
+            xmltooling::HTTPResponse::samesite_t sameSitePolicy
             ) const;
 #endif
         bool recover(const Application& app, const char* key, const char* data);
diff --git a/shibsp/impl/XMLApplication.cpp b/shibsp/impl/XMLApplication.cpp
index 0355d1f..20a9d6d 100644
--- a/shibsp/impl/XMLApplication.cpp
+++ b/shibsp/impl/XMLApplication.cpp
@@ -38,6 +38,7 @@
 #include <xercesc/util/XMLStringTokenizer.hpp>
 #include <xmltooling/XMLToolingConfig.h>
 #include <xmltooling/util/NDC.h>
+#include <xmltooling/util/XMLConstants.h>
 #include <xmltooling/util/XMLHelper.h>
 
 #ifndef SHIBSP_LITE
@@ -82,6 +83,7 @@ namespace {
     static const XMLCh _Audience[] =            UNICODE_LITERAL_8(A,u,d,i,e,n,c,e);
     static const XMLCh Binding[] =              UNICODE_LITERAL_7(B,i,n,d,i,n,g);
     static const XMLCh Channel[]=               UNICODE_LITERAL_7(C,h,a,n,n,e,l);
+    static const XMLCh checkCorrelation[] =     UNICODE_LITERAL_16(c,h,e,c,k,C,o,r,r,e,l,a,t,i,o,n);
     static const XMLCh _CredentialResolver[] =  UNICODE_LITERAL_18(C,r,e,d,e,n,t,i,a,l,R,e,s,o,l,v,e,r);
     static const XMLCh _default[] =             UNICODE_LITERAL_7(d,e,f,a,u,l,t);
     static const XMLCh ExternalApplicationOverrides[] = UNICODE_LITERAL_28(E,x,t,e,r,n,a,l,A,p,p,l,i,c,a,t,i,o,n,O,v,e,r,r,i,d,e,s);
@@ -556,6 +558,9 @@ void XMLApplication::doHandlers(const ProtocolProvider* pp, const DOMElement* e,
                     child = XMLHelper::getNextSiblingElement(child);
                     continue;
                 }
+                // Somewhat icky; we disable request/response correlation by default by injecting an attribute
+                // to be picked up by the auto-added BEARER policy rule that's wrapped around this same element.
+                child->setAttributeNS(nullptr, checkCorrelation, xmlconstants::XML_ZERO);
                 handler.reset(
                     conf.AssertionConsumerServiceManager.newPlugin(bindprop.c_str(), pair<const DOMElement*,const char*>(child, getId()), m_deprecationSupport)
                     );
@@ -755,6 +760,10 @@ void XMLApplication::doSSO(const ProtocolProvider& pp, set<string>& protocols, D
                 if (idprop.first && pathprop.first) {
                     DOMElement* acsdom = e->getOwnerDocument()->createElementNS(samlconstants::SAML20MD_NS, _AssertionConsumerService);
 
+                    // Somewhat icky; we disable request/response correlation by default by injecting an attribute
+                    // to be picked up by the auto-added BEARER policy rule that's wrapped around this same element.
+                    acsdom->setAttributeNS(nullptr, checkCorrelation, xmlconstants::XML_ZERO);
+
                     // Copy in any attributes from the <SSO> element so they can be accessed as properties in the ACS handler,
                     // since the handlers aren't attached to the SSO element.
                     for (XMLSize_t p = 0; p < ssopropslen; ++p) {

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list