Change audience handling and validators to separate out entityID.
[shibboleth/sp.git] / shibsp / handler / impl / Shib1SessionInitiator.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  * Shib1SessionInitiator.cpp
19  * 
20  * Shibboleth 1.x AuthnRequest support.
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 "handler/RemotedHandler.h"
30 #include "handler/SessionInitiator.h"
31 #include "util/SPConstants.h"
32
33 #ifndef SHIBSP_LITE
34 # include <saml/saml2/metadata/Metadata.h>
35 # include <saml/saml2/metadata/EndpointManager.h>
36 #endif
37 #include <xmltooling/XMLToolingConfig.h>
38 #include <xmltooling/util/URLEncoder.h>
39
40 using namespace shibsp;
41 using namespace opensaml::saml2md;
42 using namespace opensaml;
43 using namespace xmltooling;
44 using namespace std;
45
46 namespace shibsp {
47
48 #if defined (_MSC_VER)
49     #pragma warning( push )
50     #pragma warning( disable : 4250 )
51 #endif
52
53     class SHIBSP_DLLLOCAL Shib1SessionInitiator : public SessionInitiator, public AbstractHandler, public RemotedHandler
54     {
55     public:
56         Shib1SessionInitiator(const DOMElement* e, const char* appId)
57                 : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".SessionInitiator.Shib1")), m_appId(appId) {
58             // If Location isn't set, defer address registration until the setParent call.
59             pair<bool,const char*> loc = getString("Location");
60             if (loc.first) {
61                 string address = m_appId + loc.second + "::run::Shib1SI";
62                 setAddress(address.c_str());
63             }
64         }
65         virtual ~Shib1SessionInitiator() {}
66         
67         void setParent(const PropertySet* parent);
68         void receive(DDF& in, ostream& out);
69         pair<bool,long> run(SPRequest& request, const char* entityID=NULL, bool isHandler=true) const;
70
71     private:
72         pair<bool,long> doRequest(
73             const Application& application,
74             HTTPResponse& httpResponse,
75             const char* entityID,
76             const char* acsLocation,
77             string& relayState
78             ) const;
79         string m_appId;
80     };
81
82 #if defined (_MSC_VER)
83     #pragma warning( pop )
84 #endif
85
86     SessionInitiator* SHIBSP_DLLLOCAL Shib1SessionInitiatorFactory(const pair<const DOMElement*,const char*>& p)
87     {
88         return new Shib1SessionInitiator(p.first, p.second);
89     }
90
91 };
92
93 void Shib1SessionInitiator::setParent(const PropertySet* parent)
94 {
95     DOMPropertySet::setParent(parent);
96     pair<bool,const char*> loc = getString("Location");
97     if (loc.first) {
98         string address = m_appId + loc.second + "::run::Shib1SI";
99         setAddress(address.c_str());
100     }
101     else {
102         m_log.warn("no Location property in Shib1 SessionInitiator (or parent), can't register as remoted handler");
103     }
104 }
105
106 pair<bool,long> Shib1SessionInitiator::run(SPRequest& request, const char* entityID, bool isHandler) const
107 {
108     // We have to know the IdP to function.
109     if (!entityID || !*entityID)
110         return make_pair(false,0L);
111
112     string target;
113     const Handler* ACS=NULL;
114     const char* option;
115     const Application& app=request.getApplication();
116
117     if (isHandler) {
118         option=request.getParameter("acsIndex");
119         if (option) {
120             ACS = app.getAssertionConsumerServiceByIndex(atoi(option));
121             if (!ACS)
122                 request.log(SPRequest::SPWarn, "invalid acsIndex specified in request, using default ACS location");
123         }
124
125         option = request.getParameter("target");
126         if (option)
127             target = option;
128
129         // Since we're passing the ACS by value, we need to compute the return URL,
130         // so we'll need the target resource for real.
131         recoverRelayState(request.getApplication(), request, request, target, false);
132     }
133     else {
134         // We're running as a "virtual handler" from within the filter.
135         // The target resource is the current one and everything else is defaulted.
136         target=request.getRequestURL();
137     }
138
139     // Since we're not passing by index, we need to fully compute the return URL.
140     if (!ACS) {
141         pair<bool,unsigned int> index = getUnsignedInt("defaultACSIndex");
142         if (index.first) {
143             ACS = app.getAssertionConsumerServiceByIndex(index.second);
144             if (!ACS)
145                 request.log(SPRequest::SPWarn, "invalid defaultACSIndex, using default ACS location");
146         }
147         if (!ACS)
148             ACS = app.getDefaultAssertionConsumerService();
149     }
150
151     // Compute the ACS URL. We add the ACS location to the base handlerURL.
152     string ACSloc=request.getHandlerURL(target.c_str());
153     pair<bool,const char*> loc=ACS ? ACS->getString("Location") : pair<bool,const char*>(false,NULL);
154     if (loc.first) ACSloc+=loc.second;
155
156     if (isHandler) {
157         // We may already have RelayState set if we looped back here,
158         // but just in case target is a resource, we reset it back.
159         target.erase();
160         option = request.getParameter("target");
161         if (option)
162             target = option;
163     }
164
165     m_log.debug("attempting to initiate session using Shibboleth with provider (%s)", entityID);
166
167     if (SPConfig::getConfig().isEnabled(SPConfig::OutOfProcess))
168         return doRequest(app, request, entityID, ACSloc.c_str(), target);
169
170     // Remote the call.
171     DDF out,in = DDF(m_address.c_str()).structure();
172     DDFJanitor jin(in), jout(out);
173     in.addmember("application_id").string(app.getId());
174     in.addmember("entity_id").string(entityID);
175     in.addmember("acsLocation").string(ACSloc.c_str());
176     if (!target.empty())
177         in.addmember("RelayState").string(target.c_str());
178
179     // Remote the processing.
180     out = request.getServiceProvider().getListenerService()->send(in);
181     return unwrap(request, out);
182 }
183
184 void Shib1SessionInitiator::receive(DDF& in, ostream& out)
185 {
186     // Find application.
187     const char* aid=in["application_id"].string();
188     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
189     if (!app) {
190         // Something's horribly wrong.
191         m_log.error("couldn't find application (%s) to generate AuthnRequest", aid ? aid : "(missing)");
192         throw ConfigurationException("Unable to locate application for new session, deleted?");
193     }
194
195     const char* entityID = in["entity_id"].string();
196     const char* acsLocation = in["acsLocation"].string();
197     if (!entityID || !acsLocation)
198         throw ConfigurationException("No entityID or acsLocation parameter supplied to remoted SessionInitiator.");
199
200     DDF ret(NULL);
201     DDFJanitor jout(ret);
202
203     // Wrap the outgoing object with a Response facade.
204     auto_ptr<HTTPResponse> http(getResponse(ret));
205
206     string relayState(in["RelayState"].string() ? in["RelayState"].string() : "");
207
208     // Since we're remoted, the result should either be a throw, which we pass on,
209     // a false/0 return, which we just return as an empty structure, or a response/redirect,
210     // which we capture in the facade and send back.
211     doRequest(*app, *http.get(), entityID, acsLocation, relayState);
212     out << ret;
213 }
214
215 pair<bool,long> Shib1SessionInitiator::doRequest(
216     const Application& app,
217     HTTPResponse& httpResponse,
218     const char* entityID,
219     const char* acsLocation,
220     string& relayState
221     ) const
222 {
223 #ifndef SHIBSP_LITE
224     // Use metadata to invoke the SSO service directly.
225     MetadataProvider* m=app.getMetadataProvider();
226     Locker locker(m);
227     MetadataProvider::Criteria mc(entityID, &IDPSSODescriptor::ELEMENT_QNAME, shibspconstants::SHIB1_PROTOCOL_ENUM);
228     pair<const EntityDescriptor*,const RoleDescriptor*> entity = m->getEntityDescriptor(mc);
229     if (!entity.first) {
230         m_log.warn("unable to locate metadata for provider (%s)", entityID);
231         throw MetadataException("Unable to locate metadata for identity provider ($entityID)", namedparams(1, "entityID", entityID));
232     }
233     else if (!entity.second) {
234         m_log.warn("unable to locate Shibboleth-aware identity provider role for provider (%s)", entityID);
235         if (getParent())
236             return make_pair(false,0L);
237         throw MetadataException("Unable to locate Shibboleth-aware identity provider role for provider ($entityID)", namedparams(1, "entityID", entityID));
238     }
239     const EndpointType* ep=EndpointManager<SingleSignOnService>(
240         dynamic_cast<const IDPSSODescriptor*>(entity.second)->getSingleSignOnServices()
241         ).getByBinding(shibspconstants::SHIB1_AUTHNREQUEST_PROFILE_URI);
242     if (!ep) {
243         m_log.warn("unable to locate compatible SSO service for provider (%s)", entityID);
244         if (getParent())
245             return make_pair(false,0L);
246         throw MetadataException("Unable to locate compatible SSO service for provider ($entityID)", namedparams(1, "entityID", entityID));
247     }
248
249     preserveRelayState(app, httpResponse, relayState);
250
251     // Shib 1.x requires a target value.
252     if (relayState.empty())
253         relayState = "default";
254
255     char timebuf[16];
256     sprintf(timebuf,"%lu",time(NULL));
257     const URLEncoder* urlenc = XMLToolingConfig::getConfig().getURLEncoder();
258     auto_ptr_char dest(ep->getLocation());
259     string req=string(dest.get()) + (strchr(dest.get(),'?') ? '&' : '?') + "shire=" + urlenc->encode(acsLocation) +
260         "&time=" + timebuf + "&target=" + urlenc->encode(relayState.c_str()) +
261         "&providerId=" + urlenc->encode(app.getRelyingParty(entity.first)->getString("entityID").second);
262
263     return make_pair(true, httpResponse.sendRedirect(req.c_str()));
264 #else
265     return make_pair(false,0L);
266 #endif
267 }