2 * Copyright 2001-2010 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * WAYFSessionInitiator.cpp
20 * Shibboleth WAYF support.
24 #include "Application.h"
25 #include "exceptions.h"
26 #include "SPRequest.h"
27 #include "handler/AbstractHandler.h"
28 #include "handler/SessionInitiator.h"
31 # include <saml/util/SAMLConstants.h>
33 # include "lite/SAMLConstants.h"
37 #include <xmltooling/XMLToolingConfig.h>
38 #include <xmltooling/util/URLEncoder.h>
40 using namespace shibsp;
41 using namespace opensaml;
42 using namespace xmltooling;
47 #if defined (_MSC_VER)
48 #pragma warning( push )
49 #pragma warning( disable : 4250 )
52 class SHIBSP_DLLLOCAL WAYFSessionInitiator : public SessionInitiator, public AbstractHandler
55 WAYFSessionInitiator(const DOMElement* e, const char* appId)
56 : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.WAYF"), NULL, &m_remapper), m_url(NULL) {
57 pair<bool,const char*> url = getString("URL");
59 throw ConfigurationException("WAYF SessionInitiator requires a URL property.");
62 virtual ~WAYFSessionInitiator() {}
64 pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
70 #if defined (_MSC_VER)
71 #pragma warning( pop )
74 SessionInitiator* SHIBSP_DLLLOCAL WAYFSessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
76 return new WAYFSessionInitiator(p.first, p.second);
81 pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, string& entityID, bool isHandler) const
83 // The IdP CANNOT be specified for us to run. Otherwise, we'd be redirecting to a WAYF
84 // anytime the IdP's metadata was wrong.
85 if (!entityID.empty() || !checkCompatibility(request, isHandler))
86 return make_pair(false,0L);
89 pair<bool,const char*> prop;
90 const Handler* ACS=NULL;
91 const Application& app=request.getApplication();
92 pair<bool,const char*> discoveryURL = pair<bool,const char*>(true, m_url);
95 prop.second = request.getParameter("acsIndex");
96 if (prop.second && *prop.second) {
97 ACS = app.getAssertionConsumerServiceByIndex(atoi(prop.second));
99 request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using acsIndex property");
102 prop = getString("target", request);
104 target = prop.second;
106 // Since we're passing the ACS by value, we need to compute the return URL,
107 // so we'll need the target resource for real.
108 recoverRelayState(request.getApplication(), request, request, target, false);
110 prop.second = request.getParameter("discoveryURL");
111 if (prop.second && *prop.second)
112 discoveryURL.second = prop.second;
115 // Check for a hardwired target value in the map or handler.
116 prop = getString("target", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
118 target = prop.second;
120 target = request.getRequestURL();
122 discoveryURL = request.getRequestSettings().first->getString("discoveryURL");
125 // Since we're not passing by index, we need to fully compute the return URL.
127 pair<bool,unsigned int> index = getUnsignedInt("acsIndex", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
129 ACS = app.getAssertionConsumerServiceByIndex(index.second);
131 request.log(SPRequest::SPWarn, "invalid acsIndex property, using default ACS location");
134 ACS = app.getDefaultAssertionConsumerService();
137 // Validate the ACS for use with this protocol.
138 pair<bool,const char*> ACSbinding = ACS ? ACS->getString("Binding") : pair<bool,const char*>(false,NULL);
139 if (ACSbinding.first) {
140 pair<bool,const char*> compatibleBindings = getString("compatibleBindings");
141 if (compatibleBindings.first && strstr(compatibleBindings.second, ACSbinding.second) == NULL) {
142 m_log.error("configured or requested ACS has non-SAML 1.x binding");
143 throw ConfigurationException("Configured or requested ACS has non-SAML 1.x binding ($1).", params(1, ACSbinding.second));
145 else if (strcmp(ACSbinding.second, samlconstants::SAML1_PROFILE_BROWSER_POST) &&
146 strcmp(ACSbinding.second, samlconstants::SAML1_PROFILE_BROWSER_ARTIFACT)) {
147 m_log.error("configured or requested ACS has non-SAML 1.x binding");
148 throw ConfigurationException("Configured or requested ACS has non-SAML 1.x binding ($1).", params(1, ACSbinding.second));
152 if (!discoveryURL.first)
153 discoveryURL.second = m_url;
154 m_log.debug("sending request to WAYF (%s)", discoveryURL.second);
156 // Compute the ACS URL. We add the ACS location to the base handlerURL.
157 string ACSloc = request.getHandlerURL(target.c_str());
158 prop = ACS ? ACS->getString("Location") : pair<bool,const char*>(false,NULL);
160 ACSloc += prop.second;
163 // We may already have RelayState set if we looped back here,
164 // but we've turned it back into a resource by this point, so if there's
165 // a target on the URL, reset to that value.
166 prop.second = request.getParameter("target");
167 if (prop.second && *prop.second)
168 target = prop.second;
171 preserveRelayState(app, request, target);
173 preservePostData(app, request, request, target.c_str());
175 // WAYF requires a target value.
180 sprintf(timebuf,"%lu",time(NULL));
181 const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
182 string req=string(discoveryURL.second) + (strchr(discoveryURL.second,'?') ? '&' : '?') + "shire=" + urlenc->encode(ACSloc.c_str()) +
183 "&time=" + timebuf + "&target=" + urlenc->encode(target.c_str()) +
184 "&providerId=" + urlenc->encode(app.getString("entityID").second);
186 return make_pair(true, request.sendRedirect(req.c_str()));