SSPCPP-616 - clean up concatenated string literals
[shibboleth/cpp-sp.git] / shibsp / handler / impl / WAYFSessionInitiator.cpp
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  * WAYFSessionInitiator.cpp
23  * 
24  * Shibboleth WAYF support.
25  */
26
27 #include "internal.h"
28 #include "Application.h"
29 #include "exceptions.h"
30 #include "SPRequest.h"
31 #include "handler/AbstractHandler.h"
32 #include "handler/SessionInitiator.h"
33
34 #ifndef SHIBSP_LITE
35 # include <saml/util/SAMLConstants.h>
36 #else
37 # include "lite/SAMLConstants.h"
38 #endif
39
40 #include <ctime>
41 #include <boost/lexical_cast.hpp>
42 #include <xmltooling/XMLToolingConfig.h>
43 #include <xmltooling/util/URLEncoder.h>
44
45 using namespace shibsp;
46 using namespace opensaml;
47 using namespace xmltooling;
48 using namespace boost;
49 using namespace std;
50
51 namespace shibsp {
52
53 #if defined (_MSC_VER)
54     #pragma warning( push )
55     #pragma warning( disable : 4250 )
56 #endif
57
58     class SHIBSP_DLLLOCAL WAYFSessionInitiator : public SessionInitiator, public AbstractHandler
59     {
60     public:
61         WAYFSessionInitiator(const DOMElement* e, const char* appId)
62                 : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT ".SessionInitiator.WAYF"), nullptr, &m_remapper), m_url(nullptr) {
63             pair<bool,const char*> url = getString("URL");
64             if (!url.first)
65                 throw ConfigurationException("WAYF SessionInitiator requires a URL property.");
66             m_url = url.second;
67         }
68         virtual ~WAYFSessionInitiator() {}
69         
70         pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
71
72         const XMLCh* getProtocolFamily() const {
73             return samlconstants::SAML11_PROTOCOL_ENUM;
74         }
75
76     private:
77         const char* m_url;
78     };
79
80 #if defined (_MSC_VER)
81     #pragma warning( pop )
82 #endif
83
84     SessionInitiator* SHIBSP_DLLLOCAL WAYFSessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
85     {
86         return new WAYFSessionInitiator(p.first, p.second);
87     }
88
89 };
90
91 pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, string& entityID, bool isHandler) const
92 {
93     // The IdP CANNOT be specified for us to run. Otherwise, we'd be redirecting to a WAYF
94     // anytime the IdP's metadata was wrong.
95     if (!entityID.empty() || !checkCompatibility(request, isHandler))
96         return make_pair(false, 0L);
97
98     string target;
99     pair<bool,const char*> prop;
100     const Handler* ACS = nullptr;
101     const Application& app = request.getApplication();
102     pair<bool,const char*> discoveryURL = pair<bool,const char*>(true, m_url);
103
104     if (isHandler) {
105         prop.second = request.getParameter("acsIndex");
106         if (prop.second && *prop.second) {
107             ACS = app.getAssertionConsumerServiceByIndex(atoi(prop.second));
108             if (!ACS)
109                 request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using acsIndex property");
110         }
111
112         prop = getString("target", request);
113         if (prop.first)
114             target = prop.second;
115
116         // Since we're passing the ACS by value, we need to compute the return URL,
117         // so we'll need the target resource for real.
118         recoverRelayState(request.getApplication(), request, request, target, false);
119         request.getApplication().limitRedirect(request, target.c_str());
120
121         prop.second = request.getParameter("discoveryURL");
122         if (prop.second && *prop.second)
123             discoveryURL.second = prop.second;
124     }
125     else {
126         // Check for a hardwired target value in the map or handler.
127         prop = getString("target", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
128         if (prop.first)
129             target = prop.second;
130         else
131             target = request.getRequestURL();
132
133         discoveryURL = request.getRequestSettings().first->getString("discoveryURL");
134     }
135     
136     if (!ACS) {
137         // Try fixed index property.
138         pair<bool,unsigned int> index = getUnsignedInt("acsIndex", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
139         if (index.first)
140             ACS = app.getAssertionConsumerServiceByIndex(index.second);
141     }
142
143     // If we picked by index, validate the ACS for use with this protocol.
144     if (!ACS || !XMLString::equals(samlconstants::SAML11_PROTOCOL_ENUM, ACS->getProtocolFamily())) {
145         if (ACS)
146             request.log(SPRequest::SPWarn, "invalid acsIndex property, or non-SAML 1.x ACS, using default SAML 1.x ACS");
147         ACS = app.getAssertionConsumerServiceByProtocol(getProtocolFamily());
148         if (!ACS)
149             throw ConfigurationException("Unable to locate a SAML 1.x ACS endpoint to use for response.");
150     }
151
152     if (!discoveryURL.first)
153         discoveryURL.second = m_url;
154     m_log.debug("sending request to WAYF (%s)", discoveryURL.second);
155
156     // Since we're not passing by index, we need to fully compute the return URL.
157     // Compute the ACS URL. We add the ACS location to the base handlerURL.
158     string ACSloc = request.getHandlerURL(target.c_str());
159     prop = ACS->getString("Location");
160     if (prop.first)
161         ACSloc += prop.second;
162
163     if (isHandler) {
164         // We may already have RelayState set if we looped back here,
165         // but we've turned it back into a resource by this point, so if there's
166         // a target on the URL, reset to that value.
167         prop.second = request.getParameter("target");
168         if (prop.second && *prop.second)
169             target = prop.second;
170     }
171
172     preserveRelayState(app, request, target);
173     if (!isHandler)
174         preservePostData(app, request, request, target.c_str());
175
176     // WAYF requires a target value.
177     if (target.empty())
178         target = "default";
179
180     const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
181     string req=string(discoveryURL.second) + (strchr(discoveryURL.second,'?') ? '&' : '?') + "shire=" + urlenc->encode(ACSloc.c_str()) +
182         "&time=" + lexical_cast<string>(time(nullptr)) + "&target=" + urlenc->encode(target.c_str()) +
183         "&providerId=" + urlenc->encode(app.getString("entityID").second);
184
185     return make_pair(true, request.sendRedirect(req.c_str()));
186 }