Set fourth file version digit to signify rebuild.
[shibboleth/cpp-opensaml.git] / saml / binding / impl / ArtifactMap.cpp
1 /**
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.
6  *
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
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
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.
19  */
20
21 /**
22  * ArtifactMap.cpp
23  * 
24  * Helper class for SAMLArtifact mapping and retrieval. 
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "binding/ArtifactMap.h"
30 #include "binding/SAMLArtifact.h"
31
32 #include <ctime>
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>
45
46 using namespace opensaml;
47 using namespace xmltooling::logging;
48 using namespace xmltooling;
49 using namespace boost::lambda;
50 using namespace boost;
51 using namespace std;
52
53 namespace opensaml {
54     // In-memory storage of mappings instead of using storage API.
55     class SAML_DLLLOCAL ArtifactMappings
56     {
57     public:
58         ArtifactMappings() : m_lock(Mutex::create()) {}
59         ~ArtifactMappings() {}
60
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);
64     
65     private:
66         struct SAML_DLLLOCAL Mapping {
67             Mapping() : m_xml(nullptr), m_expires(0) {}
68             ~Mapping() {
69                 delete m_xml;
70             }
71             XMLObject* m_xml;
72             string m_relying;
73             time_t m_expires;
74         };
75
76         void removeMapping(const map<string,Mapping>::iterator& i);
77         
78         auto_ptr<Mutex> m_lock;
79         map<string,Mapping> m_artMap;
80         multimap<time_t,string> m_expMap;
81     };
82
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);
87 };
88
89 void ArtifactMappings::removeMapping(const map<string,Mapping>::iterator& i)
90 {
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);
94
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))
99         );
100     if (el != range.second) {
101         m_expMap.erase(el);
102     }
103
104     m_artMap.erase(i);
105 }
106
107 void ArtifactMappings::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty, int TTL)
108 {
109     Lock wrapper(m_lock);
110
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);
117     }
118     
119     // Key is the hexed handle.
120     string hexed = SAMLArtifact::toHex(artifact->getMessageHandle());
121     Mapping& m = m_artMap[hexed];
122     m.m_xml = content;
123     if (relyingParty)
124         m.m_relying = relyingParty;
125     m.m_expires = now + TTL;
126     m_expMap.insert(pair<const time_t, string>(m.m_expires, hexed));
127 }
128
129 XMLObject* ArtifactMappings::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
130 {
131     Category& log=Category::getInstance(SAML_LOGCAT ".ArtifactMap");
132     Lock wrapper(m_lock);
133
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.");
137     
138     if (!(i->second.m_relying.empty())) {
139         if (!relyingParty || i->second.m_relying != relyingParty) {
140             log.warn(
141                 "request from (%s) for artifact issued to (%s)",
142                 relyingParty ? relyingParty : "unknown", i->second.m_relying.c_str()
143                 );
144             removeMapping(i);
145             throw BindingException("Unauthorized artifact mapping request.");
146         }
147     }
148     
149     if (time(nullptr) >= i->second.m_expires) {
150         removeMapping(i);
151         throw BindingException("Requested artifact has expired.");
152     }
153     
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
157     removeMapping(i);
158     return ret;
159 }
160
161 string ArtifactMappings::getRelyingParty(const SAMLArtifact* artifact)
162 {
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;
167 }
168
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)
171 {
172     if (!m_storage)
173         m_mappings.reset(new ArtifactMappings());
174 }
175
176 ArtifactMap::ArtifactMap(const DOMElement* e, xmltooling::StorageService* storage)
177     : m_storage(storage), m_artifactTTL(180)
178 {
179     if (e) {
180         auto_ptr_char c(e->getAttributeNS(nullptr, context));
181         if (c.get() && *c.get()) {
182             m_context = c.get();
183             if (storage && m_context.length() > m_storage->getCapabilities().getContextSize()) {
184                 throw IOException("ArtifactMap context length exceeds capacity of storage service.");
185             }
186         }
187         else {
188             m_context = "opensaml::ArtifactMap";
189         }
190
191         const XMLCh* TTL = e->getAttributeNS(nullptr, artifactTTL);
192         if (TTL) {
193             try {
194                 m_artifactTTL = XMLString::parseInt(TTL);
195             }
196             catch (XMLException&) {
197                 m_artifactTTL = 0;
198             }
199             if (!m_artifactTTL)
200                 m_artifactTTL = 180;
201         }
202     }
203     
204     if (!m_storage)
205         m_mappings.reset(new ArtifactMappings());
206 }
207
208 ArtifactMap::~ArtifactMap()
209 {
210 }
211
212 void ArtifactMap::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty)
213 {
214     if (content->getParent())
215         throw BindingException("Cannot store artifact mapping for XML content with parent.");
216     else if (!m_storage)
217         return m_mappings->storeContent(content, artifact, relyingParty, m_artifactTTL);
218     
219     // Marshall with defaulted document, to reuse existing DOM and/or create a bound Document.
220     DOMElement* root = content->marshall();
221     
222     // Build a DOM with the same document to store the relyingParty mapping.
223     if (relyingParty) {
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());
228     }
229     
230     // Serialize the root element, whatever it is, for storage.
231     string xmlbuf;
232     XMLHelper::serialize(root, xmlbuf);
233
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());
238     else
239         key = SAMLArtifact::toHex(key);
240
241     if (!m_storage->createText(
242         m_context.c_str(),
243         key.c_str(),
244         xmlbuf.c_str(),
245         time(nullptr) + m_artifactTTL
246         )) {
247         throw IOException("Attempt to insert duplicate artifact into map.");
248     }
249         
250     // Cleanup by destroying XML.
251     delete content;
252 }
253
254 XMLObject* ArtifactMap::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
255 {
256 #ifdef _DEBUG
257     xmltooling::NDC ndc("retrieveContent");
258 #endif
259     Category& log=Category::getInstance(SAML_LOGCAT ".ArtifactMap");
260
261     if (!m_storage)
262         return m_mappings->retrieveContent(artifact, relyingParty);
263
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());
268     else
269         key = SAMLArtifact::toHex(key);
270
271     // Read the mapping and then delete it.
272     string xmlbuf;
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());
276     
277     // Parse the data back into XML.
278     istringstream is(xmlbuf);
279     DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(is);
280     XercesJanitor<DOMDocument> janitor(doc);
281     
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.");
289         }
290         messageRoot = XMLHelper::getFirstChildElement(messageRoot);
291     }
292     
293     // Unmarshall...
294     XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(messageRoot, true);    // bind document
295     janitor.release();
296     
297     log.debug("resolved artifact for (%s)", relyingParty ? relyingParty : "unknown");
298     return xmlObject;
299 }
300
301 string ArtifactMap::getRelyingParty(const SAMLArtifact* artifact)
302 {
303     if (!m_storage)
304         return m_mappings->getRelyingParty(artifact);
305
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());
310     else
311         key = SAMLArtifact::toHex(key);
312
313     string xmlbuf;
314     if (!m_storage->readText(m_context.c_str(), key.c_str(), &xmlbuf))
315         throw BindingException("Artifact not found in mapping database.");
316     
317     // Parse the data back into XML.
318     istringstream is(xmlbuf);
319     DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(is);
320     XercesJanitor<DOMDocument> janitor(doc);
321     
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));
326         return temp.get();
327     }
328     return string();
329 }