Merge in updated Apache POST body code.
[shibboleth/sp.git] / shibsp / handler / impl / WAYFSessionInitiator.cpp
1 /*
2  *  Copyright 2001-2007 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 log4cpp;
38 using namespace std;
39
40 namespace shibsp {
41
42 #if defined (_MSC_VER)
43     #pragma warning( push )
44     #pragma warning( disable : 4250 )
45 #endif
46
47     class SHIBSP_DLLLOCAL WAYFSessionInitiator : public SessionInitiator, public AbstractHandler
48     {
49     public:
50         WAYFSessionInitiator(const DOMElement* e, const char* appId)
51                 : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator")), m_url(NULL) {
52             pair<bool,const char*> url = getString("URL");
53             if (!url.first)
54                 throw ConfigurationException("WAYF SessionInitiator requires a URL property.");
55             m_url = url.second;
56         }
57         virtual ~WAYFSessionInitiator() {}
58         
59         pair<bool,long> run(SPRequest& request, const char* entityID=NULL, bool isHandler=true) const;
60
61     private:
62         const char* m_url;
63     };
64
65 #if defined (_MSC_VER)
66     #pragma warning( pop )
67 #endif
68
69     SessionInitiator* SHIBSP_DLLLOCAL WAYFSessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
70     {
71         return new WAYFSessionInitiator(p.first, p.second);
72     }
73
74 };
75
76 pair<bool,long> WAYFSessionInitiator::run(SPRequest& request, const char* entityID, bool isHandler) const
77 {
78     // The IdP CANNOT be specified for us to run. Otherwise, we'd be redirecting to a WAYF
79     // anytime the IdP's metadata was wrong.
80     if (entityID && *entityID)
81         return make_pair(false,0);
82
83     string target;
84     const char* option;
85     const Handler* ACS=NULL;
86     const Application& app=request.getApplication();
87
88     if (isHandler) {
89         option=request.getParameter("acsIndex");
90         if (option) {
91             ACS=app.getAssertionConsumerServiceByIndex(atoi(option));
92             if (!ACS)
93                 request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using default ACS location");
94         }
95
96         option = request.getParameter("target");
97         if (option)
98             target = option;
99         recoverRelayState(request.getApplication(), request, target, false);
100     }
101     else {
102         // We're running as a "virtual handler" from within the filter.
103         // The target resource is the current one and everything else is defaulted.
104         target=request.getRequestURL();
105     }
106     
107     // Since we're not passing by index, we need to fully compute the return URL.
108     if (!ACS) {
109         pair<bool,unsigned int> index = getUnsignedInt("defaultACSIndex");
110         if (index.first) {
111             ACS = app.getAssertionConsumerServiceByIndex(index.second);
112             if (!ACS)
113                 request.log(SPRequest::SPWarn, "invalid defaultACSIndex, using default ACS location");
114         }
115         if (!ACS)
116             ACS = app.getDefaultAssertionConsumerService();
117     }
118
119     m_log.debug("sending request to WAYF (%s)", m_url);
120
121     // Compute the ACS URL. We add the ACS location to the base handlerURL.
122     string ACSloc=request.getHandlerURL(target.c_str());
123     pair<bool,const char*> loc=ACS ? ACS->getString("Location") : pair<bool,const char*>(false,NULL);
124     if (loc.first) ACSloc+=loc.second;
125
126     if (isHandler) {
127         // We may already have RelayState set if we looped back here,
128         // but just in case target is a resource, we reset it back.
129         option = request.getParameter("target");
130         if (option)
131             target = option;
132     }
133     preserveRelayState(request.getApplication(), request, target);
134
135     // WAYF requires a target value.
136     if (target.empty())
137         target = "default";
138
139     char timebuf[16];
140     sprintf(timebuf,"%u",time(NULL));
141     const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
142     string req=string(m_url) + (strchr(m_url,'?') ? '&' : '?') + "shire=" + urlenc->encode(ACSloc.c_str()) +
143         "&time=" + timebuf + "&target=" + urlenc->encode(target.c_str()) +
144         "&providerId=" + urlenc->encode(app.getString("entityID").second);
145
146     return make_pair(true, request.sendRedirect(req.c_str()));
147 }