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.
24 * Helper class for SAMLArtifact mapping and retrieval.
28 #include "exceptions.h"
29 #include "binding/ArtifactMap.h"
30 #include "binding/SAMLArtifact.h"
33 #include <boost/lambda/bind.hpp>
34 #include <boost/lambda/lambda.hpp>
35 #include <xercesc/util/XMLUniDefs.hpp>
36 #include <xmltooling/logging.h>
37 #include <xmltooling/XMLObjectBuilder.h>
38 #include <xmltooling/XMLToolingConfig.h>
39 #include <xmltooling/security/SecurityHelper.h>
40 #include <xmltooling/util/NDC.h>
41 #include <xmltooling/util/ParserPool.h>
42 #include <xmltooling/util/StorageService.h>
43 #include <xmltooling/util/XMLHelper.h>
44 #include <xmltooling/util/Threads.h>
46 using namespace opensaml;
47 using namespace xmltooling::logging;
48 using namespace xmltooling;
49 using namespace boost::lambda;
50 using namespace boost;
54 // In-memory storage of mappings instead of using storage API.
55 class SAML_DLLLOCAL ArtifactMappings
58 ArtifactMappings() : m_lock(Mutex::create()) {}
59 ~ArtifactMappings() {}
61 void storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty, int TTL);
62 XMLObject* retrieveContent(const SAMLArtifact* artifact, const char* relyingParty);
63 string getRelyingParty(const SAMLArtifact* artifact);
66 struct SAML_DLLLOCAL Mapping {
67 Mapping() : m_xml(nullptr), m_expires(0) {}
76 void removeMapping(const map<string,Mapping>::iterator& i);
78 auto_ptr<Mutex> m_lock;
79 map<string,Mapping> m_artMap;
80 multimap<time_t,string> m_expMap;
83 static const XMLCh artifactTTL[] = UNICODE_LITERAL_11(a,r,t,i,f,a,c,t,T,T,L);
84 static const XMLCh context[] = UNICODE_LITERAL_7(c,o,n,t,e,x,t);
85 static const XMLCh Mapping[] = UNICODE_LITERAL_7(M,a,p,p,i,n,g);
86 static const XMLCh _relyingParty[] = UNICODE_LITERAL_12(r,e,l,y,i,n,g,P,a,r,t,y);
89 void ArtifactMappings::removeMapping(const map<string,Mapping>::iterator& i)
91 // All elements in the secondary map whose key matches the expiration of the removed mapping.
92 pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
93 m_expMap.equal_range(i->second.m_expires);
95 // Find an element in the matching range whose value matches the input key.
96 multimap<time_t,string>::iterator el = find_if(
97 range.first, range.second,
98 (lambda::bind(&multimap<time_t,string>::value_type::second, _1) == boost::ref(i->first))
100 if (el != range.second) {
107 void ArtifactMappings::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty, int TTL)
109 Lock wrapper(m_lock);
111 // Garbage collect any expired artifacts.
112 time_t now = time(nullptr);
113 multimap<time_t,string>::iterator stop = m_expMap.upper_bound(now);
114 for (multimap<time_t,string>::iterator i = m_expMap.begin(); i != stop; m_expMap.erase(i++)) {
115 delete m_artMap[i->second].m_xml;
116 m_artMap.erase(i->second);
119 // Key is the hexed handle.
120 string hexed = SAMLArtifact::toHex(artifact->getMessageHandle());
121 Mapping& m = m_artMap[hexed];
124 m.m_relying = relyingParty;
125 m.m_expires = now + TTL;
126 m_expMap.insert(pair<const time_t, string>(m.m_expires, hexed));
129 XMLObject* ArtifactMappings::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
131 Category& log=Category::getInstance(SAML_LOGCAT ".ArtifactMap");
132 Lock wrapper(m_lock);
134 map<string,Mapping>::iterator i = m_artMap.find(SAMLArtifact::toHex(artifact->getMessageHandle()));
135 if (i == m_artMap.end())
136 throw BindingException("Requested artifact not in map or may have expired.");
138 if (!(i->second.m_relying.empty())) {
139 if (!relyingParty || i->second.m_relying != relyingParty) {
141 "request from (%s) for artifact issued to (%s)",
142 relyingParty ? relyingParty : "unknown", i->second.m_relying.c_str()
145 throw BindingException("Unauthorized artifact mapping request.");
149 if (time(nullptr) >= i->second.m_expires) {
151 throw BindingException("Requested artifact has expired.");
154 log.debug("resolved artifact for (%s)", relyingParty ? relyingParty : "unknown");
155 XMLObject* ret = i->second.m_xml;
156 i->second.m_xml = nullptr; // clear member so it doesn't get deleted
161 string ArtifactMappings::getRelyingParty(const SAMLArtifact* artifact)
163 map<string,Mapping>::iterator i = m_artMap.find(SAMLArtifact::toHex(artifact->getMessageHandle()));
164 if (i == m_artMap.end())
165 throw BindingException("Requested artifact not in map or may have expired.");
166 return i->second.m_relying;
169 ArtifactMap::ArtifactMap(xmltooling::StorageService* storage, const char* context, unsigned int artifactTTL)
170 : m_storage(storage), m_context((context && *context) ? context : "opensaml::ArtifactMap"), m_artifactTTL(artifactTTL)
173 m_mappings.reset(new ArtifactMappings());
176 ArtifactMap::ArtifactMap(const DOMElement* e, xmltooling::StorageService* storage)
177 : m_storage(storage), m_artifactTTL(180)
180 auto_ptr_char c(e->getAttributeNS(nullptr, context));
181 if (c.get() && *c.get()) {
183 if (storage && m_context.length() > m_storage->getCapabilities().getContextSize()) {
184 throw IOException("ArtifactMap context length exceeds capacity of storage service.");
188 m_context = "opensaml::ArtifactMap";
191 const XMLCh* TTL = e->getAttributeNS(nullptr, artifactTTL);
194 m_artifactTTL = XMLString::parseInt(TTL);
196 catch (XMLException&) {
205 m_mappings.reset(new ArtifactMappings());
208 ArtifactMap::~ArtifactMap()
212 void ArtifactMap::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty)
214 if (content->getParent())
215 throw BindingException("Cannot store artifact mapping for XML content with parent.");
217 return m_mappings->storeContent(content, artifact, relyingParty, m_artifactTTL);
219 // Marshall with defaulted document, to reuse existing DOM and/or create a bound Document.
220 DOMElement* root = content->marshall();
222 // Build a DOM with the same document to store the relyingParty mapping.
224 auto_ptr_XMLCh temp(relyingParty);
225 root = root->getOwnerDocument()->createElementNS(nullptr,Mapping);
226 root->setAttributeNS(nullptr,_relyingParty,temp.get());
227 root->appendChild(content->getDOM());
230 // Serialize the root element, whatever it is, for storage.
232 XMLHelper::serialize(root, xmlbuf);
234 // Use hex form of message handler as storage key unless it's too big.
235 string key = artifact->getMessageHandle();
236 if (key.length() > m_storage->getCapabilities().getKeySize())
237 key = SecurityHelper::doHash("SHA1", key.data(), key.length());
239 key = SAMLArtifact::toHex(key);
241 if (!m_storage->createText(
245 time(nullptr) + m_artifactTTL
247 throw IOException("Attempt to insert duplicate artifact into map.");
250 // Cleanup by destroying XML.
254 XMLObject* ArtifactMap::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
257 xmltooling::NDC ndc("retrieveContent");
259 Category& log=Category::getInstance(SAML_LOGCAT ".ArtifactMap");
262 return m_mappings->retrieveContent(artifact, relyingParty);
264 // Use hex form of message handler as storage key unless it's too big.
265 string key = artifact->getMessageHandle();
266 if (key.length() > m_storage->getCapabilities().getKeySize())
267 key = SecurityHelper::doHash("SHA1", key.data(), key.length());
269 key = SAMLArtifact::toHex(key);
271 // Read the mapping and then delete it.
273 if (!m_storage->readText(m_context.c_str(), key.c_str(), &xmlbuf))
274 throw BindingException("Artifact not found in mapping database.");
275 m_storage->deleteText(m_context.c_str(), key.c_str());
277 // Parse the data back into XML.
278 istringstream is(xmlbuf);
279 DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(is);
280 XercesJanitor<DOMDocument> janitor(doc);
282 // Check the root element.
283 DOMElement* messageRoot = doc->getDocumentElement();
284 if (XMLHelper::isNodeNamed(messageRoot, nullptr, Mapping)) {
285 auto_ptr_char temp(messageRoot->getAttributeNS(nullptr,_relyingParty));
286 if (!relyingParty || strcmp(temp.get(),relyingParty)) {
287 log.warn("request from (%s) for artifact issued to (%s)", relyingParty ? relyingParty : "unknown", temp.get());
288 throw BindingException("Unauthorized artifact mapping request.");
290 messageRoot = XMLHelper::getFirstChildElement(messageRoot);
294 XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(messageRoot, true); // bind document
297 log.debug("resolved artifact for (%s)", relyingParty ? relyingParty : "unknown");
301 string ArtifactMap::getRelyingParty(const SAMLArtifact* artifact)
304 return m_mappings->getRelyingParty(artifact);
306 // Use hex form of message handler as storage key unless it's too big.
307 string key = artifact->getMessageHandle();
308 if (key.length() > m_storage->getCapabilities().getKeySize())
309 key = SecurityHelper::doHash("SHA1", key.data(), key.length());
311 key = SAMLArtifact::toHex(key);
314 if (!m_storage->readText(m_context.c_str(), key.c_str(), &xmlbuf))
315 throw BindingException("Artifact not found in mapping database.");
317 // Parse the data back into XML.
318 istringstream is(xmlbuf);
319 DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(is);
320 XercesJanitor<DOMDocument> janitor(doc);
322 // Check the root element.
323 DOMElement* messageRoot = doc->getDocumentElement();
324 if (XMLHelper::isNodeNamed(messageRoot, nullptr, Mapping)) {
325 auto_ptr_char temp(messageRoot->getAttributeNS(nullptr,_relyingParty));