2698baadec279261759c0af463f71468cc47cc14
[shibboleth/cpp-sp.git] / shibsp / metadata / DynamicMetadataProvider.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  * DynamicMetadataProvider.cpp
19  *
20  * Advanced implementation of a dynamic caching MetadataProvider.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "Application.h"
26 #include "ServiceProvider.h"
27 #include "metadata/MetadataProviderCriteria.h"
28
29 #include <saml/version.h>
30 #include <saml/binding/SAMLArtifact.h>
31 #include <saml/saml2/metadata/Metadata.h>
32 #include <saml/saml2/metadata/DynamicMetadataProvider.h>
33
34 #include <xmltooling/logging.h>
35 #include <xmltooling/XMLToolingConfig.h>
36 #include <xmltooling/security/Credential.h>
37 #include <xmltooling/security/CredentialCriteria.h>
38 #include <xmltooling/security/CredentialResolver.h>
39 #include <xmltooling/security/X509TrustEngine.h>
40 #include <xmltooling/soap/HTTPSOAPTransport.h>
41 #include <xmltooling/util/NDC.h>
42 #include <xmltooling/util/XMLHelper.h>
43
44 #include <xercesc/framework/Wrapper4InputSource.hpp>
45 #include <xercesc/util/XMLUniDefs.hpp>
46
47 using namespace shibsp;
48 using namespace opensaml;
49 using namespace xmltooling::logging;
50 using namespace xmltooling;
51 using namespace std;
52
53 namespace shibsp {
54     class SAML_DLLLOCAL DummyCredentialResolver : public CredentialResolver
55     {
56     public:
57         DummyCredentialResolver() {}
58         ~DummyCredentialResolver() {}
59
60         Lockable* lock() {return this;}
61         void unlock() {}
62
63         const Credential* resolve(const CredentialCriteria* criteria=NULL) const {return NULL;}
64         vector<const Credential*>::size_type resolve(
65             vector<const Credential*>& results, const CredentialCriteria* criteria=NULL
66             ) const {return 0;}
67     };
68
69     class SHIBSP_DLLLOCAL DynamicMetadataProvider : public saml2md::DynamicMetadataProvider
70     {
71     public:
72         DynamicMetadataProvider(const xercesc::DOMElement* e=NULL);
73
74         virtual ~DynamicMetadataProvider() {
75             delete m_trust;
76         }
77
78     protected:
79         saml2md::EntityDescriptor* resolve(const saml2md::MetadataProvider::Criteria& criteria) const;
80
81     private:
82         bool m_verifyHost,m_ignoreTransport;
83         X509TrustEngine* m_trust;
84     };
85
86
87     saml2md::MetadataProvider* SHIBSP_DLLLOCAL DynamicMetadataProviderFactory(const DOMElement* const & e)
88     {
89         return new DynamicMetadataProvider(e);
90     }
91
92     static const XMLCh ignoreTransport[] =  UNICODE_LITERAL_15(i,g,n,o,r,e,T,r,a,n,s,p,o,r,t);
93     static const XMLCh _TrustEngine[] =     UNICODE_LITERAL_11(T,r,u,s,t,E,n,g,i,n,e);
94     static const XMLCh type[] =             UNICODE_LITERAL_4(t,y,p,e);
95     static const XMLCh verifyHost[] =       UNICODE_LITERAL_10(v,e,r,i,f,y,H,o,s,t);
96 };
97
98 DynamicMetadataProvider::DynamicMetadataProvider(const DOMElement* e)
99     : saml2md::DynamicMetadataProvider(e), m_verifyHost(true), m_ignoreTransport(false), m_trust(NULL)
100 {
101     const XMLCh* flag = e ? e->getAttributeNS(NULL, verifyHost) : NULL;
102     if (flag && (*flag == chLatin_f || *flag == chDigit_0))
103         m_verifyHost = false;
104     flag = e ? e->getAttributeNS(NULL, ignoreTransport) : NULL;
105     if (flag && (*flag == chLatin_t || *flag == chDigit_1)) {
106         m_ignoreTransport = true;
107         return;
108     }
109
110     e = e ? XMLHelper::getFirstChildElement(e, _TrustEngine) : NULL;
111     auto_ptr_char t2(e ? e->getAttributeNS(NULL,type) : NULL);
112     if (t2.get()) {
113         TrustEngine* trust = XMLToolingConfig::getConfig().TrustEngineManager.newPlugin(t2.get(),e);
114         if (!(m_trust = dynamic_cast<X509TrustEngine*>(trust))) {
115             delete trust;
116             throw ConfigurationException("DynamicMetadataProvider requires an X509TrustEngine plugin.");
117         }
118         return;
119     }
120
121     throw ConfigurationException("DynamicMetadataProvider requires an X509TrustEngine plugin unless ignoreTransport is true.");
122 }
123
124 saml2md::EntityDescriptor* DynamicMetadataProvider::resolve(const saml2md::MetadataProvider::Criteria& criteria) const
125 {
126 #ifdef _DEBUG
127     xmltooling::NDC("resolve");
128 #endif
129     Category& log=Category::getInstance(SHIBSP_LOGCAT".MetadataProvider.Dynamic");
130
131     string name;
132     if (criteria.entityID_ascii) {
133         name = criteria.entityID_ascii;
134     }
135     else if (criteria.entityID_unicode) {
136         auto_ptr_char temp(criteria.entityID_unicode);
137         name = temp.get();
138     }
139     else if (criteria.artifact) {
140         throw saml2md::MetadataException("Unable to resolve metadata dynamically from an artifact.");
141     }
142
143     // Establish networking properties based on calling application.
144     const MetadataProviderCriteria* mpc = dynamic_cast<const MetadataProviderCriteria*>(&criteria);
145     if (!mpc)
146         throw saml2md::MetadataException("Dynamic MetadataProvider requires Shibboleth-aware lookup criteria, check calling code.");
147     const PropertySet* relyingParty;
148     if (criteria.entityID_unicode)
149         relyingParty = mpc->application.getRelyingParty(criteria.entityID_unicode);
150     else {
151         auto_ptr_XMLCh temp2(name.c_str());
152         relyingParty = mpc->application.getRelyingParty(temp2.get());
153     }
154
155     // Prepare a transport object addressed appropriately.
156     SOAPTransport::Address addr(relyingParty->getString("entityID").second, name.c_str(), name.c_str());
157     const char* pch = strchr(addr.m_endpoint,':');
158     if (!pch)
159         throw IOException("entityID was not a URL.");
160     string scheme(addr.m_endpoint, pch-addr.m_endpoint);
161     SOAPTransport* transport=NULL;
162     try {
163         transport = XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr);
164     }
165     catch (exception& ex) {
166         log.error("exception while building transport object to resolve URL: %s", ex.what());
167         throw IOException("Unable to resolve entityID with a known transport protocol.");
168     }
169     auto_ptr<SOAPTransport> transportwrapper(transport);
170
171     // Apply properties as directed.
172     transport->setVerifyHost(m_verifyHost);
173     DummyCredentialResolver dcr;
174     if (m_trust && !transport->setTrustEngine(m_trust, &dcr))
175         throw IOException("Unable to install X509TrustEngine into metadata resolver.");
176
177     Locker credlocker(NULL, false);
178     CredentialResolver* credResolver = NULL;
179     pair<bool,const char*> authType=relyingParty->getString("authType");
180     if (!authType.first || !strcmp(authType.second,"TLS")) {
181         credResolver = mpc->application.getCredentialResolver();
182         if (credResolver)
183             credlocker.assign(credResolver);
184         if (credResolver) {
185             CredentialCriteria cc;
186             cc.setUsage(Credential::TLS_CREDENTIAL);
187             authType = relyingParty->getString("keyName");
188             if (authType.first)
189                 cc.getKeyNames().insert(authType.second);
190             const Credential* cred = credResolver->resolve(&cc);
191             cc.getKeyNames().clear();
192             if (cred) {
193                 if (!transport->setCredential(cred))
194                     log.error("failed to load Credential into metadata resolver");
195             }
196             else {
197                 log.error("no TLS credential supplied");
198             }
199         }
200         else {
201             log.error("no CredentialResolver available for TLS");
202         }
203     }
204     else {
205         SOAPTransport::transport_auth_t type=SOAPTransport::transport_auth_none;
206         pair<bool,const char*> username=relyingParty->getString("authUsername");
207         pair<bool,const char*> password=relyingParty->getString("authPassword");
208         if (!username.first || !password.first)
209             log.error("transport authType (%s) specified but authUsername or authPassword was missing", authType.second);
210         else if (!strcmp(authType.second,"basic"))
211             type = SOAPTransport::transport_auth_basic;
212         else if (!strcmp(authType.second,"digest"))
213             type = SOAPTransport::transport_auth_digest;
214         else if (!strcmp(authType.second,"ntlm"))
215             type = SOAPTransport::transport_auth_ntlm;
216         else if (!strcmp(authType.second,"gss"))
217             type = SOAPTransport::transport_auth_gss;
218         else if (strcmp(authType.second,"none"))
219             log.error("unknown authType (%s) specified for RelyingParty", authType.second);
220         if (type > SOAPTransport::transport_auth_none) {
221             if (transport->setAuth(type,username.second,password.second))
222                 log.debug("configured for transport authentication (method=%s, username=%s)", authType.second, username.second);
223             else
224                 log.error("failed to configure transport authentication (method=%s)", authType.second);
225         }
226     }
227
228     pair<bool,unsigned int> timeout = relyingParty->getUnsignedInt("connectTimeout");
229     transport->setConnectTimeout(timeout.first ? timeout.second : 10);
230     timeout = relyingParty->getUnsignedInt("timeout");
231     transport->setTimeout(timeout.first ? timeout.second : 20);
232     mpc->application.getServiceProvider().setTransportOptions(*transport);
233
234     HTTPSOAPTransport* http = dynamic_cast<HTTPSOAPTransport*>(transport);
235     if (http) {
236         pair<bool,bool> flag = relyingParty->getBool("chunkedEncoding");
237         http->useChunkedEncoding(flag.first && flag.second);
238         http->setRequestHeader("Xerces-C", XERCES_FULLVERSIONDOT);
239         http->setRequestHeader("XML-Security-C", XSEC_FULLVERSIONDOT);
240         http->setRequestHeader("OpenSAML-C", OPENSAML_FULLVERSIONDOT);
241         http->setRequestHeader("User-Agent", PACKAGE_NAME);
242         http->setRequestHeader(PACKAGE_NAME, PACKAGE_VERSION);
243     }
244
245     try {
246         // Use a NULL stream to trigger a body-less "GET" operation.
247         transport->send();
248         istream& msg = transport->receive();
249
250         DOMDocument* doc=NULL;
251         StreamInputSource src(msg, "DynamicMetadataProvider");
252         Wrapper4InputSource dsrc(&src,false);
253         if (m_validate)
254             doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
255         else
256             doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
257
258         // Wrap the document for now.
259         XercesJanitor<DOMDocument> docjanitor(doc);
260
261         // Unmarshall objects, binding the document.
262         auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
263         docjanitor.release();
264
265         // Make sure it's metadata.
266         saml2md::EntityDescriptor* entity = dynamic_cast<saml2md::EntityDescriptor*>(xmlObject.get());
267         if (!entity) {
268             throw saml2md::MetadataException(
269                 "Root of metadata instance not recognized: $1", params(1,xmlObject->getElementQName().toString().c_str())
270                 );
271         }
272         xmlObject.release();
273         return entity;
274     }
275     catch (XMLException& e) {
276         auto_ptr_char msg(e.getMessage());
277         log.error("Xerces error while resolving entityID (%s): %s", name.c_str(), msg.get());
278         throw saml2md::MetadataException(msg.get());
279     }
280 }