53b1560b779e116d7787b4867b7932834991f1e0
[shibboleth/cpp-opensaml.git] / saml / binding / impl / ArtifactMap.cpp
1 /*
2  *  Copyright 2001-2009 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  * ArtifactMap.cpp
19  * 
20  * Helper class for SAMLArtifact mapping and retrieval. 
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "binding/ArtifactMap.h"
26 #include "binding/SAMLArtifact.h"
27
28 #include <ctime>
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/ParserPool.h>
35 #include <xmltooling/util/StorageService.h>
36 #include <xmltooling/util/XMLHelper.h>
37 #include <xmltooling/util/Threads.h>
38
39 using namespace opensaml;
40 using namespace xmltooling::logging;
41 using namespace xmltooling;
42 using namespace std;
43
44 namespace opensaml {
45     // In-memory storage of mappings instead of using storage API.
46     class SAML_DLLLOCAL ArtifactMappings
47     {
48     public:
49         ArtifactMappings() : m_lock(Mutex::create()) {}
50         ~ArtifactMappings() {
51             delete m_lock;
52             for (map<string,Mapping>::iterator i=m_artMap.begin(); i!=m_artMap.end(); ++i)
53                 delete i->second.m_xml;
54         }
55         void storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty, int TTL);
56         XMLObject* retrieveContent(const SAMLArtifact* artifact, const char* relyingParty);
57         string getRelyingParty(const SAMLArtifact* artifact);
58     
59     private:
60         struct SAML_DLLLOCAL Mapping {
61             Mapping() : m_xml(NULL), m_expires(0) {}
62             XMLObject* m_xml;
63             string m_relying;
64             time_t m_expires;
65         };
66
67         void removeMapping(const map<string,Mapping>::iterator& i);
68         
69         Mutex* m_lock;
70         map<string,Mapping> m_artMap;
71         multimap<time_t,string> m_expMap;
72     };
73
74     static const XMLCh artifactTTL[] =  UNICODE_LITERAL_11(a,r,t,i,f,a,c,t,T,T,L);
75     static const XMLCh context[] =      UNICODE_LITERAL_7(c,o,n,t,e,x,t);
76     static const XMLCh Mapping[] =      UNICODE_LITERAL_7(M,a,p,p,i,n,g);
77     static const XMLCh _relyingParty[] = UNICODE_LITERAL_12(r,e,l,y,i,n,g,P,a,r,t,y);
78 };
79
80 void ArtifactMappings::removeMapping(const map<string,Mapping>::iterator& i)
81 {
82     // Update secondary map.
83     pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
84         m_expMap.equal_range(i->second.m_expires);
85     for (; range.first != range.second; ++range.first) {
86         if (range.first->second == i->first) {
87             m_expMap.erase(range.first);
88             break;
89         }
90     }
91     delete i->second.m_xml;
92     m_artMap.erase(i);
93 }
94
95 void ArtifactMappings::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty, int TTL)
96 {
97     Lock wrapper(m_lock);
98
99     // Garbage collect any expired artifacts.
100     time_t now=time(NULL);
101     multimap<time_t,string>::iterator stop=m_expMap.upper_bound(now);
102     for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {
103         delete m_artMap[i->second].m_xml;
104         m_artMap.erase(i->second);
105     }
106     
107     // Key is the hexed handle.
108     string hexed = SAMLArtifact::toHex(artifact->getMessageHandle());
109     Mapping& m = m_artMap[hexed];
110     m.m_xml = content;
111     if (relyingParty)
112         m.m_relying = relyingParty;
113     m.m_expires = now + TTL;
114     m_expMap.insert(pair<const time_t,string>(m.m_expires,hexed));
115 }
116
117 XMLObject* ArtifactMappings::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
118 {
119     Category& log=Category::getInstance(SAML_LOGCAT".ArtifactMap");
120     Lock wrapper(m_lock);
121
122     map<string,Mapping>::iterator i=m_artMap.find(SAMLArtifact::toHex(artifact->getMessageHandle()));
123     if (i==m_artMap.end())
124         throw BindingException("Requested artifact not in map or may have expired.");
125     
126     if (!(i->second.m_relying.empty())) {
127         if (!relyingParty || i->second.m_relying != relyingParty) {
128             log.warn(
129                 "request from (%s) for artifact issued to (%s)",
130                 relyingParty ? relyingParty : "unknown", i->second.m_relying.c_str()
131                 );
132             removeMapping(i);
133             throw BindingException("Unauthorized artifact mapping request.");
134         }
135     }
136     
137     if (time(NULL) >= i->second.m_expires) {
138         removeMapping(i);
139         throw BindingException("Requested artifact has expired.");
140     }
141     
142     log.debug("resolved artifact for (%s)", relyingParty ? relyingParty : "unknown");
143     XMLObject* ret = i->second.m_xml;
144     i->second.m_xml = NULL; // clear member so it doesn't get deleted
145     removeMapping(i);
146     return ret;
147 }
148
149 string ArtifactMappings::getRelyingParty(const SAMLArtifact* artifact)
150 {
151     map<string,Mapping>::iterator i=m_artMap.find(SAMLArtifact::toHex(artifact->getMessageHandle()));
152     if (i==m_artMap.end())
153         throw BindingException("Requested artifact not in map or may have expired.");
154     return i->second.m_relying;
155 }
156
157 ArtifactMap::ArtifactMap(xmltooling::StorageService* storage, const char* context, unsigned int artifactTTL)
158     : m_storage(storage), m_context((context && *context) ? context : "opensaml::ArtifactMap"), m_mappings(NULL), m_artifactTTL(artifactTTL)
159 {
160     if (!m_storage)
161         m_mappings = new ArtifactMappings();
162 }
163
164 ArtifactMap::ArtifactMap(const DOMElement* e, xmltooling::StorageService* storage)
165     : m_storage(storage), m_mappings(NULL), m_artifactTTL(180)
166 {
167     if (e) {
168         auto_ptr_char c(e->getAttributeNS(NULL, context));
169         if (c.get() && *c.get())
170             m_context = c.get();
171         else
172             m_context = "opensaml::ArtifactMap";
173         
174         const XMLCh* TTL = e->getAttributeNS(NULL, artifactTTL);
175         if (TTL) {
176             m_artifactTTL = XMLString::parseInt(TTL);
177             if (!m_artifactTTL)
178                 m_artifactTTL = 180;
179         }
180     }
181     
182     if (!m_storage)
183         m_mappings = new ArtifactMappings();
184 }
185
186 ArtifactMap::~ArtifactMap()
187 {
188     delete m_mappings;
189 }
190
191 void ArtifactMap::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty)
192 {
193     if (content->getParent())
194         throw BindingException("Cannot store artifact mapping for XML content with parent.");
195     else if (!m_storage)
196         return m_mappings->storeContent(content, artifact, relyingParty, m_artifactTTL);
197     
198     // Marshall with defaulted document, to reuse existing DOM and/or create a bound Document.
199     DOMElement* root = content->marshall();
200     
201     // Build a DOM with the same document to store the relyingParty mapping.
202     if (relyingParty) {
203         auto_ptr_XMLCh temp(relyingParty);
204         root = root->getOwnerDocument()->createElementNS(NULL,Mapping);
205         root->setAttributeNS(NULL,_relyingParty,temp.get());
206         root->appendChild(content->getDOM());
207     }
208     
209     // Serialize the root element, whatever it is, for storage.
210     string xmlbuf;
211     XMLHelper::serialize(root, xmlbuf);
212     if (!m_storage->createText(
213         m_context.c_str(),
214         SAMLArtifact::toHex(artifact->getMessageHandle()).c_str(),
215         xmlbuf.c_str(),
216         time(NULL) + m_artifactTTL
217         )) {
218         throw IOException("Attempt to insert duplicate artifact into map.");
219     }
220         
221     // Cleanup by destroying XML.
222     delete content;
223 }
224
225 XMLObject* ArtifactMap::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
226 {
227 #ifdef _DEBUG
228     xmltooling::NDC ndc("retrieveContent");
229 #endif
230     Category& log=Category::getInstance(SAML_LOGCAT".ArtifactMap");
231
232     if (!m_storage)
233         return m_mappings->retrieveContent(artifact, relyingParty);
234     
235     // Read the mapping and then delete it.
236     string xmlbuf;
237     string key = SAMLArtifact::toHex(artifact->getMessageHandle());
238     if (!m_storage->readText(m_context.c_str(), key.c_str(), &xmlbuf))
239         throw BindingException("Artifact not found in mapping database.");
240     m_storage->deleteText(m_context.c_str(), key.c_str());
241     
242     // Parse the data back into XML.
243     istringstream is(xmlbuf);
244     DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(is);
245     XercesJanitor<DOMDocument> janitor(doc);
246     
247     // Check the root element.
248     DOMElement* messageRoot = doc->getDocumentElement();
249     if (XMLHelper::isNodeNamed(messageRoot, NULL, Mapping)) {
250         auto_ptr_char temp(messageRoot->getAttributeNS(NULL,_relyingParty));
251         if (!relyingParty || strcmp(temp.get(),relyingParty)) {
252             log.warn("request from (%s) for artifact issued to (%s)", relyingParty ? relyingParty : "unknown", temp.get());
253             throw BindingException("Unauthorized artifact mapping request.");
254         }
255         messageRoot = XMLHelper::getFirstChildElement(messageRoot);
256     }
257     
258     // Unmarshall...
259     XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(messageRoot, true);    // bind document
260     janitor.release();
261     
262     log.debug("resolved artifact for (%s)", relyingParty ? relyingParty : "unknown");
263     return xmlObject;
264 }
265
266 string ArtifactMap::getRelyingParty(const SAMLArtifact* artifact)
267 {
268     if (!m_storage)
269         return m_mappings->getRelyingParty(artifact);
270     
271     string xmlbuf;
272     if (!m_storage->readText(m_context.c_str(), SAMLArtifact::toHex(artifact->getMessageHandle()).c_str(), &xmlbuf))
273         throw BindingException("Artifact not found in mapping database.");
274     
275     // Parse the data back into XML.
276     istringstream is(xmlbuf);
277     DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(is);
278     XercesJanitor<DOMDocument> janitor(doc);
279     
280     // Check the root element.
281     DOMElement* messageRoot = doc->getDocumentElement();
282     if (XMLHelper::isNodeNamed(messageRoot, NULL, Mapping)) {
283         auto_ptr_char temp(messageRoot->getAttributeNS(NULL,_relyingParty));
284         return temp.get();
285     }
286     return string();
287 }