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.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
22 * WAYFSessionInitiator.cpp
24 * Shibboleth WAYF support.
28 #include "Application.h"
29 #include "exceptions.h"
30 #include "SPRequest.h"
31 #include "handler/AbstractHandler.h"
32 #include "handler/SessionInitiator.h"
35 # include <saml/util/SAMLConstants.h>
37 # include "lite/SAMLConstants.h"
41 #include <xmltooling/XMLToolingConfig.h>
42 #include <xmltooling/util/URLEncoder.h>
44 using namespace shibsp;
45 using namespace opensaml;
46 using namespace xmltooling;
51 #if defined (_MSC_VER)
52 #pragma warning( push )
53 #pragma warning( disable : 4250 )
56 class SHIBSP_DLLLOCAL WAYFSessionInitiator : public SessionInitiator, public AbstractHandler
59 WAYFSessionInitiator(const DOMElement* e, const char* appId)
60 : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.WAYF"), nullptr, &m_remapper), m_url(nullptr) {
61 pair<bool,const char*> url = getString("URL");
63 throw ConfigurationException("WAYF SessionInitiator requires a URL property.");
66 virtual ~WAYFSessionInitiator() {}
68 pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
70 const XMLCh* getProtocolFamily() const {
71 return samlconstants::SAML11_PROTOCOL_ENUM;
78 #if defined (_MSC_VER)
79 #pragma warning( pop )
82 SessionInitiator* SHIBSP_DLLLOCAL WAYFSessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
84 return new WAYFSessionInitiator(p.first, p.second);
89 pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, string& entityID, bool isHandler) const
91 // The IdP CANNOT be specified for us to run. Otherwise, we'd be redirecting to a WAYF
92 // anytime the IdP's metadata was wrong.
93 if (!entityID.empty() || !checkCompatibility(request, isHandler))
94 return make_pair(false,0L);
97 pair<bool,const char*> prop;
98 const Handler* ACS=nullptr;
99 const Application& app=request.getApplication();
100 pair<bool,const char*> discoveryURL = pair<bool,const char*>(true, m_url);
103 prop.second = request.getParameter("acsIndex");
104 if (prop.second && *prop.second) {
105 ACS = app.getAssertionConsumerServiceByIndex(atoi(prop.second));
107 request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using acsIndex property");
110 prop = getString("target", request);
112 target = prop.second;
114 // Since we're passing the ACS by value, we need to compute the return URL,
115 // so we'll need the target resource for real.
116 recoverRelayState(request.getApplication(), request, request, target, false);
117 limitRelayState(m_log, request.getApplication(), request, target.c_str());
119 prop.second = request.getParameter("discoveryURL");
120 if (prop.second && *prop.second)
121 discoveryURL.second = prop.second;
124 // Check for a hardwired target value in the map or handler.
125 prop = getString("target", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
127 target = prop.second;
129 target = request.getRequestURL();
131 discoveryURL = request.getRequestSettings().first->getString("discoveryURL");
135 // Try fixed index property.
136 pair<bool,unsigned int> index = getUnsignedInt("acsIndex", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
138 ACS = app.getAssertionConsumerServiceByIndex(index.second);
141 // If we picked by index, validate the ACS for use with this protocol.
142 if (!ACS || !XMLString::equals(samlconstants::SAML11_PROTOCOL_ENUM, ACS->getProtocolFamily())) {
144 request.log(SPRequest::SPWarn, "invalid acsIndex property, or non-SAML 1.x ACS, using default SAML 1.x ACS");
145 ACS = app.getAssertionConsumerServiceByProtocol(getProtocolFamily());
147 throw ConfigurationException("Unable to locate a SAML 1.x ACS endpoint to use for response.");
150 if (!discoveryURL.first)
151 discoveryURL.second = m_url;
152 m_log.debug("sending request to WAYF (%s)", discoveryURL.second);
154 // Since we're not passing by index, we need to fully compute the return URL.
155 // Compute the ACS URL. We add the ACS location to the base handlerURL.
156 string ACSloc = request.getHandlerURL(target.c_str());
157 prop = ACS->getString("Location");
159 ACSloc += prop.second;
162 // We may already have RelayState set if we looped back here,
163 // but we've turned it back into a resource by this point, so if there's
164 // a target on the URL, reset to that value.
165 prop.second = request.getParameter("target");
166 if (prop.second && *prop.second)
167 target = prop.second;
170 preserveRelayState(app, request, target);
172 preservePostData(app, request, request, target.c_str());
174 // WAYF requires a target value.
179 sprintf(timebuf,"%lu",time(nullptr));
180 const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
181 string req=string(discoveryURL.second) + (strchr(discoveryURL.second,'?') ? '&' : '?') + "shire=" + urlenc->encode(ACSloc.c_str()) +
182 "&time=" + timebuf + "&target=" + urlenc->encode(target.c_str()) +
183 "&providerId=" + urlenc->encode(app.getString("entityID").second);
185 return make_pair(true, request.sendRedirect(req.c_str()));