1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20 
21 /**
22  * AbstractHandler.cpp
23  *
24  * Base class for handlers based on a DOMPropertySet.
25  */
26 
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "Application.h"
30 #include "ServiceProvider.h"
31 #include "SPRequest.h"
32 #include "handler/AbstractHandler.h"
33 #include "handler/LogoutHandler.h"
34 #include "remoting/ListenerService.h"
35 #include "util/CGIParser.h"
36 #include "util/SPConstants.h"
37 #include "util/TemplateParameters.h"
38 
39 #include <vector>
40 #include <fstream>
41 #define BOOST_BIND_GLOBAL_PLACEHOLDERS
42 #include <boost/bind.hpp>
43 #include <boost/lexical_cast.hpp>
44 #include <boost/algorithm/string.hpp>
45 #include <xmltooling/XMLToolingConfig.h>
46 #include <xmltooling/util/PathResolver.h>
47 #include <xmltooling/util/URLEncoder.h>
48 
49 
50 #ifndef SHIBSP_LITE
51 # include <saml/exceptions.h>
52 # include <saml/SAMLConfig.h>
53 # include <saml/binding/SAMLArtifact.h>
54 # include <saml/saml1/core/Protocols.h>
55 # include <saml/saml2/core/Protocols.h>
56 # include <saml/saml2/metadata/Metadata.h>
57 # include <saml/saml2/metadata/MetadataCredentialCriteria.h>
58 # include <saml/util/SAMLConstants.h>
59 # include <xmltooling/security/Credential.h>
60 # include <xmltooling/security/CredentialResolver.h>
61 # include <xmltooling/util/StorageService.h>
62 using namespace opensaml::saml2md;
63 using namespace opensaml;
64 #else
65 # include "lite/SAMLConstants.h"
66 #endif
67 
68 #include <xmltooling/XMLToolingConfig.h>
69 #include <xmltooling/util/URLEncoder.h>
70 
71 using namespace shibsp;
72 using namespace samlconstants;
73 using namespace xmltooling;
74 using namespace xercesc;
75 using namespace boost;
76 using namespace std;
77 
78 namespace shibsp {
79     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML1ConsumerFactory;
80     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2ConsumerFactory;
81     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2ArtifactResolutionFactory;
82     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2LogoutFactory;
83     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SAML2NameIDMgmtFactory;
84     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AssertionLookupFactory;
85     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory AttributeCheckerFactory;
86     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory DiscoveryFeedFactory;
87     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory ExternalAuthFactory;
88     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory MetadataGeneratorFactory;
89     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory StatusHandlerFactory;
90     SHIBSP_DLLLOCAL PluginManager< Handler,string,pair<const DOMElement*,const char*> >::Factory SessionHandlerFactory;
91 
92 
generateRandomHex(std::string & buf,unsigned int len)93     void SHIBSP_DLLLOCAL generateRandomHex(std::string& buf, unsigned int len) {
94         static char DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
95         int r;
96         unsigned char b1,b2;
97         buf.erase();
98         for (unsigned int i=0; i<len; i+=4) {
99             r = rand();
100             b1 = (0x00FF & r);
101             b2 = (0xFF00 & r)  >> 8;
102             buf += (DIGITS[(0xF0 & b1) >> 4 ]);
103             buf += (DIGITS[0x0F & b1]);
104             buf += (DIGITS[(0xF0 & b2) >> 4 ]);
105             buf += (DIGITS[0x0F & b2]);
106         }
107     }
108 };
109 
registerHandlers()110 void SHIBSP_API shibsp::registerHandlers()
111 {
112     SPConfig& conf=SPConfig::getConfig();
113 
114     conf.AssertionConsumerServiceManager.registerFactory(SAML1_ASSERTION_CONSUMER_SERVICE, SAML1ConsumerFactory);
115     conf.AssertionConsumerServiceManager.registerFactory(SAML1_PROFILE_BROWSER_ARTIFACT, SAML1ConsumerFactory);
116     conf.AssertionConsumerServiceManager.registerFactory(SAML1_PROFILE_BROWSER_POST, SAML1ConsumerFactory);
117     conf.AssertionConsumerServiceManager.registerFactory(SAML20_ASSERTION_CONSUMER_SERVICE, SAML2ConsumerFactory);
118     conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_POST, SAML2ConsumerFactory);
119     conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_POST_SIMPLESIGN, SAML2ConsumerFactory);
120     conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_ARTIFACT, SAML2ConsumerFactory);
121     conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_PAOS, SAML2ConsumerFactory);
122 
123     conf.ArtifactResolutionServiceManager.registerFactory(SAML20_ARTIFACT_RESOLUTION_SERVICE, SAML2ArtifactResolutionFactory);
124     conf.ArtifactResolutionServiceManager.registerFactory(SAML20_BINDING_SOAP, SAML2ArtifactResolutionFactory);
125 
126     conf.HandlerManager.registerFactory(SAML20_BINDING_URI, AssertionLookupFactory);
127     conf.HandlerManager.registerFactory(ATTR_CHECKER_HANDLER, AttributeCheckerFactory);
128     conf.HandlerManager.registerFactory(DISCOVERY_FEED_HANDLER, DiscoveryFeedFactory);
129     conf.HandlerManager.registerFactory(EXTERNAL_AUTH_HANDLER, ExternalAuthFactory);
130     conf.HandlerManager.registerFactory(METADATA_GENERATOR_HANDLER, MetadataGeneratorFactory);
131     conf.HandlerManager.registerFactory(STATUS_HANDLER, StatusHandlerFactory);
132     conf.HandlerManager.registerFactory(SESSION_HANDLER, SessionHandlerFactory);
133 
134     conf.SingleLogoutServiceManager.registerFactory(SAML20_LOGOUT_HANDLER, SAML2LogoutFactory);
135     conf.SingleLogoutServiceManager.registerFactory(SAML20_BINDING_SOAP, SAML2LogoutFactory);
136     conf.SingleLogoutServiceManager.registerFactory(SAML20_BINDING_HTTP_REDIRECT, SAML2LogoutFactory);
137     conf.SingleLogoutServiceManager.registerFactory(SAML20_BINDING_HTTP_POST, SAML2LogoutFactory);
138     conf.SingleLogoutServiceManager.registerFactory(SAML20_BINDING_HTTP_POST_SIMPLESIGN, SAML2LogoutFactory);
139     conf.SingleLogoutServiceManager.registerFactory(SAML20_BINDING_HTTP_ARTIFACT, SAML2LogoutFactory);
140 
141     conf.ManageNameIDServiceManager.registerFactory(SAML20_NAMEID_MGMT_SERVICE, SAML2NameIDMgmtFactory);
142     conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_SOAP, SAML2NameIDMgmtFactory);
143     conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_HTTP_REDIRECT, SAML2NameIDMgmtFactory);
144     conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_HTTP_POST, SAML2NameIDMgmtFactory);
145     conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_HTTP_POST_SIMPLESIGN, SAML2NameIDMgmtFactory);
146     conf.ManageNameIDServiceManager.registerFactory(SAML20_BINDING_HTTP_ARTIFACT, SAML2NameIDMgmtFactory);
147 }
148 
Handler()149 Handler::Handler()
150 {
151 }
152 
~Handler()153 Handler::~Handler()
154 {
155 }
156 
157 #ifndef SHIBSP_LITE
158 
generateMetadata(SPSSODescriptor &,const char *) const159 void Handler::generateMetadata(SPSSODescriptor&, const char*) const
160 {
161 }
162 
163 #endif
164 
getProtocolFamily() const165 const XMLCh* Handler::getProtocolFamily() const
166 {
167     return nullptr;
168 }
169 
getEventType() const170 const char* Handler::getEventType() const
171 {
172     return nullptr;
173 }
174 
log(SPRequest::SPLogLevel level,const string & msg) const175 void Handler::log(SPRequest::SPLogLevel level, const string& msg) const
176 {
177     Category::getInstance(SHIBSP_LOGCAT ".Handler").log(
178         (level == SPRequest::SPDebug ? Priority::DEBUG :
179         (level == SPRequest::SPInfo ? Priority::INFO :
180         (level == SPRequest::SPWarn ? Priority::WARN :
181         (level == SPRequest::SPError ? Priority::ERROR : Priority::CRIT)))),
182         msg
183         );
184 }
185 
cleanRelayState(const Application & application,const xmltooling::HTTPRequest & request,xmltooling::HTTPResponse & response) const186 void Handler::cleanRelayState(
187     const Application& application, const xmltooling::HTTPRequest& request, xmltooling::HTTPResponse& response
188     ) const
189 {
190     pair<bool,const char*> mech = getString("relayState");
191     if (!mech.first) {
192         // Check for setting on Sessions element.
193         const PropertySet* sessionprop = application.getPropertySet("Sessions");
194         if (sessionprop) {
195             mech = sessionprop->getString("relayState");
196         }
197     }
198 
199     int maxRSCookies = 20,purgedRSCookies = 0;
200     int maxOSCookies = 20,purgedOSCookies = 0;
201 
202     if (mech.first && !strncmp(mech.second, "cookie", 6)) {
203         mech.second += 6;
204         if (*mech.second == ':' && isdigit(*(++mech.second))) {
205             maxRSCookies = maxOSCookies = atoi(mech.second);
206             if (maxRSCookies == 0) {
207                 maxRSCookies = maxOSCookies = 20;
208             }
209         }
210     }
211 
212     // Walk the list of cookies backwards by name.
213     const map<string,string>& cookies = request.getCookies();
214     for (map<string,string>::const_reverse_iterator i = cookies.rbegin(); i != cookies.rend(); ++i) {
215         if (starts_with(i->first, "_shibstate_")) {
216             if (maxRSCookies > 0) {
217                 // Keep it, but count it against the limit.
218                 --maxRSCookies;
219             }
220             else {
221                 // We're over the limit, so everything here and older gets cleaned up.
222                 response.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
223                 ++purgedRSCookies;
224             }
225         }
226         else if (starts_with(i->first, "_opensaml_req_")) {
227             if (maxOSCookies > 0) {
228                 // Keep it, but count it against the limit.
229                 --maxOSCookies;
230             }
231             else {
232                 // We're over the limit, so everything here and older gets cleaned up.
233                 response.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
234                 ++purgedOSCookies;
235             }
236         }
237     }
238 
239     if (purgedRSCookies > 0)
240         log(SPRequest::SPDebug, string("purged ") + lexical_cast<string>(purgedRSCookies) + " stale relay state cookie(s) from client");
241     if (purgedOSCookies > 0)
242         log(SPRequest::SPDebug, string("purged ") + lexical_cast<string>(purgedOSCookies) + " stale request correlation cookie(s) from client");
243 }
244 
preserveRelayState(const Application & application,HTTPResponse & response,string & relayState) const245 void Handler::preserveRelayState(const Application& application, HTTPResponse& response, string& relayState) const
246 {
247     // The empty string implies no state to deal with but we need to generate a correlation handle.
248     if (relayState.empty()) {
249         generateRandomHex(relayState, 4);
250         relayState = "corr:" + lexical_cast<string>(time(nullptr)) + '_' + relayState;
251         return;
252     }
253 
254     // No setting means just pass state by value.
255     pair<bool,const char*> mech = getString("relayState");
256     if (!mech.first) {
257         // Check for setting on Sessions element.
258         const PropertySet* sessionprop = application.getPropertySet("Sessions");
259         if (sessionprop)
260             mech = sessionprop->getString("relayState");
261     }
262     if (!mech.first || !mech.second || !*mech.second)
263         return;
264 
265     if (!strncmp(mech.second, "cookie", 6)) {
266         // Here we store the state in a cookie and send a fixed
267         // value so we can recognize it on the way back.
268         if (relayState.find("cookie:") != 0 && relayState.find("ss:") != 0) {
269             // Generate a random key for the cookie name instead of the fixed name.
270             string rsKey;
271             generateRandomHex(rsKey, 4);
272             rsKey = lexical_cast<string>(time(nullptr)) + '_' + rsKey;
273             string shib_cookie_name = "_shibstate_" + rsKey;
274             response.setCookie(shib_cookie_name.c_str(),
275                 XMLToolingConfig::getConfig().getURLEncoder()->encode(relayState.c_str()).c_str(),
276                 0, HTTPResponse::SAMESITE_NONE);
277             relayState = "cookie:" + rsKey;
278         }
279     }
280     else if (!strncmp(mech.second, "ss:", 3)) {
281         if (relayState.find("cookie:") != 0 && relayState.find("ss:") != 0) {
282             mech.second+=3;
283             if (*mech.second) {
284                 if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
285 #ifndef SHIBSP_LITE
286                     StorageService* storage = application.getServiceProvider().getStorageService(mech.second);
287                     if (storage) {
288                         // Use a random key
289                         string rsKey;
290                         SAMLConfig::getConfig().generateRandomBytes(rsKey,32);
291                         rsKey = SAMLArtifact::toHex(rsKey);
292                         if (relayState.length() <= storage->getCapabilities().getStringSize()) {
293                             if (!storage->createString("RelayState", rsKey.c_str(), relayState.c_str(), time(nullptr) + 600))
294                                 throw IOException("Collision generating in-memory relay state key.");
295                         }
296                         else {
297                             if (!storage->createText("RelayState", rsKey.c_str(), relayState.c_str(), time(nullptr) + 600))
298                                 throw IOException("Collision generating in-memory relay state key.");
299                         }
300                         relayState = string(mech.second-3) + ':' + rsKey;
301                     }
302                     else {
303                         string msg("Storage-backed RelayState with invalid StorageService ID (");
304                         msg = msg + mech.second+ ')';
305                         log(SPRequest::SPError, msg);
306                         relayState.erase();
307                     }
308 #else
309                     throw ConfigurationException("Lite version of library cannot be used out of process.");
310 #endif
311                 }
312                 else if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
313                     DDF out,in = DDF("set::RelayState").structure();
314                     in.addmember("id").string(mech.second);
315                     in.addmember("value").unsafe_string(relayState.c_str());
316                     DDFJanitor jin(in),jout(out);
317                     out = application.getServiceProvider().getListenerService()->send(in);
318                     if (!out.isstring())
319                         throw IOException("StorageService-backed RelayState mechanism did not return a state key.");
320                     relayState = string(mech.second-3) + ':' + out.string();
321                 }
322             }
323         }
324     }
325     else {
326         throw ConfigurationException("Unsupported relayState mechanism ($1).", params(1,mech.second));
327     }
328 }
329 
recoverRelayState(const Application & application,const HTTPRequest & request,HTTPResponse & response,string & relayState,bool clear) const330 void Handler::recoverRelayState(
331     const Application& application, const HTTPRequest& request, HTTPResponse& response, string& relayState, bool clear
332     ) const
333 {
334     SPConfig& conf = SPConfig::getConfig();
335 
336     // Sentry value that signifies it was only a correlation tool.
337     if (starts_with(relayState, "corr:")) {
338         relayState.clear();
339         return;
340     }
341 
342     // Look for StorageService-backed state of the form "ss:SSID:key".
343     const char* state = relayState.c_str();
344     if (strstr(state,"ss:") == state) {
345         state += 3;
346         const char* key = strchr(state,':');
347         if (key) {
348             string ssid = relayState.substr(3, key - state);
349             key++;
350             if (!ssid.empty() && *key) {
351                 if (conf.isEnabled(SPConfig::OutOfProcess)) {
352 #ifndef SHIBSP_LITE
353                     StorageService* storage = conf.getServiceProvider()->getStorageService(ssid.c_str());
354                     if (storage) {
355                         ssid = key;
356                         if (storage->readString("RelayState",ssid.c_str(),&relayState) > 0) {
357                             if (clear)
358                                 storage->deleteString("RelayState",ssid.c_str());
359                             request.absolutize(relayState);
360                             return;
361                         }
362                         else if (storage->readText("RelayState",ssid.c_str(),&relayState) > 0) {
363                             if (clear)
364                                 storage->deleteText("RelayState",ssid.c_str());
365                             request.absolutize(relayState);
366                             return;
367                         }
368                         else {
369                             relayState.erase();
370                         }
371                     }
372                     else {
373                         string msg("Storage-backed RelayState with invalid StorageService ID (");
374                         msg += ssid + ')';
375                         log(SPRequest::SPError, msg);
376                         relayState.erase();
377                     }
378 #endif
379                 }
380                 else if (conf.isEnabled(SPConfig::InProcess)) {
381                     DDF out,in = DDF("get::RelayState").structure();
382                     in.addmember("id").string(ssid.c_str());
383                     in.addmember("key").string(key);
384                     in.addmember("clear").integer(clear ? 1 : 0);
385                     DDFJanitor jin(in),jout(out);
386                     out = application.getServiceProvider().getListenerService()->send(in);
387                     if (!out.isstring()) {
388                         log(SPRequest::SPError, "StorageService-backed RelayState mechanism did not return a state value.");
389                         relayState.erase();
390                     }
391                     else {
392                         relayState = out.string();
393                         request.absolutize(relayState);
394                         return;
395                     }
396                 }
397             }
398         }
399     }
400 
401     // Look for cookie-backed state of the form "cookie:timestamp_key".
402     state = relayState.c_str();
403     if (strstr(state,"cookie:") == state) {
404         state += 7;
405         if (*state) {
406             // Pull the value from the "relay state" cookie.
407             string relay_cookie = string("_shibstate_") + state;
408             state = request.getCookie(relay_cookie.c_str());
409             if (state && *state) {
410                 // URL-decode the value.
411                 char* rscopy = strdup(state);
412                 XMLToolingConfig::getConfig().getURLEncoder()->decode(rscopy);
413                 relayState = rscopy;
414                 free(rscopy);
415                 if (clear) {
416                     response.setCookie(relay_cookie.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
417                 }
418                 request.absolutize(relayState);
419                 return;
420             }
421         }
422 
423         relayState.erase();
424     }
425 
426     // Check for "default" value (or the old "cookie" value that might come from stale bookmarks).
427     if (relayState.empty() || relayState == "default" || relayState == "cookie") {
428         pair<bool,const char*> homeURL=application.getString("homeURL");
429         if (homeURL.first)
430             relayState = homeURL.second;
431         else
432             relayState = '/';
433     }
434 
435     request.absolutize(relayState);
436 }
437 
AbstractHandler(const DOMElement * e,Category & log,DOMNodeFilter * filter,const Remapper * remapper)438 AbstractHandler::AbstractHandler(
439     const DOMElement* e, Category& log, DOMNodeFilter* filter, const Remapper* remapper
440     ) : m_log(log) {
441     load(e, nullptr, filter, remapper);
442 }
443 
~AbstractHandler()444 AbstractHandler::~AbstractHandler()
445 {
446 }
447 
log(SPRequest::SPLogLevel level,const string & msg) const448 void AbstractHandler::log(SPRequest::SPLogLevel level, const string& msg) const
449 {
450     m_log.log(
451         (level == SPRequest::SPDebug ? Priority::DEBUG :
452         (level == SPRequest::SPInfo ? Priority::INFO :
453         (level == SPRequest::SPWarn ? Priority::WARN :
454         (level == SPRequest::SPError ? Priority::ERROR : Priority::CRIT)))),
455         msg
456         );
457 }
458 
459 #ifndef SHIBSP_LITE
460 
getType() const461 const char* Handler::getType() const
462 {
463     return getString("type").second;
464 }
465 
checkError(const XMLObject * response,const saml2md::RoleDescriptor * role) const466 void AbstractHandler::checkError(const XMLObject* response, const saml2md::RoleDescriptor* role) const
467 {
468     const saml2p::StatusResponseType* r2 = dynamic_cast<const saml2p::StatusResponseType*>(response);
469     if (r2) {
470         const saml2p::Status* status = r2->getStatus();
471         if (status) {
472             const saml2p::StatusCode* sc = status->getStatusCode();
473             const XMLCh* code = sc ? sc->getValue() : nullptr;
474             if (code && !XMLString::equals(code,saml2p::StatusCode::SUCCESS)) {
475                 FatalProfileException ex("SAML response reported an IdP error.");
476                 annotateException(&ex, role, status);   // throws it
477             }
478         }
479     }
480 
481     const saml1p::Response* r1 = dynamic_cast<const saml1p::Response*>(response);
482     if (r1) {
483         const saml1p::Status* status = r1->getStatus();
484         if (status) {
485             const saml1p::StatusCode* sc = status->getStatusCode();
486             const xmltooling::QName* code = sc ? sc->getValue() : nullptr;
487             if (code && *code != saml1p::StatusCode::SUCCESS) {
488                 FatalProfileException ex("SAML response reported an IdP error.");
489                 annotateException(&ex, role, status);   // throws it
490             }
491         }
492     }
493 }
494 
fillStatus(saml2p::StatusResponseType & response,const XMLCh * code,const XMLCh * subcode,const char * msg) const495 void AbstractHandler::fillStatus(saml2p::StatusResponseType& response, const XMLCh* code, const XMLCh* subcode, const char* msg) const
496 {
497     saml2p::Status* status = saml2p::StatusBuilder::buildStatus();
498     saml2p::StatusCode* scode = saml2p::StatusCodeBuilder::buildStatusCode();
499     status->setStatusCode(scode);
500     scode->setValue(code);
501     if (subcode) {
502         saml2p::StatusCode* ssubcode = saml2p::StatusCodeBuilder::buildStatusCode();
503         scode->setStatusCode(ssubcode);
504         ssubcode->setValue(subcode);
505     }
506     if (msg) {
507         pair<bool,bool> flag = getBool("detailedErrors", shibspconstants::ASCII_SHIBSPCONFIG_NS);
508         auto_ptr_XMLCh widemsg((flag.first && flag.second) ? msg : "Error processing request.");
509         saml2p::StatusMessage* sm = saml2p::StatusMessageBuilder::buildStatusMessage();
510         status->setStatusMessage(sm);
511         sm->setMessage(widemsg.get());
512     }
513     response.setStatus(status);
514 }
515 
sendMessage(const MessageEncoder & encoder,XMLObject * msg,const char * relayState,const char * destination,const saml2md::RoleDescriptor * role,const Application & application,HTTPResponse & httpResponse,const char * defaultSigningProperty) const516 long AbstractHandler::sendMessage(
517     const MessageEncoder& encoder,
518     XMLObject* msg,
519     const char* relayState,
520     const char* destination,
521     const saml2md::RoleDescriptor* role,
522     const Application& application,
523     HTTPResponse& httpResponse,
524     const char* defaultSigningProperty
525     ) const
526 {
527     const EntityDescriptor* entity = role ? dynamic_cast<const EntityDescriptor*>(role->getParent()) : nullptr;
528     const PropertySet* relyingParty = application.getRelyingParty(entity);
529     pair<bool, const char*> flag = getString("signing");
530     if (!flag.first)
531         flag = getString("signing", shibspconstants::ASCII_SHIBSPCONFIG_NS);
532     if (!flag.first)
533         flag = relyingParty->getString("signing");
534     if (SPConfig::shouldSignOrEncrypt(flag.first ? flag.second : defaultSigningProperty, destination, encoder.isUserAgentPresent())) {
535         CredentialResolver* credResolver = application.getCredentialResolver();
536         if (credResolver) {
537             Locker credLocker(credResolver);
538             const Credential* cred = nullptr;
539             pair<bool,const char*> keyName = relyingParty->getString("keyName");
540             pair<bool,const XMLCh*> sigalg = relyingParty->getXMLString("signingAlg");
541             if (role) {
542                 MetadataCredentialCriteria mcc(*role);
543                 mcc.setUsage(Credential::SIGNING_CREDENTIAL);
544                 if (keyName.first)
545                     mcc.getKeyNames().insert(keyName.second);
546                 if (sigalg.first) {
547                     // Using an explicit algorithm, so resolve a credential directly.
548                     mcc.setXMLAlgorithm(sigalg.second);
549                     cred = credResolver->resolve(&mcc);
550                 }
551                 else {
552                     // Prefer credential based on peer's requirements.
553                     pair<const SigningMethod*,const Credential*> p = role->getSigningMethod(*credResolver, mcc);
554                     if (p.first)
555                         sigalg = make_pair(true, p.first->getAlgorithm());
556                     if (p.second)
557                         cred = p.second;
558                 }
559             }
560             else {
561                 CredentialCriteria cc;
562                 cc.setUsage(Credential::SIGNING_CREDENTIAL);
563                 if (keyName.first)
564                     cc.getKeyNames().insert(keyName.second);
565                 if (sigalg.first)
566                     cc.setXMLAlgorithm(sigalg.second);
567                 cred = credResolver->resolve(&cc);
568             }
569             if (cred) {
570                 // Signed request.
571                 pair<bool,const XMLCh*> digalg = relyingParty->getXMLString("digestAlg");
572                 if (!digalg.first && role) {
573                     const DigestMethod* dm = role->getDigestMethod();
574                     if (dm)
575                         digalg = make_pair(true, dm->getAlgorithm());
576                 }
577                 return encoder.encode(
578                     httpResponse,
579                     msg,
580                     destination,
581                     entity,
582                     relayState,
583                     &application,
584                     cred,
585                     sigalg.second,
586                     (digalg.first ? digalg.second : nullptr)
587                     );
588             }
589             else {
590                 m_log.warn("no signing credential resolved, leaving message unsigned");
591             }
592         }
593         else {
594             m_log.warn("no credential resolver installed, leaving message unsigned");
595         }
596     }
597 
598     // Unsigned request.
599     return encoder.encode(httpResponse, msg, destination, entity, relayState, &application);
600 }
601 
602 #endif
603 
preservePostData(const Application & application,const HTTPRequest & request,HTTPResponse & response,const char * relayState) const604 void AbstractHandler::preservePostData(
605     const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState
606     ) const
607 {
608 #ifdef HAVE_STRCASECMP
609     if (strcasecmp(request.getMethod(), "POST")) return;
610 #else
611     if (stricmp(request.getMethod(), "POST")) return;
612 #endif
613 
614     // No specs mean no save.
615     const PropertySet* props = application.getPropertySet("Sessions");
616     pair<bool,const char*> mech = props ? props->getString("postData") : pair<bool,const char*>(false,nullptr);
617     if (!mech.first) {
618         m_log.info("postData property not supplied, form data will not be preserved across SSO");
619         return;
620     }
621 
622     DDF postData = getPostData(application, request);
623     if (postData.isnull())
624         return;
625 
626     if (strstr(mech.second,"ss:") == mech.second) {
627         mech.second+=3;
628         if (!*mech.second) {
629             postData.destroy();
630             throw ConfigurationException("Unsupported postData mechanism ($1).", params(1, mech.second - 3));
631         }
632 
633         string postkey;
634         if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess)) {
635             DDFJanitor postjan(postData);
636 #ifndef SHIBSP_LITE
637             StorageService* storage = application.getServiceProvider().getStorageService(mech.second);
638             if (storage) {
639                 // Use a random key
640                 string rsKey;
641                 SAMLConfig::getConfig().generateRandomBytes(rsKey, 32);
642                 rsKey = SAMLArtifact::toHex(rsKey);
643                 ostringstream out;
644                 out << postData;
645                 if (!storage->createText("PostData", rsKey.c_str(), out.str().c_str(), time(nullptr) + 600))
646                     throw IOException("Attempted to insert duplicate storage key.");
647                 postkey = string(mech.second-3) + ':' + rsKey;
648             }
649             else {
650                 m_log.error("storage-backed PostData mechanism with invalid StorageService ID (%s)", mech.second);
651             }
652 #else
653             throw ConfigurationException("Lite version of library cannot be used out of process.");
654 #endif
655         }
656         else if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
657             DDF out,in = DDF("set::PostData").structure();
658             DDFJanitor jin(in),jout(out);
659             in.addmember("id").string(mech.second);
660             in.add(postData);
661             out = application.getServiceProvider().getListenerService()->send(in);
662             if (!out.isstring())
663                 throw IOException("StorageService-backed PostData mechanism did not return a state key.");
664             postkey = string(mech.second-3) + ':' + out.string();
665         }
666 
667         string shib_cookie = getPostCookieName(application, relayState);
668 
669         // Purge any cookies in excess of 25.
670         int maxCookies = 20,purgedCookies = 0;
671 
672         // Walk the list of cookies backwards by name.
673         const map<string,string>& cookies = request.getCookies();
674         for (map<string,string>::const_reverse_iterator i = cookies.rbegin(); i != cookies.rend(); ++i) {
675             // Process post data cookies only.
676             if (starts_with(i->first, "_shibpost_")) {
677                 if (maxCookies > 0) {
678                     // Keep it, but count it against the limit.
679                     --maxCookies;
680                 }
681                 else {
682                     // We're over the limit, so everything here and older gets cleaned up.
683                     response.setCookie(i->first.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
684                     ++purgedCookies;
685                 }
686             }
687         }
688 
689         if (purgedCookies > 0)
690             log(SPRequest::SPDebug, string("purged ") + lexical_cast<string>(purgedCookies) + " stale POST preservation cookie(s) from client");
691 
692         // Set a cookie with key info.
693         response.setCookie(shib_cookie.c_str(), postkey.c_str(), 0, HTTPResponse::SAMESITE_NONE);
694     }
695     else {
696         postData.destroy();
697         throw ConfigurationException("Unsupported postData mechanism ($1).", params(1,mech.second));
698     }
699 }
700 
recoverPostData(const Application & application,const HTTPRequest & request,HTTPResponse & response,const char * relayState) const701 DDF AbstractHandler::recoverPostData(
702     const Application& application, const HTTPRequest& request, HTTPResponse& response, const char* relayState
703     ) const
704 {
705     string shib_cookie = getPostCookieName(application, relayState);
706 
707     // First we need the post recovery cookie.
708     const char* cookie = request.getCookie(shib_cookie.c_str());
709     if (!cookie || !*cookie)
710         return DDF();
711 
712     // Clear the cookie.
713     response.setCookie(shib_cookie.c_str(), nullptr, 0, HTTPResponse::SAMESITE_NONE);
714 
715     // Look for StorageService-backed state of the form "ss:SSID:key".
716     const char* state = cookie;
717     if (strstr(state, "ss:") == state) {
718         state += 3;
719         const char* key = strchr(state, ':');
720         if (key) {
721             string ssid = string(cookie).substr(3, key - state);
722             key++;
723             if (!ssid.empty() && *key) {
724                 SPConfig& conf = SPConfig::getConfig();
725                 if (conf.isEnabled(SPConfig::OutOfProcess)) {
726 #ifndef SHIBSP_LITE
727                     StorageService* storage = conf.getServiceProvider()->getStorageService(ssid.c_str());
728                     if (storage) {
729                         if (storage->readText("PostData", key, &ssid) > 0) {
730                             storage->deleteText("PostData", key);
731                             istringstream inret(ssid);
732                             DDF ret;
733                             inret >> ret;
734                             return ret;
735                         }
736                         else {
737                             m_log.error("failed to recover form post data using key (%s)", key);
738                         }
739                     }
740                     else {
741                         m_log.error("storage-backed PostData with invalid StorageService ID (%s)", ssid.c_str());
742                     }
743 #endif
744                 }
745                 else if (conf.isEnabled(SPConfig::InProcess)) {
746                     DDF in = DDF("get::PostData").structure();
747                     DDFJanitor jin(in);
748                     in.addmember("id").string(ssid.c_str());
749                     in.addmember("key").string(key);
750                     DDF out = application.getServiceProvider().getListenerService()->send(in);
751                     if (out.islist())
752                         return out;
753                     out.destroy();
754                     m_log.error("storageService-backed PostData mechanism did not return preserved data.");
755                 }
756             }
757         }
758     }
759     return DDF();
760 }
761 
sendPostResponse(const Application & application,HTTPResponse & httpResponse,const char * url,DDF & postData) const762 long AbstractHandler::sendPostResponse(
763     const Application& application, HTTPResponse& httpResponse, const char* url, DDF& postData
764     ) const
765 {
766     HTTPResponse::sanitizeURL(url);
767 
768     const PropertySet* props=application.getPropertySet("Sessions");
769     pair<bool,const char*> postTemplate = props ? props->getString("postTemplate") : pair<bool,const char*>(true,nullptr);
770     if (!postTemplate.first)
771         postTemplate.second = "postTemplate.html";
772 
773     string fname(postTemplate.second);
774     ifstream infile(XMLToolingConfig::getConfig().getPathResolver()->resolve(fname, PathResolver::XMLTOOLING_CFG_FILE).c_str());
775     if (!infile)
776         throw ConfigurationException("Unable to access HTML template ($1).", params(1, fname.c_str()));
777     TemplateParameters respParam;
778     respParam.m_map["action"] = url;
779 
780     // Load the parameters into objects for the template.
781     multimap<string,string>& collection = respParam.m_collectionMap["PostedData"];
782     DDF param = postData.first();
783     while (!param.isnull()) {
784         collection.insert(pair<const string,string>(param.name(), (param.string() ? param.string() : "")));
785         param = postData.next();
786     }
787 
788     stringstream str;
789     XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, respParam);
790 
791     pair<bool,bool> postExpire = props ? props->getBool("postExpire") : make_pair(false,false);
792 
793     httpResponse.setContentType("text/html");
794     if (!postExpire.first || postExpire.second) {
795         httpResponse.setResponseHeader("Expires", "Wed, 01 Jan 1997 12:00:00 GMT");
796         httpResponse.setResponseHeader("Cache-Control", "no-cache, no-store, must-revalidate, private, max-age=0");
797         httpResponse.setResponseHeader("Pragma", "no-cache");
798     }
799     return httpResponse.sendResponse(str);
800 }
801 
getPostCookieName(const Application & app,const char * relayState) const802 string AbstractHandler::getPostCookieName(const Application& app, const char* relayState) const
803 {
804     // Decorates the name of the cookie with the relay state key, if any.
805     // Doing so gives a better assurance that the recovered data really
806     // belongs to the relayed request.
807     if (strstr(relayState, "cookie:") == relayState) {
808         return string("_shibpost_") + (relayState + 7);
809     }
810     else if (strstr(relayState, "ss:") == relayState) {
811         const char* pch = strchr(relayState + 3, ':');
812         if (pch)
813             return string("_shibpost_") + (pch + 1);
814     }
815     return app.getCookieName("_shibpost_");
816 }
817 
getPostData(const Application & application,const HTTPRequest & request) const818 DDF AbstractHandler::getPostData(const Application& application, const HTTPRequest& request) const
819 {
820     string contentType = request.getContentType();
821     if (contentType.find("application/x-www-form-urlencoded") != string::npos) {
822         const PropertySet* props = application.getPropertySet("Sessions");
823         pair<bool,unsigned int> plimit = props ? props->getUnsignedInt("postLimit") : pair<bool,unsigned int>(false,0);
824         if (!plimit.first)
825             plimit.second = 1024 * 1024;
826         if (plimit.second == 0 || request.getContentLength() <= plimit.second) {
827             CGIParser cgi(request);
828             pair<CGIParser::walker,CGIParser::walker> params = cgi.getParameters(nullptr);
829             if (params.first == params.second)
830                 return DDF("parameters").list();
831             DDF child;
832             DDF ret = DDF("parameters").list();
833             for (; params.first != params.second; ++params.first) {
834                 if (!params.first->first.empty()) {
835                     child = DDF(params.first->first.c_str()).unsafe_string(params.first->second);
836                     ret.add(child);
837                 }
838             }
839             return ret;
840         }
841         else {
842             m_log.warn("POST limit exceeded, ignoring %d bytes of posted data", request.getContentLength());
843         }
844     }
845     else {
846         m_log.info("ignoring POST data with non-standard encoding (%s)", contentType.c_str());
847     }
848     return DDF();
849 }
850 
getBool(const char * name,const HTTPRequest & request,unsigned int type) const851 pair<bool,bool> AbstractHandler::getBool(const char* name, const HTTPRequest& request, unsigned int type) const
852 {
853     if (type & HANDLER_PROPERTY_REQUEST) {
854         const char* param = request.getParameter(name);
855         if (param && *param)
856             return make_pair(true, (*param=='t' || *param=='1'));
857     }
858 
859     const SPRequest* sprequest = dynamic_cast<const SPRequest*>(&request);
860     if (sprequest && (type & HANDLER_PROPERTY_MAP)) {
861         pair<bool,bool> ret = sprequest->getRequestSettings().first->getBool(name);
862         if (ret.first)
863             return ret;
864     }
865 
866     if (type & HANDLER_PROPERTY_FIXED) {
867         return getBool(name);
868     }
869 
870     return make_pair(false,false);
871 }
872 
getString(const char * name,const HTTPRequest & request,unsigned int type) const873 pair<bool,const char*> AbstractHandler::getString(const char* name, const HTTPRequest& request, unsigned int type) const
874 {
875     if (type & HANDLER_PROPERTY_REQUEST) {
876         const char* param = request.getParameter(name);
877         if (param && *param)
878             return make_pair(true, param);
879     }
880 
881     const SPRequest* sprequest = dynamic_cast<const SPRequest*>(&request);
882     if (sprequest && (type & HANDLER_PROPERTY_MAP)) {
883         pair<bool,const char*> ret = sprequest->getRequestSettings().first->getString(name);
884         if (ret.first)
885             return ret;
886     }
887 
888     if (type & HANDLER_PROPERTY_FIXED) {
889         return getString(name);
890     }
891 
892     return pair<bool,const char*>(false,nullptr);
893 }
894 
getUnsignedInt(const char * name,const HTTPRequest & request,unsigned int type) const895 pair<bool,unsigned int> AbstractHandler::getUnsignedInt(const char* name, const HTTPRequest& request, unsigned int type) const
896 {
897     if (type & HANDLER_PROPERTY_REQUEST) {
898         const char* param = request.getParameter(name);
899         if (param && *param) {
900             try {
901                 return pair<bool,unsigned int>(true, lexical_cast<unsigned int>(param));
902             }
903             catch (bad_lexical_cast&) {
904                 return pair<bool,unsigned int>(false,0);
905             }
906         }
907     }
908 
909     const SPRequest* sprequest = dynamic_cast<const SPRequest*>(&request);
910     if (sprequest && (type & HANDLER_PROPERTY_MAP)) {
911         pair<bool,unsigned int> ret = sprequest->getRequestSettings().first->getUnsignedInt(name);
912         if (ret.first)
913             return ret;
914     }
915 
916     if (type & HANDLER_PROPERTY_FIXED) {
917         return getUnsignedInt(name);
918     }
919 
920     return pair<bool,unsigned int>(false,0);
921 }
922 
getInt(const char * name,const HTTPRequest & request,unsigned int type) const923 pair<bool,int> AbstractHandler::getInt(const char* name, const HTTPRequest& request, unsigned int type) const
924 {
925     if (type & HANDLER_PROPERTY_REQUEST) {
926         const char* param = request.getParameter(name);
927         if (param && *param)
928             return pair<bool,int>(true, atoi(param));
929     }
930 
931     const SPRequest* sprequest = dynamic_cast<const SPRequest*>(&request);
932     if (sprequest && (type & HANDLER_PROPERTY_MAP)) {
933         pair<bool,int> ret = sprequest->getRequestSettings().first->getInt(name);
934         if (ret.first)
935             return ret;
936     }
937 
938     if (type & HANDLER_PROPERTY_FIXED) {
939         return getInt(name);
940     }
941 
942     return pair<bool,int>(false,0);
943 }
944