327a18f01d8654fd9c62f70dd5bb33a52367ffa0
[shibboleth/sp.git] / shibsp / binding / impl / XMLProtocolProvider.cpp
1 /*\r
2  *  Copyright 2010 Internet2\r
3  *\r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *     http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 /**\r
18  * XMLProtocolProvider.cpp\r
19  *\r
20  * XML-based protocol provider.\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "exceptions.h"\r
25 #include "binding/ProtocolProvider.h"\r
26 #include "util/DOMPropertySet.h"\r
27 #include "util/SPConstants.h"\r
28 \r
29 #include <map>\r
30 #include <xmltooling/io/HTTPResponse.h>\r
31 #include <xmltooling/util/NDC.h>\r
32 #include <xmltooling/util/ReloadableXMLFile.h>\r
33 #include <xmltooling/util/Threads.h>\r
34 #include <xmltooling/util/XMLHelper.h>\r
35 #include <xercesc/util/XMLUniDefs.hpp>\r
36 \r
37 using shibspconstants::SHIB2SPPROTOCOLS_NS;\r
38 using namespace shibsp;\r
39 using namespace xmltooling;\r
40 using namespace std;\r
41 \r
42 namespace shibsp {\r
43 \r
44     static const XMLCh _id[] =                  UNICODE_LITERAL_2(i,d);\r
45     static const XMLCh Binding[] =              UNICODE_LITERAL_7(B,i,n,d,i,n,g);\r
46     static const XMLCh Protocol[] =             UNICODE_LITERAL_8(P,r,o,t,o,c,o,l);\r
47     static const XMLCh Protocols[] =            UNICODE_LITERAL_9(P,r,o,t,o,c,o,l,s);\r
48     static const XMLCh Service[] =              UNICODE_LITERAL_7(S,e,r,v,i,c,e);\r
49 \r
50 #if defined (_MSC_VER)\r
51     #pragma warning( push )\r
52     #pragma warning( disable : 4250 )\r
53 #endif\r
54 \r
55     class SHIBSP_DLLLOCAL XMLProtocolProviderImpl : public DOMNodeFilter, DOMPropertySet\r
56     {\r
57     public:\r
58         XMLProtocolProviderImpl(const DOMElement* e, Category& log);\r
59         ~XMLProtocolProviderImpl() {\r
60             for (protmap_t::iterator i = m_map.begin(); i != m_map.end(); ++i) {\r
61                 delete i->second.first;\r
62                 for_each(i->second.second.begin(), i->second.second.end(), xmltooling::cleanup<PropertySet>());\r
63             }\r
64             if (m_document)\r
65                 m_document->release();\r
66         }\r
67 \r
68         void setDocument(DOMDocument* doc) {\r
69             m_document = doc;\r
70         }\r
71 \r
72 #ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE\r
73         short\r
74 #else\r
75         FilterAction\r
76 #endif\r
77         acceptNode(const DOMNode* node) const {\r
78             return FILTER_REJECT;\r
79         }\r
80 \r
81     private:\r
82         DOMDocument* m_document;\r
83         // Map of protocol/service pair to a service propset plus an array of Binding propsets.\r
84         typedef map< pair<string,string>, pair< PropertySet*,vector<const PropertySet*> > > protmap_t;\r
85         protmap_t m_map;\r
86 \r
87         friend class SHIBSP_DLLLOCAL XMLProtocolProvider;\r
88     };\r
89 \r
90     class XMLProtocolProvider : public ProtocolProvider, public ReloadableXMLFile\r
91     {\r
92     public:\r
93         XMLProtocolProvider(const DOMElement* e)\r
94                 : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".ProtocolProvider.XML")), m_impl(nullptr) {\r
95             background_load(); // guarantees an exception or the policy is loaded\r
96         }\r
97 \r
98         ~XMLProtocolProvider() {\r
99             shutdown();\r
100             delete m_impl;\r
101         }\r
102 \r
103         const PropertySet* getService(const char* protocol, const char* service) const {\r
104             XMLProtocolProviderImpl::protmap_t::const_iterator i = m_impl->m_map.find(pair<string,string>(protocol,service));\r
105             return (i != m_impl->m_map.end()) ? i->second.first : nullptr;\r
106         }\r
107 \r
108         const vector<const PropertySet*>& getBindings(const char* protocol, const char* service) const {\r
109             XMLProtocolProviderImpl::protmap_t::const_iterator i = m_impl->m_map.find(pair<string,string>(protocol,service));\r
110             if (i != m_impl->m_map.end())\r
111                 return i->second.second;\r
112             throw ConfigurationException("ProtocolProvider can't return bindings for undefined protocol and service.");\r
113         }\r
114 \r
115     protected:\r
116         pair<bool,DOMElement*> load(bool backup);\r
117         pair<bool,DOMElement*> background_load();\r
118 \r
119     private:\r
120         XMLProtocolProviderImpl* m_impl;\r
121     };\r
122 \r
123 #if defined (_MSC_VER)\r
124     #pragma warning( pop )\r
125 #endif\r
126 \r
127     ProtocolProvider* SHIBSP_DLLLOCAL XMLProtocolProviderFactory(const DOMElement* const & e)\r
128     {\r
129         return new XMLProtocolProvider(e);\r
130     }\r
131 }\r
132 \r
133 void SHIBSP_API shibsp::registerProtocolProviders()\r
134 {\r
135     SPConfig::getConfig().ProtocolProviderManager.registerFactory(XML_PROTOCOL_PROVIDER, XMLProtocolProviderFactory);\r
136 }\r
137 \r
138 ProtocolProvider::ProtocolProvider()\r
139 {\r
140 }\r
141 \r
142 ProtocolProvider::~ProtocolProvider()\r
143 {\r
144 }\r
145 \r
146 XMLProtocolProviderImpl::XMLProtocolProviderImpl(const DOMElement* e, Category& log) : m_document(nullptr)\r
147 {\r
148 #ifdef _DEBUG\r
149     xmltooling::NDC ndc("XMLProtocolProviderImpl");\r
150 #endif\r
151     //typedef map< pair<string,string>, pair< PropertySet*,vector<const PropertySet*> > > protmap_t;\r
152 \r
153     if (!XMLHelper::isNodeNamed(e, SHIB2SPPROTOCOLS_NS, Protocols))\r
154         throw ConfigurationException("XML ProtocolProvider requires prot:Protocols at root of configuration.");\r
155 \r
156     e = XMLHelper::getFirstChildElement(e, SHIB2SPPROTOCOLS_NS, Protocol);\r
157     while (e) {\r
158         string id = XMLHelper::getAttrString(e, nullptr, _id);\r
159         if (!id.empty()) {\r
160             const DOMElement* svc = XMLHelper::getFirstChildElement(e, SHIB2SPPROTOCOLS_NS, Service);\r
161             while (svc) {\r
162                 string svcid = XMLHelper::getAttrString(svc, nullptr, _id);\r
163                 if (!svcid.empty()) {\r
164                     pair< PropertySet*,vector<const PropertySet*> >& entry = m_map[make_pair(id,svcid)];\r
165                     if (!entry.first) {\r
166                         // Wrap the Service in a propset.\r
167                         DOMPropertySet* svcprop = new DOMPropertySet();\r
168                         entry.first = svcprop;\r
169                         svcprop->load(svc, &log, this);\r
170 \r
171                         // Walk the Bindings.\r
172                         const DOMElement* bind = XMLHelper::getFirstChildElement(svc, SHIB2SPPROTOCOLS_NS, Binding);\r
173                         while (bind) {\r
174                             DOMPropertySet* bindprop = new DOMPropertySet();\r
175                             entry.second.push_back(bindprop);\r
176                             bindprop->load(bind, &log, this);\r
177                             bind = XMLHelper::getNextSiblingElement(bind, SHIB2SPPROTOCOLS_NS, Binding);\r
178                         }\r
179                     }\r
180                 }\r
181                 svc = XMLHelper::getNextSiblingElement(svc, SHIB2SPPROTOCOLS_NS, Service);\r
182             }\r
183         }\r
184         e = XMLHelper::getNextSiblingElement(e, SHIB2SPPROTOCOLS_NS, Protocol);\r
185     }\r
186 }\r
187 \r
188 \r
189 pair<bool,DOMElement*> XMLProtocolProvider::load(bool backup)\r
190 {\r
191     // Load from source using base class.\r
192     pair<bool,DOMElement*> raw = ReloadableXMLFile::load(backup);\r
193 \r
194     // If we own it, wrap it.\r
195     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr);\r
196 \r
197     XMLProtocolProviderImpl* impl = new XMLProtocolProviderImpl(raw.second, m_log);\r
198 \r
199     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.\r
200     impl->setDocument(docjanitor.release());\r
201 \r
202     // Perform the swap inside a lock.\r
203     if (m_lock)\r
204         m_lock->wrlock();\r
205     SharedLock locker(m_lock, false);\r
206     delete m_impl;\r
207     m_impl = impl;\r
208 \r
209 \r
210     return make_pair(false,(DOMElement*)nullptr);\r
211 }\r
212 \r
213 pair<bool,DOMElement*> XMLProtocolProvider::background_load()\r
214 {\r
215     try {\r
216         return load(false);\r
217     }\r
218     catch (long& ex) {\r
219         if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED)\r
220             m_log.info("remote resource (%s) unchanged", m_source.c_str());\r
221         if (!m_loaded && !m_backing.empty())\r
222             return load(true);\r
223         throw;\r
224     }\r
225     catch (exception&) {\r
226         if (!m_loaded && !m_backing.empty())\r
227             return load(true);\r
228         throw;\r
229     }\r
230 }\r