90484536083a3355aca2b5a89a39b42f8850e764
[shibboleth/sp.git] / shibsp / handler / impl / AbstractHandler.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  * AbstractHandler.cpp
19  * 
20  * Base class for handlers based on a DOMPropertySet. 
21  */
22
23 #include "internal.h"
24 #include "Application.h"
25 #include "exceptions.h"
26 #include "ServiceProvider.h"
27 #include "SPRequest.h"
28 #include "handler/AbstractHandler.h"
29 #include "remoting/ListenerService.h"
30
31 #include <log4cpp/Category.hh>
32 #include <saml/saml1/core/Protocols.h>
33 #include <saml/saml2/core/Protocols.h>
34 #include <saml/util/SAMLConstants.h>
35 #include <xmltooling/XMLToolingConfig.h>
36 #include <xmltooling/util/StorageService.h>
37 #include <xmltooling/util/URLEncoder.h>
38
39 using namespace shibsp;
40 using namespace samlconstants;
41 using namespace opensaml;
42 using namespace xmltooling;
43 using namespace log4cpp;
44 using namespace xercesc;
45 using namespace std;
46
47 namespace shibsp {
48     SHIBSP_DLLLOCAL PluginManager<Handler,pair<const DOMElement*,const char*>>::Factory SAML1ConsumerFactory;
49     SHIBSP_DLLLOCAL PluginManager<Handler,pair<const DOMElement*,const char*>>::Factory SAML2ConsumerFactory;
50     SHIBSP_DLLLOCAL PluginManager<Handler,pair<const DOMElement*,const char*>>::Factory ChainingSessionInitiatorFactory;
51     SHIBSP_DLLLOCAL PluginManager<Handler,pair<const DOMElement*,const char*>>::Factory Shib1SessionInitiatorFactory;
52 };
53
54 void SHIBSP_API shibsp::registerHandlers()
55 {
56     SPConfig& conf=SPConfig::getConfig();
57     
58     conf.AssertionConsumerServiceManager.registerFactory(SAML1_PROFILE_BROWSER_ARTIFACT, SAML1ConsumerFactory);
59     conf.AssertionConsumerServiceManager.registerFactory(SAML1_PROFILE_BROWSER_POST, SAML1ConsumerFactory);
60     conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_ARTIFACT, SAML2ConsumerFactory);
61     conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_POST, SAML2ConsumerFactory);
62     conf.AssertionConsumerServiceManager.registerFactory(SAML20_BINDING_HTTP_POST_SIMPLESIGN, SAML2ConsumerFactory);
63
64     conf.SessionInitiatorManager.registerFactory(CHAINING_SESSION_INITIATOR, ChainingSessionInitiatorFactory);
65     conf.SessionInitiatorManager.registerFactory(SHIB1_SESSION_INITIATOR, Shib1SessionInitiatorFactory);
66 }
67
68 AbstractHandler::AbstractHandler(
69     const DOMElement* e, log4cpp::Category& log, DOMNodeFilter* filter, const map<string,string>* remapper
70     ) : m_log(log) {
71     load(e,log,filter,remapper);
72 }
73
74 void AbstractHandler::checkError(const XMLObject* response) const
75 {
76     const saml2p::StatusResponseType* r2 = dynamic_cast<const saml2p::StatusResponseType*>(response);
77     if (r2) {
78         const saml2p::Status* status = r2->getStatus();
79         if (status) {
80             const saml2p::StatusCode* sc = status->getStatusCode();
81             const XMLCh* code = sc ? sc->getValue() : NULL;
82             if (code && !XMLString::equals(code,saml2p::StatusCode::SUCCESS)) {
83                 FatalProfileException ex("SAML Response message contained an error.");
84                 auto_ptr_char c1(code);
85                 ex.addProperty("StatusCode", c1.get());
86                 if (sc->getStatusCode()) {
87                     code = sc->getStatusCode()->getValue();
88                     auto_ptr_char c2(code);
89                     ex.addProperty("StatusCode2", c2.get());
90                 }
91                 if (status->getStatusMessage()) {
92                     auto_ptr_char msg(status->getStatusMessage()->getMessage());
93                     ex.addProperty("StatusMessage", msg.get());
94                 }
95             }
96         }
97     }
98
99     const saml1p::Response* r1 = dynamic_cast<const saml1p::Response*>(response);
100     if (r1) {
101         const saml1p::Status* status = r1->getStatus();
102         if (status) {
103             const saml1p::StatusCode* sc = status->getStatusCode();
104             const QName* code = sc ? sc->getValue() : NULL;
105             if (code && *code != saml1p::StatusCode::SUCCESS) {
106                 FatalProfileException ex("SAML Response message contained an error.");
107                 ex.addProperty("StatusCode", code->toString().c_str());
108                 if (sc->getStatusCode()) {
109                     code = sc->getStatusCode()->getValue();
110                     if (code)
111                         ex.addProperty("StatusCode2", code->toString().c_str());
112                 }
113                 if (status->getStatusMessage()) {
114                     auto_ptr_char msg(status->getStatusMessage()->getMessage());
115                     ex.addProperty("StatusMessage", msg.get());
116                 }
117             }
118         }
119     }
120 }
121
122 void AbstractHandler::preserveRelayState(SPRequest& request, string& relayState) const
123 {
124     pair<bool,const char*> mech=getString("relayState");
125     const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
126
127     // No setting means just pass it by value.
128     if (!mech.first || !mech.second || !*mech.second) {
129         relayState = urlenc->encode(relayState.c_str());
130     }
131     else if (!strcmp(mech.second, "cookie")) {
132         // Here we store the state in a cookie and send a fixed
133         // value so we can recognize it on the way back.
134         pair<string,const char*> shib_cookie=request.getApplication().getCookieNameProps("_shibstate_");
135         string stateval = urlenc->encode(relayState.c_str()) + shib_cookie.second;
136         request.setCookie(shib_cookie.first.c_str(),stateval.c_str());
137         relayState = "cookie";
138     }
139     else if (strstr(mech.second,"ss:")==mech.second) {
140         mech.second+=3;
141         if (*mech.second) {
142             DDF out,in = DDF("set::RelayState").structure();
143             in.addmember("id").string(mech.second);
144             in.addmember("value").string(relayState.c_str());
145             DDFJanitor jin(in),jout(out);
146             out = request.getServiceProvider().getListenerService()->send(in);
147             if (!out.isstring())
148                 throw IOException("StorageService-backed RelayState mechanism did not return a state key.");
149             relayState = string(mech.second-3) + ':' + urlenc->encode(out.string());
150         }
151     }
152     else
153         throw ConfigurationException("Unsupported relayState mechanism ($1).", params(1,mech.second));
154 }
155
156 void AbstractHandler::recoverRelayState(HTTPRequest& httpRequest, string& relayState) const
157 {
158     SPConfig& conf = SPConfig::getConfig();
159
160     // Look for StorageService-backed state of the form "ss:SSID:key".
161     const char* state = relayState.c_str();
162     if (strstr(state,"ss:")==state) {
163         state += 3;
164         const char* key = strchr(state,':');
165         if (key) {
166             string ssid = relayState.substr(3, key - state);
167             key++;
168             if (!ssid.empty() && *key) {
169                 if (conf.isEnabled(SPConfig::OutOfProcess)) {
170                     StorageService* storage = conf.getServiceProvider()->getStorageService(ssid.c_str());
171                     if (storage) {
172                         if (storage->readString("RelayState",key,&relayState)>0)
173                             storage->deleteString("RelayState",key);
174                         else
175                             relayState = "default";
176                     }
177                     else {
178                         Category::getInstance(SHIBSP_LOGCAT".Handler").error(
179                             "Storage-backed RelayState with invalid StorageService ID (%s)", ssid.c_str()
180                             );
181                         relayState = "default";
182                     }
183                 }
184                 else if (conf.isEnabled(SPConfig::InProcess)) {
185                     // In process, we should be able to cast down to a full SPRequest.
186                     SPRequest& request = dynamic_cast<SPRequest&>(httpRequest);
187                     DDF out,in = DDF("get::RelayState").structure();
188                     in.addmember("id").string(ssid.c_str());
189                     in.addmember("key").string(key);
190                     DDFJanitor jin(in),jout(out);
191                     out = request.getServiceProvider().getListenerService()->send(in);
192                     if (!out.isstring())
193                         throw IOException("StorageService-backed RelayState mechanism did not return a state value.");
194                     relayState = out.string();
195                 }
196             }
197         }
198     }
199     else if (conf.isEnabled(SPConfig::InProcess)) {
200         // In process, we should be able to cast down to a full SPRequest.
201         SPRequest& request = dynamic_cast<SPRequest&>(httpRequest);
202         if (relayState.empty() || relayState == "cookie") {
203             // Pull the value from the "relay state" cookie.
204             pair<string,const char*> relay_cookie = request.getApplication().getCookieNameProps("_shibstate_");
205             const char* state = request.getCookie(relay_cookie.first.c_str());
206             if (state && *state) {
207                 // URL-decode the value.
208                 char* rscopy=strdup(state);
209                 XMLToolingConfig::getConfig().getURLEncoder()->decode(rscopy);
210                 relayState = rscopy;
211                 free(rscopy);
212                 
213                 // Clear the cookie.
214                 request.setCookie(relay_cookie.first.c_str(),relay_cookie.second);
215             }
216             else
217                 relayState = "default"; // fall through...
218         }
219         
220         // Check for "default" value.
221         if (relayState == "default") {
222             pair<bool,const char*> homeURL=request.getApplication().getString("homeURL");
223             relayState=homeURL.first ? homeURL.second : "/";
224         }
225     }
226 }