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