2 * Copyright 2001-2008 Internet2
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * DynamicMetadataProvider.cpp
20 * Advanced implementation of a dynamic caching MetadataProvider.
24 #include "exceptions.h"
25 #include "ServiceProvider.h"
26 #include "metadata/MetadataProviderCriteria.h"
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>
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>
42 #include <xercesc/framework/Wrapper4InputSource.hpp>
43 #include <xercesc/util/XMLUniDefs.hpp>
45 using namespace shibsp;
46 using namespace opensaml;
47 using namespace xmltooling::logging;
48 using namespace xmltooling;
52 class SAML_DLLLOCAL DummyCredentialResolver : public CredentialResolver
55 DummyCredentialResolver() {}
56 ~DummyCredentialResolver() {}
58 Lockable* lock() {return this;}
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
67 class SHIBSP_DLLLOCAL DynamicMetadataProvider : public saml2md::DynamicMetadataProvider
70 DynamicMetadataProvider(const xercesc::DOMElement* e=NULL);
72 virtual ~DynamicMetadataProvider() {
77 saml2md::EntityDescriptor* resolve(const saml2md::MetadataProvider::Criteria& criteria) const;
80 bool m_verifyHost,m_ignoreTransport;
81 X509TrustEngine* m_trust;
85 saml2md::MetadataProvider* SHIBSP_DLLLOCAL DynamicMetadataProviderFactory(const DOMElement* const & e)
87 return new DynamicMetadataProvider(e);
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);
96 DynamicMetadataProvider::DynamicMetadataProvider(const DOMElement* e)
97 : saml2md::DynamicMetadataProvider(e), m_verifyHost(true), m_ignoreTransport(false), m_trust(NULL)
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;
108 e = e ? XMLHelper::getFirstChildElement(e, _TrustEngine) : NULL;
109 auto_ptr_char t2(e ? e->getAttributeNS(NULL,type) : NULL);
111 TrustEngine* trust = XMLToolingConfig::getConfig().TrustEngineManager.newPlugin(t2.get(),e);
112 if (!(m_trust = dynamic_cast<X509TrustEngine*>(trust))) {
114 throw ConfigurationException("DynamicMetadataProvider requires an X509TrustEngine plugin.");
119 throw ConfigurationException("DynamicMetadataProvider requires an X509TrustEngine plugin unless ignoreTransport is true.");
122 saml2md::EntityDescriptor* DynamicMetadataProvider::resolve(const saml2md::MetadataProvider::Criteria& criteria) const
125 xmltooling::NDC("resolve");
127 Category& log=Category::getInstance(SHIBSP_LOGCAT".MetadataProvider.Dynamic");
130 if (criteria.entityID_ascii)
131 name = criteria.entityID_ascii;
132 else if (criteria.entityID_unicode) {
133 auto_ptr_char temp(criteria.entityID_unicode);
136 else if (criteria.artifact)
137 name = criteria.artifact->getSource();
139 // Establish networking properties based on calling application.
140 const MetadataProviderCriteria* mpc = dynamic_cast<const MetadataProviderCriteria*>(&criteria);
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);
147 auto_ptr_XMLCh temp2(name.c_str());
148 relyingParty = mpc->application.getRelyingParty(temp2.get());
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,':');
155 throw IOException("entityID was not a URL.");
156 string scheme(addr.m_endpoint, pch-addr.m_endpoint);
157 SOAPTransport* transport=NULL;
159 transport = XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr);
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.");
165 auto_ptr<SOAPTransport> transportwrapper(transport);
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.");
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();
179 credlocker.assign(credResolver);
181 CredentialCriteria cc;
182 cc.setUsage(Credential::TLS_CREDENTIAL);
183 authType = relyingParty->getString("keyName");
185 cc.getKeyNames().insert(authType.second);
186 const Credential* cred = credResolver->resolve(&cc);
187 cc.getKeyNames().clear();
189 if (!transport->setCredential(cred))
190 log.error("failed to load Credential into metadata resolver");
193 log.error("no TLS credential supplied");
197 log.error("no CredentialResolver available for TLS");
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);
220 log.error("failed to configure transport authentication (method=%s)", authType.second);
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);
230 HTTPSOAPTransport* http = dynamic_cast<HTTPSOAPTransport*>(transport);
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);
242 // Use an empty stream to trigger a body-less "GET" operation.
244 transport->send(dummy);
245 istream& msg = transport->receive();
247 DOMDocument* doc=NULL;
248 StreamInputSource src(msg, "DynamicMetadataProvider");
249 Wrapper4InputSource dsrc(&src,false);
251 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
253 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
255 // Wrap the document for now.
256 XercesJanitor<DOMDocument> docjanitor(doc);
258 // Unmarshall objects, binding the document.
259 auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
260 docjanitor.release();
262 // Make sure it's metadata.
263 saml2md::EntityDescriptor* entity = dynamic_cast<saml2md::EntityDescriptor*>(xmlObject.get());
265 throw saml2md::MetadataException(
266 "Root of metadata instance not recognized: $1", params(1,xmlObject->getElementQName().toString().c_str())
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());