Change license header.
[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 <xmltooling/XMLToolingConfig.h>
42 #include <xmltooling/util/URLEncoder.h>
43
44 using namespace shibsp;
45 using namespace opensaml;
46 using namespace xmltooling;
47 using namespace std;
48
49 namespace shibsp {
50
51 #if defined (_MSC_VER)
52     #pragma warning( push )
53     #pragma warning( disable : 4250 )
54 #endif
55
56     class SHIBSP_DLLLOCAL WAYFSessionInitiator : public SessionInitiator, public AbstractHandler
57     {
58     public:
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");
62             if (!url.first)
63                 throw ConfigurationException("WAYF SessionInitiator requires a URL property.");
64             m_url = url.second;
65         }
66         virtual ~WAYFSessionInitiator() {}
67         
68         pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
69
70         const XMLCh* getProtocolFamily() const {
71             return samlconstants::SAML11_PROTOCOL_ENUM;
72         }
73
74     private:
75         const char* m_url;
76     };
77
78 #if defined (_MSC_VER)
79     #pragma warning( pop )
80 #endif
81
82     SessionInitiator* SHIBSP_DLLLOCAL WAYFSessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
83     {
84         return new WAYFSessionInitiator(p.first, p.second);
85     }
86
87 };
88
89 pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, string& entityID, bool isHandler) const
90 {
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);
95
96     string target;
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);
101
102     if (isHandler) {
103         prop.second = request.getParameter("acsIndex");
104         if (prop.second && *prop.second) {
105             ACS = app.getAssertionConsumerServiceByIndex(atoi(prop.second));
106             if (!ACS)
107                 request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using acsIndex property");
108         }
109
110         prop = getString("target", request);
111         if (prop.first)
112             target = prop.second;
113
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());
118
119         prop.second = request.getParameter("discoveryURL");
120         if (prop.second && *prop.second)
121             discoveryURL.second = prop.second;
122     }
123     else {
124         // Check for a hardwired target value in the map or handler.
125         prop = getString("target", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
126         if (prop.first)
127             target = prop.second;
128         else
129             target = request.getRequestURL();
130
131         discoveryURL = request.getRequestSettings().first->getString("discoveryURL");
132     }
133     
134     if (!ACS) {
135         // Try fixed index property.
136         pair<bool,unsigned int> index = getUnsignedInt("acsIndex", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
137         if (index.first)
138             ACS = app.getAssertionConsumerServiceByIndex(index.second);
139     }
140
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())) {
143         if (ACS)
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());
146         if (!ACS)
147             throw ConfigurationException("Unable to locate a SAML 1.x ACS endpoint to use for response.");
148     }
149
150     if (!discoveryURL.first)
151         discoveryURL.second = m_url;
152     m_log.debug("sending request to WAYF (%s)", discoveryURL.second);
153
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");
158     if (prop.first)
159         ACSloc += prop.second;
160
161     if (isHandler) {
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;
168     }
169
170     preserveRelayState(app, request, target);
171     if (!isHandler)
172         preservePostData(app, request, request, target.c_str());
173
174     // WAYF requires a target value.
175     if (target.empty())
176         target = "default";
177
178     char timebuf[16];
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);
184
185     return make_pair(true, request.sendRedirect(req.c_str()));
186 }