Factor out LogoutInitiator class, simpler API to get ACS by binding.
[shibboleth/sp.git] / shibsp / handler / impl / WAYFSessionInitiator.cpp
1 /*
2  *  Copyright 2001-2010 Internet2
3  * 
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /**
18  * WAYFSessionInitiator.cpp
19  * 
20  * Shibboleth WAYF support.
21  */
22
23 #include "internal.h"
24 #include "Application.h"
25 #include "exceptions.h"
26 #include "SPRequest.h"
27 #include "handler/AbstractHandler.h"
28 #include "handler/SessionInitiator.h"
29
30 #ifndef SHIBSP_LITE
31 # include <saml/util/SAMLConstants.h>
32 #else
33 # include "lite/SAMLConstants.h"
34 #endif
35
36 #include <ctime>
37 #include <xmltooling/XMLToolingConfig.h>
38 #include <xmltooling/util/URLEncoder.h>
39
40 using namespace shibsp;
41 using namespace opensaml;
42 using namespace xmltooling;
43 using namespace std;
44
45 namespace shibsp {
46
47 #if defined (_MSC_VER)
48     #pragma warning( push )
49     #pragma warning( disable : 4250 )
50 #endif
51
52     class SHIBSP_DLLLOCAL WAYFSessionInitiator : public SessionInitiator, public AbstractHandler
53     {
54     public:
55         WAYFSessionInitiator(const DOMElement* e, const char* appId)
56                 : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.WAYF"), nullptr, &m_remapper), m_url(nullptr) {
57             pair<bool,const char*> url = getString("URL");
58             if (!url.first)
59                 throw ConfigurationException("WAYF SessionInitiator requires a URL property.");
60             m_url = url.second;
61         }
62         virtual ~WAYFSessionInitiator() {}
63         
64         pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
65
66     private:
67         const char* m_url;
68     };
69
70 #if defined (_MSC_VER)
71     #pragma warning( pop )
72 #endif
73
74     SessionInitiator* SHIBSP_DLLLOCAL WAYFSessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
75     {
76         return new WAYFSessionInitiator(p.first, p.second);
77     }
78
79 };
80
81 pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, string& entityID, bool isHandler) const
82 {
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);
87
88     string target;
89     pair<bool,const char*> prop;
90     const Handler* ACS=nullptr;
91     const Application& app=request.getApplication();
92     pair<bool,const char*> discoveryURL = pair<bool,const char*>(true, m_url);
93
94     if (isHandler) {
95         prop.second = request.getParameter("acsIndex");
96         if (prop.second && *prop.second) {
97             ACS = app.getAssertionConsumerServiceByIndex(atoi(prop.second));
98             if (!ACS)
99                 request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using acsIndex property");
100         }
101
102         prop = getString("target", request);
103         if (prop.first)
104             target = prop.second;
105
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);
109
110         prop.second = request.getParameter("discoveryURL");
111         if (prop.second && *prop.second)
112             discoveryURL.second = prop.second;
113     }
114     else {
115         // Check for a hardwired target value in the map or handler.
116         prop = getString("target", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
117         if (prop.first)
118             target = prop.second;
119         else
120             target = request.getRequestURL();
121
122         discoveryURL = request.getRequestSettings().first->getString("discoveryURL");
123     }
124     
125     // Since we're not passing by index, we need to fully compute the return URL.
126     if (!ACS) {
127         // Try fixed index property, or incoming binding set, or default, in order.
128         pair<bool,unsigned int> index = getUnsignedInt("acsIndex", request, HANDLER_PROPERTY_MAP|HANDLER_PROPERTY_FIXED);
129         if (index.first) {
130             ACS = app.getAssertionConsumerServiceByIndex(index.second);
131             if (!ACS)
132                 request.log(SPRequest::SPWarn, "invalid acsIndex property, using default ACS location");
133         }
134         /*
135         for (vector<string>::const_iterator b = m_incomingBindings.begin(); !ACS && b != m_incomingBindings.end(); ++b) {
136             ACS = app.getAssertionConsumerServiceByBinding(b->c_str());
137             if (ACS && !XMLString::equals(getProtocolFamily(), ACS->getProtocolFamily()))
138                 ACS = nullptr;
139         }
140         */
141         if (!ACS)
142             ACS = app.getDefaultAssertionConsumerService();
143     }
144
145     // Validate the ACS for use with this protocol.
146     if (ACS && !XMLString::equals(samlconstants::SAML11_PROTOCOL_ENUM, ACS->getProtocolFamily())) {
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, ACS->getString("Binding").second));
149     }
150
151     if (!discoveryURL.first)
152         discoveryURL.second = m_url;
153     m_log.debug("sending request to WAYF (%s)", discoveryURL.second);
154
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 ? ACS->getString("Location") : pair<bool,const char*>(false,nullptr);
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 }