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