2 * Copyright 2001-2009 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.
20 * Helper class for SAMLArtifact mapping and retrieval.
24 #include "exceptions.h"
25 #include "binding/ArtifactMap.h"
26 #include "binding/SAMLArtifact.h"
29 #include <xercesc/util/XMLUniDefs.hpp>
30 #include <xmltooling/logging.h>
31 #include <xmltooling/XMLObjectBuilder.h>
32 #include <xmltooling/XMLToolingConfig.h>
33 #include <xmltooling/util/NDC.h>
34 #include <xmltooling/util/StorageService.h>
35 #include <xmltooling/util/XMLHelper.h>
36 #include <xmltooling/util/Threads.h>
38 using namespace opensaml;
39 using namespace xmltooling::logging;
40 using namespace xmltooling;
44 // In-memory storage of mappings instead of using storage API.
45 class SAML_DLLLOCAL ArtifactMappings
48 ArtifactMappings() : m_lock(Mutex::create()) {}
51 for (map<string,Mapping>::iterator i=m_artMap.begin(); i!=m_artMap.end(); ++i)
52 delete i->second.m_xml;
54 void storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty, int TTL);
55 XMLObject* retrieveContent(const SAMLArtifact* artifact, const char* relyingParty);
56 string getRelyingParty(const SAMLArtifact* artifact);
59 struct SAML_DLLLOCAL Mapping {
60 Mapping() : m_xml(NULL), m_expires(0) {}
66 void removeMapping(const map<string,Mapping>::iterator& i);
69 map<string,Mapping> m_artMap;
70 multimap<time_t,string> m_expMap;
73 static const XMLCh artifactTTL[] = UNICODE_LITERAL_11(a,r,t,i,f,a,c,t,T,T,L);
74 static const XMLCh context[] = UNICODE_LITERAL_7(c,o,n,t,e,x,t);
75 static const XMLCh Mapping[] = UNICODE_LITERAL_7(M,a,p,p,i,n,g);
76 static const XMLCh _relyingParty[] = UNICODE_LITERAL_12(r,e,l,y,i,n,g,P,a,r,t,y);
79 void ArtifactMappings::removeMapping(const map<string,Mapping>::iterator& i)
81 // Update secondary map.
82 pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
83 m_expMap.equal_range(i->second.m_expires);
84 for (; range.first != range.second; ++range.first) {
85 if (range.first->second == i->first) {
86 m_expMap.erase(range.first);
90 delete i->second.m_xml;
94 void ArtifactMappings::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty, int TTL)
98 // Garbage collect any expired artifacts.
99 time_t now=time(NULL);
100 multimap<time_t,string>::iterator stop=m_expMap.upper_bound(now);
101 for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {
102 delete m_artMap[i->second].m_xml;
103 m_artMap.erase(i->second);
106 // Key is the hexed handle.
107 string hexed = SAMLArtifact::toHex(artifact->getMessageHandle());
108 Mapping& m = m_artMap[hexed];
111 m.m_relying = relyingParty;
112 m.m_expires = now + TTL;
113 m_expMap.insert(pair<const time_t,string>(m.m_expires,hexed));
116 XMLObject* ArtifactMappings::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
118 Category& log=Category::getInstance(SAML_LOGCAT".ArtifactMap");
119 Lock wrapper(m_lock);
121 map<string,Mapping>::iterator i=m_artMap.find(SAMLArtifact::toHex(artifact->getMessageHandle()));
122 if (i==m_artMap.end())
123 throw BindingException("Requested artifact not in map or may have expired.");
125 if (!(i->second.m_relying.empty())) {
126 if (!relyingParty || i->second.m_relying != relyingParty) {
128 "request from (%s) for artifact issued to (%s)",
129 relyingParty ? relyingParty : "unknown", i->second.m_relying.c_str()
132 throw BindingException("Unauthorized artifact mapping request.");
136 if (time(NULL) >= i->second.m_expires) {
138 throw BindingException("Requested artifact has expired.");
141 log.debug("resolved artifact for (%s)", relyingParty ? relyingParty : "unknown");
142 XMLObject* ret = i->second.m_xml;
143 i->second.m_xml = NULL; // clear member so it doesn't get deleted
148 string ArtifactMappings::getRelyingParty(const SAMLArtifact* artifact)
150 map<string,Mapping>::iterator i=m_artMap.find(SAMLArtifact::toHex(artifact->getMessageHandle()));
151 if (i==m_artMap.end())
152 throw BindingException("Requested artifact not in map or may have expired.");
153 return i->second.m_relying;
156 ArtifactMap::ArtifactMap(xmltooling::StorageService* storage, const char* context, unsigned int artifactTTL)
157 : m_storage(storage), m_context((context && *context) ? context : "opensaml::ArtifactMap"), m_mappings(NULL), m_artifactTTL(artifactTTL)
160 m_mappings = new ArtifactMappings();
163 ArtifactMap::ArtifactMap(const DOMElement* e, xmltooling::StorageService* storage)
164 : m_storage(storage), m_mappings(NULL), m_artifactTTL(180)
167 auto_ptr_char c(e->getAttributeNS(NULL, context));
168 if (c.get() && *c.get())
171 m_context = "opensaml::ArtifactMap";
173 const XMLCh* TTL = e->getAttributeNS(NULL, artifactTTL);
175 m_artifactTTL = XMLString::parseInt(TTL);
182 m_mappings = new ArtifactMappings();
185 ArtifactMap::~ArtifactMap()
190 void ArtifactMap::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty)
192 if (content->getParent())
193 throw BindingException("Cannot store artifact mapping for XML content with parent.");
195 return m_mappings->storeContent(content, artifact, relyingParty, m_artifactTTL);
197 // Marshall with defaulted document, to reuse existing DOM and/or create a bound Document.
198 DOMElement* root = content->marshall();
200 // Build a DOM with the same document to store the relyingParty mapping.
202 auto_ptr_XMLCh temp(relyingParty);
203 root = root->getOwnerDocument()->createElementNS(NULL,Mapping);
204 root->setAttributeNS(NULL,_relyingParty,temp.get());
205 root->appendChild(content->getDOM());
208 // Serialize the root element, whatever it is, for storage.
210 XMLHelper::serialize(root, xmlbuf);
211 if (!m_storage->createText(
213 SAMLArtifact::toHex(artifact->getMessageHandle()).c_str(),
215 time(NULL) + m_artifactTTL
217 throw IOException("Attempt to insert duplicate artifact into map.");
220 // Cleanup by destroying XML.
224 XMLObject* ArtifactMap::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
227 xmltooling::NDC ndc("retrieveContent");
229 Category& log=Category::getInstance(SAML_LOGCAT".ArtifactMap");
232 return m_mappings->retrieveContent(artifact, relyingParty);
234 // Read the mapping and then delete it.
236 string key = SAMLArtifact::toHex(artifact->getMessageHandle());
237 if (!m_storage->readText(m_context.c_str(), key.c_str(), &xmlbuf))
238 throw BindingException("Artifact not found in mapping database.");
239 m_storage->deleteText(m_context.c_str(), key.c_str());
241 // Parse the data back into XML.
242 istringstream is(xmlbuf);
243 DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(is);
244 XercesJanitor<DOMDocument> janitor(doc);
246 // Check the root element.
247 DOMElement* messageRoot = doc->getDocumentElement();
248 if (XMLHelper::isNodeNamed(messageRoot, NULL, Mapping)) {
249 auto_ptr_char temp(messageRoot->getAttributeNS(NULL,_relyingParty));
250 if (!relyingParty || strcmp(temp.get(),relyingParty)) {
251 log.warn("request from (%s) for artifact issued to (%s)", relyingParty ? relyingParty : "unknown", temp.get());
252 throw BindingException("Unauthorized artifact mapping request.");
254 messageRoot = XMLHelper::getFirstChildElement(messageRoot);
258 XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(messageRoot, true); // bind document
261 log.debug("resolved artifact for (%s)", relyingParty ? relyingParty : "unknown");
265 string ArtifactMap::getRelyingParty(const SAMLArtifact* artifact)
268 return m_mappings->getRelyingParty(artifact);
271 if (!m_storage->readText(m_context.c_str(), SAMLArtifact::toHex(artifact->getMessageHandle()).c_str(), &xmlbuf))
272 throw BindingException("Artifact not found in mapping database.");
274 // Parse the data back into XML.
275 istringstream is(xmlbuf);
276 DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(is);
277 XercesJanitor<DOMDocument> janitor(doc);
279 // Check the root element.
280 DOMElement* messageRoot = doc->getDocumentElement();
281 if (XMLHelper::isNodeNamed(messageRoot, NULL, Mapping)) {
282 auto_ptr_char temp(messageRoot->getAttributeNS(NULL,_relyingParty));