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