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