2 * Licensed to the University Corporation for Advanced Internet
3 * Development, Inc. (UCAID) under one or more contributor license
4 * agreements. See the NOTICE file distributed with this work for
5 * additional information regarding copyright ownership.
7 * UCAID licenses this file to you under the Apache License,
8 * Version 2.0 (the "License"); you may not use this file except
9 * in compliance with the License. You may obtain a copy of the
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17 * either express or implied. See the License for the specific
18 * language governing permissions and limitations under the License.
22 * DynamicMetadataProvider.cpp
24 * Advanced implementation of a dynamic caching MetadataProvider.
28 #include "exceptions.h"
29 #include "Application.h"
30 #include "ServiceProvider.h"
31 #include "metadata/MetadataProviderCriteria.h"
33 #include <xercesc/framework/Wrapper4InputSource.hpp>
34 #include <xercesc/util/XMLUniDefs.hpp>
35 #include <xercesc/util/regx/RegularExpression.hpp>
36 #include <xsec/framework/XSECDefs.hpp>
38 #include <saml/version.h>
39 #include <saml/binding/SAMLArtifact.h>
40 #include <saml/saml2/metadata/Metadata.h>
41 #include <saml/saml2/metadata/DynamicMetadataProvider.h>
42 #include <xmltooling/logging.h>
43 #include <xmltooling/XMLToolingConfig.h>
44 #include <xmltooling/security/Credential.h>
45 #include <xmltooling/security/CredentialCriteria.h>
46 #include <xmltooling/security/CredentialResolver.h>
47 #include <xmltooling/security/X509TrustEngine.h>
48 #include <xmltooling/soap/HTTPSOAPTransport.h>
49 #include <xmltooling/util/NDC.h>
50 #include <xmltooling/util/ParserPool.h>
51 #include <xmltooling/util/URLEncoder.h>
52 #include <xmltooling/util/XMLHelper.h>
54 using namespace shibsp;
55 using namespace opensaml;
56 using namespace xmltooling::logging;
57 using namespace xmltooling;
61 class SAML_DLLLOCAL DummyCredentialResolver : public CredentialResolver
64 DummyCredentialResolver() {}
65 ~DummyCredentialResolver() {}
67 Lockable* lock() {return this;}
70 const Credential* resolve(const CredentialCriteria* criteria=nullptr) const {return nullptr;}
71 vector<const Credential*>::size_type resolve(
72 vector<const Credential*>& results, const CredentialCriteria* criteria=nullptr
76 class SHIBSP_DLLLOCAL DynamicMetadataProvider : public saml2md::DynamicMetadataProvider
79 DynamicMetadataProvider(const xercesc::DOMElement* e=nullptr);
81 virtual ~DynamicMetadataProvider() {
86 saml2md::EntityDescriptor* resolve(const saml2md::MetadataProvider::Criteria& criteria) const;
89 bool m_verifyHost,m_ignoreTransport,m_encoded;
90 string m_subst, m_match, m_regex;
91 X509TrustEngine* m_trust;
95 saml2md::MetadataProvider* SHIBSP_DLLLOCAL DynamicMetadataProviderFactory(const DOMElement* const & e)
97 return new DynamicMetadataProvider(e);
100 static const XMLCh encoded[] = UNICODE_LITERAL_7(e,n,c,o,d,e,d);
101 static const XMLCh ignoreTransport[] = UNICODE_LITERAL_15(i,g,n,o,r,e,T,r,a,n,s,p,o,r,t);
102 static const XMLCh match[] = UNICODE_LITERAL_5(m,a,t,c,h);
103 static const XMLCh Regex[] = UNICODE_LITERAL_5(R,e,g,e,x);
104 static const XMLCh Subst[] = UNICODE_LITERAL_5(S,u,b,s,t);
105 static const XMLCh _TrustEngine[] = UNICODE_LITERAL_11(T,r,u,s,t,E,n,g,i,n,e);
106 static const XMLCh type[] = UNICODE_LITERAL_4(t,y,p,e);
107 static const XMLCh verifyHost[] = UNICODE_LITERAL_10(v,e,r,i,f,y,H,o,s,t);
110 DynamicMetadataProvider::DynamicMetadataProvider(const DOMElement* e)
111 : saml2md::DynamicMetadataProvider(e),
112 m_verifyHost(XMLHelper::getAttrBool(e, true, verifyHost)),
113 m_ignoreTransport(XMLHelper::getAttrBool(e, false, ignoreTransport)),
114 m_encoded(true), m_trust(nullptr)
116 const DOMElement* child = XMLHelper::getFirstChildElement(e, Subst);
117 if (child && child->hasChildNodes()) {
118 auto_ptr_char s(child->getFirstChild()->getNodeValue());
119 if (s.get() && *s.get()) {
121 m_encoded = XMLHelper::getAttrBool(child, true, encoded);
125 if (m_subst.empty()) {
126 child = XMLHelper::getFirstChildElement(e, Regex);
127 if (child && child->hasChildNodes() && child->hasAttributeNS(nullptr, match)) {
128 m_match = XMLHelper::getAttrString(child, nullptr, match);
129 auto_ptr_char repl(child->getFirstChild()->getNodeValue());
130 if (repl.get() && *repl.get())
131 m_regex = repl.get();
135 if (!m_ignoreTransport) {
136 child = XMLHelper::getFirstChildElement(e, _TrustEngine);
137 string t = XMLHelper::getAttrString(child, nullptr, type);
139 TrustEngine* trust = XMLToolingConfig::getConfig().TrustEngineManager.newPlugin(t.c_str(), child);
140 if (!(m_trust = dynamic_cast<X509TrustEngine*>(trust))) {
142 throw ConfigurationException("DynamicMetadataProvider requires an X509TrustEngine plugin.");
147 throw ConfigurationException("DynamicMetadataProvider requires an X509TrustEngine plugin unless ignoreTransport is true.");
151 saml2md::EntityDescriptor* DynamicMetadataProvider::resolve(const saml2md::MetadataProvider::Criteria& criteria) const
154 xmltooling::NDC("resolve");
156 Category& log=Category::getInstance(SHIBSP_LOGCAT".MetadataProvider.Dynamic");
159 if (criteria.entityID_ascii) {
160 name = criteria.entityID_ascii;
162 else if (criteria.entityID_unicode) {
163 auto_ptr_char temp(criteria.entityID_unicode);
166 else if (criteria.artifact) {
167 if (m_subst.empty() && (m_regex.empty() || m_match.empty()))
168 throw saml2md::MetadataException("Unable to resolve metadata dynamically from an artifact.");
169 name = "{sha1}" + criteria.artifact->getSource();
172 // Possibly transform the input into a different URL to use.
173 if (!m_subst.empty()) {
174 string::size_type pos = m_subst.find("$entityID");
175 if (pos != string::npos) {
176 string name2 = m_subst;
177 name2.replace(pos, 9, m_encoded ? XMLToolingConfig::getConfig().getURLEncoder()->encode(name.c_str()) : name);
178 log.info("transformed location from (%s) to (%s)", name.c_str(), name2.c_str());
182 else if (!m_match.empty() && !m_regex.empty()) {
184 RegularExpression exp(m_match.c_str());
185 XMLCh* temp = exp.replace(name.c_str(), m_regex.c_str());
187 auto_ptr_char narrow(temp);
188 XMLString::release(&temp);
190 // For some reason it returns the match string if it doesn't match the expression.
191 if (name != narrow.get()) {
192 log.info("transformed location from (%s) to (%s)", name.c_str(), narrow.get());
197 catch (XMLException& ex) {
198 auto_ptr_char msg(ex.getMessage());
199 log.error("caught error applying regular expression: %s", msg.get());
203 // Establish networking properties based on calling application.
204 const MetadataProviderCriteria* mpc = dynamic_cast<const MetadataProviderCriteria*>(&criteria);
206 throw saml2md::MetadataException("Dynamic MetadataProvider requires Shibboleth-aware lookup criteria, check calling code.");
207 const PropertySet* relyingParty;
208 if (criteria.artifact)
209 relyingParty = mpc->application.getRelyingParty((XMLCh*)nullptr);
210 else if (criteria.entityID_unicode)
211 relyingParty = mpc->application.getRelyingParty(criteria.entityID_unicode);
213 auto_ptr_XMLCh temp2(name.c_str());
214 relyingParty = mpc->application.getRelyingParty(temp2.get());
217 // Prepare a transport object addressed appropriately.
218 SOAPTransport::Address addr(relyingParty->getString("entityID").second, name.c_str(), name.c_str());
219 const char* pch = strchr(addr.m_endpoint,':');
221 throw IOException("location was not a URL.");
222 string scheme(addr.m_endpoint, pch-addr.m_endpoint);
223 SOAPTransport* transport=nullptr;
225 transport = XMLToolingConfig::getConfig().SOAPTransportManager.newPlugin(scheme.c_str(), addr);
227 catch (exception& ex) {
228 log.error("exception while building transport object to resolve URL: %s", ex.what());
229 throw IOException("Unable to resolve entityID with a known transport protocol.");
231 auto_ptr<SOAPTransport> transportwrapper(transport);
233 // Apply properties as directed.
234 transport->setVerifyHost(m_verifyHost);
235 DummyCredentialResolver dcr;
236 if (m_trust && !transport->setTrustEngine(m_trust, &dcr))
237 throw IOException("Unable to install X509TrustEngine into metadata resolver.");
239 Locker credlocker(nullptr, false);
240 CredentialResolver* credResolver = nullptr;
241 pair<bool,const char*> authType=relyingParty->getString("authType");
242 if (!authType.first || !strcmp(authType.second,"TLS")) {
243 credResolver = mpc->application.getCredentialResolver();
245 credlocker.assign(credResolver);
247 CredentialCriteria cc;
248 cc.setUsage(Credential::TLS_CREDENTIAL);
249 authType = relyingParty->getString("keyName");
251 cc.getKeyNames().insert(authType.second);
252 const Credential* cred = credResolver->resolve(&cc);
253 cc.getKeyNames().clear();
255 if (!transport->setCredential(cred))
256 log.error("failed to load Credential into metadata resolver");
259 log.error("no TLS credential supplied");
263 log.error("no CredentialResolver available for TLS");
267 SOAPTransport::transport_auth_t type=SOAPTransport::transport_auth_none;
268 pair<bool,const char*> username=relyingParty->getString("authUsername");
269 pair<bool,const char*> password=relyingParty->getString("authPassword");
270 if (!username.first || !password.first)
271 log.error("transport authType (%s) specified but authUsername or authPassword was missing", authType.second);
272 else if (!strcmp(authType.second,"basic"))
273 type = SOAPTransport::transport_auth_basic;
274 else if (!strcmp(authType.second,"digest"))
275 type = SOAPTransport::transport_auth_digest;
276 else if (!strcmp(authType.second,"ntlm"))
277 type = SOAPTransport::transport_auth_ntlm;
278 else if (!strcmp(authType.second,"gss"))
279 type = SOAPTransport::transport_auth_gss;
280 else if (strcmp(authType.second,"none"))
281 log.error("unknown authType (%s) specified for RelyingParty", authType.second);
282 if (type > SOAPTransport::transport_auth_none) {
283 if (transport->setAuth(type,username.second,password.second))
284 log.debug("configured for transport authentication (method=%s, username=%s)", authType.second, username.second);
286 log.error("failed to configure transport authentication (method=%s)", authType.second);
290 pair<bool,unsigned int> timeout = relyingParty->getUnsignedInt("connectTimeout");
291 transport->setConnectTimeout(timeout.first ? timeout.second : 10);
292 timeout = relyingParty->getUnsignedInt("timeout");
293 transport->setTimeout(timeout.first ? timeout.second : 20);
294 mpc->application.getServiceProvider().setTransportOptions(*transport);
296 HTTPSOAPTransport* http = dynamic_cast<HTTPSOAPTransport*>(transport);
298 pair<bool,bool> flag = relyingParty->getBool("chunkedEncoding");
299 http->useChunkedEncoding(flag.first && flag.second);
300 http->setRequestHeader("Xerces-C", XERCES_FULLVERSIONDOT);
301 http->setRequestHeader("XML-Security-C", XSEC_FULLVERSIONDOT);
302 http->setRequestHeader("OpenSAML-C", OPENSAML_FULLVERSIONDOT);
303 http->setRequestHeader("User-Agent", PACKAGE_NAME);
304 http->setRequestHeader(PACKAGE_NAME, PACKAGE_VERSION);
308 // Use a nullptr stream to trigger a body-less "GET" operation.
310 istream& msg = transport->receive();
312 DOMDocument* doc=nullptr;
313 StreamInputSource src(msg, "DynamicMetadataProvider");
314 Wrapper4InputSource dsrc(&src,false);
316 doc=XMLToolingConfig::getConfig().getValidatingParser().parse(dsrc);
318 doc=XMLToolingConfig::getConfig().getParser().parse(dsrc);
320 // Wrap the document for now.
321 XercesJanitor<DOMDocument> docjanitor(doc);
323 // Unmarshall objects, binding the document.
324 auto_ptr<XMLObject> xmlObject(XMLObjectBuilder::buildOneFromElement(doc->getDocumentElement(), true));
325 docjanitor.release();
327 // Make sure it's metadata.
328 saml2md::EntityDescriptor* entity = dynamic_cast<saml2md::EntityDescriptor*>(xmlObject.get());
330 throw saml2md::MetadataException(
331 "Root of metadata instance not recognized: $1", params(1,xmlObject->getElementQName().toString().c_str())
337 catch (XMLException& e) {
338 auto_ptr_char msg(e.getMessage());
339 log.error("Xerces error while resolving location (%s): %s", name.c_str(), msg.get());
340 throw saml2md::MetadataException(msg.get());