https://issues.shibboleth.net/jira/browse/SSPCPP-229
[shibboleth/cpp-sp.git] / shibsp / handler / impl / WAYFSessionInitiator.cpp
1 /*
2  *  Copyright 2001-2009 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 #include <ctime>
31 #include <xmltooling/XMLToolingConfig.h>
32 #include <xmltooling/util/URLEncoder.h>
33
34 using namespace shibsp;
35 using namespace opensaml;
36 using namespace xmltooling;
37 using namespace std;
38
39 namespace shibsp {
40
41 #if defined (_MSC_VER)
42     #pragma warning( push )
43     #pragma warning( disable : 4250 )
44 #endif
45
46     class SHIBSP_DLLLOCAL WAYFSessionInitiator : public SessionInitiator, public AbstractHandler
47     {
48     public:
49         WAYFSessionInitiator(const DOMElement* e, const char* appId)
50                 : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.WAYF"), NULL, &m_remapper), m_url(NULL) {
51             pair<bool,const char*> url = getString("URL");
52             if (!url.first)
53                 throw ConfigurationException("WAYF SessionInitiator requires a URL property.");
54             m_url = url.second;
55         }
56         virtual ~WAYFSessionInitiator() {}
57         
58         pair<bool,long> run(SPRequest& request, string& entityID, bool isHandler=true) const;
59
60     private:
61         const char* m_url;
62     };
63
64 #if defined (_MSC_VER)
65     #pragma warning( pop )
66 #endif
67
68     SessionInitiator* SHIBSP_DLLLOCAL WAYFSessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
69     {
70         return new WAYFSessionInitiator(p.first, p.second);
71     }
72
73 };
74
75 pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, string& entityID, bool isHandler) const
76 {
77     // The IdP CANNOT be specified for us to run. Otherwise, we'd be redirecting to a WAYF
78     // anytime the IdP's metadata was wrong.
79     if (!entityID.empty())
80         return make_pair(false,0L);
81
82     string target;
83     string postData;
84     const char* option;
85     const Handler* ACS=NULL;
86     const Application& app=request.getApplication();
87     pair<bool,const char*> discoveryURL = pair<bool,const char*>(true, m_url);
88
89     if (isHandler) {
90         option=request.getParameter("acsIndex");
91         if (option) {
92             ACS=app.getAssertionConsumerServiceByIndex(atoi(option));
93             if (!ACS)
94                 request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using acsIndex property");
95         }
96
97         option = request.getParameter("target");
98         if (option)
99             target = option;
100         recoverRelayState(request.getApplication(), request, request, target, false);
101
102         option = request.getParameter("discoveryURL");
103         if (option)
104             discoveryURL.second = option;
105     }
106     else {
107         // We're running as a "virtual handler" from within the filter.
108         // The target resource is the current one and everything else is defaulted.
109         target=request.getRequestURL();
110         discoveryURL = request.getRequestSettings().first->getString("discoveryURL");
111     }
112     
113     // Since we're not passing by index, we need to fully compute the return URL.
114     if (!ACS) {
115         pair<bool,unsigned int> index = getUnsignedInt("acsIndex");
116         if (index.first) {
117             ACS = app.getAssertionConsumerServiceByIndex(index.second);
118             if (!ACS)
119                 request.log(SPRequest::SPWarn, "invalid acsIndex property, using default ACS location");
120         }
121         if (!ACS)
122             ACS = app.getDefaultAssertionConsumerService();
123     }
124
125     // Validate the ACS for use with this protocol.
126     pair<bool,const char*> ACSbinding = ACS ? ACS->getString("Binding") : pair<bool,const char*>(false,NULL);
127     if (ACSbinding.first) {
128         pair<bool,const char*> compatibleBindings = getString("compatibleBindings");
129         if (compatibleBindings.first && strstr(compatibleBindings.second, ACSbinding.second) == NULL) {
130             m_log.info("configured or requested ACS has non-SAML 1.x binding");
131             return make_pair(false,0L);
132         }
133         else if (strcmp(ACSbinding.second, samlconstants::SAML1_PROFILE_BROWSER_POST) &&
134                  strcmp(ACSbinding.second, samlconstants::SAML1_PROFILE_BROWSER_ARTIFACT)) {
135             m_log.info("configured or requested ACS has non-SAML 1.x binding");
136             return make_pair(false,0L);
137         }
138     }
139
140     if (!discoveryURL.first)
141         discoveryURL.second = m_url;
142     m_log.debug("sending request to WAYF (%s)", discoveryURL.second);
143
144     // Compute the ACS URL. We add the ACS location to the base handlerURL.
145     string ACSloc=request.getHandlerURL(target.c_str());
146     pair<bool,const char*> loc=ACS ? ACS->getString("Location") : pair<bool,const char*>(false,NULL);
147     if (loc.first) ACSloc+=loc.second;
148
149     if (isHandler) {
150         // We may already have RelayState set if we looped back here,
151         // but just in case target is a resource, we reset it back.
152         option = request.getParameter("target");
153         if (option)
154             target = option;
155     }
156     preserveRelayState(request.getApplication(), request, target);
157     if (!isHandler)
158         preservePostData(request.getApplication(), request, request, target.c_str());
159
160     // WAYF requires a target value.
161     if (target.empty())
162         target = "default";
163
164     char timebuf[16];
165     sprintf(timebuf,"%lu",time(NULL));
166     const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
167     string req=string(discoveryURL.second) + (strchr(discoveryURL.second,'?') ? '&' : '?') + "shire=" + urlenc->encode(ACSloc.c_str()) +
168         "&time=" + timebuf + "&target=" + urlenc->encode(target.c_str()) +
169         "&providerId=" + urlenc->encode(app.getString("entityID").second);
170
171     return make_pair(true, request.sendRedirect(req.c_str()));
172 }