MessageEncoder, ArtifactMap, and SAML 1.x encoder classes.
[shibboleth/cpp-opensaml.git] / saml / binding / impl / ArtifactMap.cpp
1 /*
2  *  Copyright 2001-2006 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 <log4cpp/Category.hh>
29 #include <xercesc/util/XMLUniDefs.hpp>
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;
36 using namespace log4cpp;
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     
53     private:
54         struct SAML_DLLLOCAL Mapping {
55             Mapping() : m_xml(NULL), m_expires(0) {}
56             XMLObject* m_xml;
57             string m_relying;
58             time_t m_expires;
59         };
60
61         void removeMapping(const map<string,Mapping>::iterator& i);
62         
63         Mutex* m_lock;
64         map<string,Mapping> m_artMap;
65         multimap<time_t,string> m_expMap;
66     };
67 };
68
69 void ArtifactMappings::removeMapping(const map<string,Mapping>::iterator& i)
70 {
71     // Update secondary map.
72     pair<multimap<time_t,string>::iterator,multimap<time_t,string>::iterator> range =
73         m_expMap.equal_range(i->second.m_expires);
74     for (; range.first != range.second; ++range.first) {
75         if (range.first->second == i->first) {
76             m_expMap.erase(range.first);
77             break;
78         }
79     }
80     delete i->second.m_xml;
81     m_artMap.erase(i);
82 }
83
84 void ArtifactMappings::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty, int TTL)
85 {
86     Lock wrapper(m_lock);
87
88     // Garbage collect any expired artifacts.
89     time_t now=time(NULL);
90     multimap<time_t,string>::iterator stop=m_expMap.upper_bound(now);
91     for (multimap<time_t,string>::iterator i=m_expMap.begin(); i!=stop; m_expMap.erase(i++)) {
92         delete m_artMap[i->second].m_xml;
93         m_artMap.erase(i->second);
94     }
95     
96     // Key is the hexed handle.
97     string hexed = SAMLArtifact::toHex(artifact->getMessageHandle());
98     Mapping& m = m_artMap[hexed];
99     m.m_xml = content;
100     if (relyingParty)
101         m.m_relying = relyingParty;
102     m.m_expires = now + TTL;
103     m_expMap.insert(make_pair(m.m_expires,hexed));
104 }
105
106 XMLObject* ArtifactMappings::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
107 {
108     Category& log=Category::getInstance(SAML_LOGCAT".ArtifactMap");
109     Lock wrapper(m_lock);
110
111     map<string,Mapping>::iterator i=m_artMap.find(SAMLArtifact::toHex(artifact->getMessageHandle()));
112     if (i==m_artMap.end())
113         throw BindingException("Requested artifact not in map or may have expired.");
114     
115     if (!(i->second.m_relying.empty())) {
116         if (!relyingParty || i->second.m_relying != relyingParty) {
117             log.warn(
118                 "request from (%s) for artifact issued to (%s)",
119                 relyingParty ? relyingParty : "unknown", i->second.m_relying.c_str()
120                 );
121             removeMapping(i);
122             throw BindingException("Unauthorized artifact mapping request.");
123         }
124     }
125     
126     if (time(NULL) >= i->second.m_expires) {
127         removeMapping(i);
128         throw BindingException("Requested artifact has expired.");
129     }
130     
131     log.debug("resolved artifact for (%s)", relyingParty ? relyingParty : "unknown");
132     XMLObject* ret = i->second.m_xml;
133     i->second.m_xml = NULL; // clear member so it doesn't get deleted
134     removeMapping(i);
135     return ret;
136 }
137
138 ArtifactMap::ArtifactMap(xmltooling::StorageService* storage, const char* context, int artifactTTL)
139     : m_storage(storage), m_context(context ? context : "opensaml::ArtifactMap"), m_mappings(NULL), m_artifactTTL(artifactTTL)
140 {
141     if (!m_storage)
142         m_mappings = new ArtifactMappings();
143 }
144
145 ArtifactMap::~ArtifactMap()
146 {
147     delete m_mappings;
148 }
149
150 static const XMLCh M[] = UNICODE_LITERAL_7(M,a,p,p,i,n,g);
151 static const XMLCh RP[] = UNICODE_LITERAL_12(r,e,l,y,i,n,g,P,a,r,t,y);
152
153 void ArtifactMap::storeContent(XMLObject* content, const SAMLArtifact* artifact, const char* relyingParty)
154 {
155     if (content->getParent())
156         throw BindingException("Cannot store artifact mapping for XML content with parent.");
157     else if (!m_storage)
158         return m_mappings->storeContent(content, artifact, relyingParty, m_artifactTTL);
159     
160     // Marshall with defaulted document, to reuse existing DOM and/or create a bound Document.
161     DOMElement* root = content->marshall();
162     
163     // Build a DOM with the same document to store the relyingParty mapping.
164     if (relyingParty) {
165         auto_ptr_XMLCh temp(relyingParty);
166         root = root->getOwnerDocument()->createElementNS(NULL,M);
167         root->setAttributeNS(NULL,RP,temp.get());
168         root->appendChild(content->getDOM());
169     }
170     
171     // Serialize the root element, whatever it is, for storage.
172     string xmlbuf;
173     XMLHelper::serialize(root, xmlbuf);
174     m_storage->createText(
175         m_context.c_str(), SAMLArtifact::toHex(artifact->getMessageHandle()).c_str(), xmlbuf.c_str(), time(NULL) + m_artifactTTL
176         );
177         
178     // Cleanup by destroying XML.
179     delete content;
180 }
181
182 XMLObject* ArtifactMap::retrieveContent(const SAMLArtifact* artifact, const char* relyingParty)
183 {
184 #ifdef _DEBUG
185     xmltooling::NDC ndc("retrieveContent");
186 #endif
187
188     if (!m_storage)
189         return m_mappings->retrieveContent(artifact, relyingParty);
190     
191     string xmlbuf;
192     string key = SAMLArtifact::toHex(artifact->getMessageHandle());
193     if (!m_storage->readText(m_context.c_str(), key.c_str(), &xmlbuf))
194         throw BindingException("Artifact not found in mapping database.");
195     
196     istringstream is(xmlbuf);
197     DOMDocument* doc=XMLToolingConfig::getConfig().getParser().parse(is);
198     XercesJanitor<DOMDocument> janitor(doc);
199
200     Category& log=Category::getInstance(SAML_LOGCAT".ArtifactMap");
201     m_storage->deleteText(m_context.c_str(), key.c_str());
202     
203     // Check the root element.
204     DOMElement* messageRoot = doc->getDocumentElement();
205     if (XMLHelper::isNodeNamed(messageRoot, NULL, M)) {
206         auto_ptr_char temp(messageRoot->getAttributeNS(NULL,RP));
207         if (!relyingParty || strcmp(temp.get(),relyingParty)) {
208             log.warn("request from (%s) for artifact issued to (%s)", relyingParty ? relyingParty : "unknown", temp.get());
209             throw BindingException("Unauthorized artifact mapping request.");
210         }
211         messageRoot = XMLHelper::getFirstChildElement(messageRoot);
212     }
213     
214     // Unmarshall...
215     XMLObject* xmlObject = XMLObjectBuilder::buildOneFromElement(messageRoot, true);    // bind document
216     janitor.release();
217     
218     log.debug("resolved artifact for (%s)", relyingParty ? relyingParty : "unknown");
219     return xmlObject;
220 }