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