5f0fdb0758adb7e5ff533944042f43648c7871bc
[shibboleth/sp.git] / shibsp / impl / XMLSecurityPolicyProvider.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  * XMLSecurityPolicyProvider.cpp\r
19  *\r
20  * XML-based security policy provider.\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "exceptions.h"\r
25 #include "Application.h"\r
26 #include "security/SecurityPolicy.h"\r
27 #include "security/SecurityPolicyProvider.h"\r
28 #include "util/DOMPropertySet.h"\r
29 #include "util/SPConstants.h"\r
30 \r
31 #include <map>\r
32 #include <saml/SAMLConfig.h>\r
33 #include <saml/binding/SecurityPolicyRule.h>\r
34 #include <xmltooling/io/HTTPResponse.h>\r
35 #include <xmltooling/util/NDC.h>\r
36 #include <xmltooling/util/ReloadableXMLFile.h>\r
37 #include <xmltooling/util/Threads.h>\r
38 #include <xmltooling/util/XMLHelper.h>\r
39 #include <xercesc/util/XMLStringTokenizer.hpp>\r
40 #include <xercesc/util/XMLUniDefs.hpp>\r
41 \r
42 using shibspconstants::SHIB2SPCONFIG_NS;\r
43 using opensaml::SAMLConfig;\r
44 using opensaml::SecurityPolicyRule;\r
45 using namespace shibsp;\r
46 using namespace xmltooling;\r
47 using namespace std;\r
48 \r
49 namespace shibsp {\r
50 \r
51 #if defined (_MSC_VER)\r
52     #pragma warning( push )\r
53     #pragma warning( disable : 4250 )\r
54 #endif\r
55 \r
56     class SHIBSP_DLLLOCAL XMLSecurityPolicyProviderImpl\r
57     {\r
58     public:\r
59         XMLSecurityPolicyProviderImpl(const DOMElement* e, Category& log);\r
60         ~XMLSecurityPolicyProviderImpl() {\r
61             for (map< string,pair<PropertySet*,vector<const SecurityPolicyRule*> > >::iterator i = m_policyMap.begin(); i != m_policyMap.end(); ++i) {\r
62                 delete i->second.first;\r
63                 for_each(i->second.second.begin(), i->second.second.end(), xmltooling::cleanup<SecurityPolicyRule>());\r
64             }\r
65             if (m_document)\r
66                 m_document->release();\r
67         }\r
68 \r
69         void setDocument(DOMDocument* doc) {\r
70             m_document = doc;\r
71         }\r
72 \r
73     private:\r
74         DOMDocument* m_document;\r
75         vector<xstring> m_whitelist,m_blacklist;\r
76         map< string,pair< PropertySet*,vector<const SecurityPolicyRule*> > > m_policyMap;\r
77 \r
78         friend class SHIBSP_DLLLOCAL XMLSecurityPolicyProvider;\r
79     };\r
80 \r
81     class XMLSecurityPolicyProvider : public SecurityPolicyProvider, public ReloadableXMLFile\r
82     {\r
83     public:\r
84         XMLSecurityPolicyProvider(const DOMElement* e)\r
85                 : ReloadableXMLFile(e, Category::getInstance(SHIBSP_LOGCAT".SecurityPolicyProvider.XML")), m_impl(nullptr) {\r
86             background_load(); // guarantees an exception or the policy is loaded\r
87         }\r
88 \r
89         ~XMLSecurityPolicyProvider() {\r
90             shutdown();\r
91             delete m_impl;\r
92         }\r
93 \r
94         const PropertySet* getPolicySettings(const char* id) const {\r
95             map<string,pair<PropertySet*,vector<const SecurityPolicyRule*> > >::const_iterator i = m_impl->m_policyMap.find(id);\r
96             if (i != m_impl->m_policyMap.end())\r
97                 return i->second.first;\r
98             throw ConfigurationException("Security Policy ($1) not found, check <SecurityPolicies> element.", params(1,id));\r
99         }\r
100 \r
101         const vector<const SecurityPolicyRule*>& getPolicyRules(const char* id) const {\r
102             map<string,pair<PropertySet*,vector<const SecurityPolicyRule*> > >::const_iterator i = m_impl->m_policyMap.find(id);\r
103             if (i != m_impl->m_policyMap.end())\r
104                 return i->second.second;\r
105             throw ConfigurationException("Security Policy ($1) not found, check <SecurityPolicies> element.", params(1,id));\r
106         }\r
107         const vector<xstring>& getAlgorithmBlacklist() const {\r
108             return m_impl->m_blacklist;\r
109         }\r
110         const vector<xstring>& getAlgorithmWhitelist() const {\r
111             return m_impl->m_whitelist;\r
112         }\r
113         \r
114     protected:\r
115         pair<bool,DOMElement*> load(bool backup);\r
116         pair<bool,DOMElement*> background_load();\r
117 \r
118     private:\r
119         XMLSecurityPolicyProviderImpl* m_impl;\r
120     };\r
121 \r
122 #if defined (_MSC_VER)\r
123     #pragma warning( pop )\r
124 #endif\r
125 \r
126     SecurityPolicyProvider* SHIBSP_DLLLOCAL XMLSecurityPolicyProviderFactory(const DOMElement* const & e)\r
127     {\r
128         return new XMLSecurityPolicyProvider(e);\r
129     }\r
130 \r
131     class SHIBSP_DLLLOCAL PolicyNodeFilter : public DOMNodeFilter\r
132     {\r
133     public:\r
134 #ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE\r
135         short\r
136 #else\r
137         FilterAction\r
138 #endif\r
139         acceptNode(const DOMNode* node) const {\r
140             return FILTER_REJECT;\r
141         }\r
142     };\r
143 \r
144     static const XMLCh _id[] =                  UNICODE_LITERAL_2(i,d);\r
145     static const XMLCh _type[] =                UNICODE_LITERAL_4(t,y,p,e);\r
146     static const XMLCh AlgorithmBlacklist[] =   UNICODE_LITERAL_18(A,l,g,o,r,i,t,h,m,B,l,a,c,k,l,i,s,t);\r
147     static const XMLCh AlgorithmWhitelist[] =   UNICODE_LITERAL_18(A,l,g,o,r,i,t,h,m,W,h,i,t,e,l,i,s,t);\r
148     static const XMLCh Policy[] =               UNICODE_LITERAL_6(P,o,l,i,c,y);\r
149     static const XMLCh PolicyRule[] =           UNICODE_LITERAL_10(P,o,l,i,c,y,R,u,l,e);\r
150     static const XMLCh Rule[] =                 UNICODE_LITERAL_4(R,u,l,e);\r
151     static const XMLCh SecurityPolicies[] =     UNICODE_LITERAL_16(S,e,c,u,r,i,t,y,P,o,l,i,c,i,e,s);\r
152 }\r
153 \r
154 void SHIBSP_API shibsp::registerSecurityPolicyProviders()\r
155 {\r
156     SPConfig::getConfig().SecurityPolicyProviderManager.registerFactory(XML_SECURITYPOLICY_PROVIDER, XMLSecurityPolicyProviderFactory);\r
157 }\r
158 \r
159 SecurityPolicyProvider::SecurityPolicyProvider()\r
160 {\r
161 }\r
162 \r
163 SecurityPolicyProvider::~SecurityPolicyProvider()\r
164 {\r
165 }\r
166 \r
167 opensaml::SecurityPolicy* SecurityPolicyProvider::createSecurityPolicy(\r
168     const Application& application, const xmltooling::QName* role, const char* policyId\r
169     ) const\r
170 {\r
171     pair<bool,bool> validate = getPolicySettings(policyId ? policyId : application.getString("policyId").second)->getBool("validate");\r
172     return new SecurityPolicy(application, role, (validate.first && validate.second), policyId);\r
173 }\r
174 \r
175 XMLSecurityPolicyProviderImpl::XMLSecurityPolicyProviderImpl(const DOMElement* e, Category& log) : m_document(nullptr)\r
176 {\r
177 #ifdef _DEBUG\r
178     xmltooling::NDC ndc("XMLSecurityPolicyProviderImpl");\r
179 #endif\r
180 \r
181     if (!XMLHelper::isNodeNamed(e, SHIB2SPCONFIG_NS, SecurityPolicies))\r
182         throw ConfigurationException("XML SecurityPolicyProvider requires conf:SecurityPolicies at root of configuration.");\r
183 \r
184     const XMLCh* algs = nullptr;\r
185     const DOMElement* alglist = XMLHelper::getLastChildElement(e, AlgorithmBlacklist);\r
186     if (alglist && alglist->hasChildNodes()) {\r
187         algs = alglist->getFirstChild()->getNodeValue();\r
188     }\r
189     else if ((alglist = XMLHelper::getLastChildElement(e, AlgorithmWhitelist)) && alglist->hasChildNodes()) {\r
190         algs = alglist->getFirstChild()->getNodeValue();\r
191     }\r
192     if (algs) {\r
193         const XMLCh* token;\r
194         XMLStringTokenizer tokenizer(algs);\r
195         while (tokenizer.hasMoreTokens()) {\r
196             token = tokenizer.nextToken();\r
197             if (token) {\r
198                 if (XMLString::equals(alglist->getLocalName(), AlgorithmBlacklist))\r
199                     m_blacklist.push_back(token);\r
200                 else\r
201                     m_whitelist.push_back(token);\r
202             }\r
203         }\r
204     }\r
205 \r
206     PolicyNodeFilter filter;\r
207     SAMLConfig& samlConf = SAMLConfig::getConfig();\r
208     e = XMLHelper::getFirstChildElement(e, Policy);\r
209     while (e) {\r
210         auto_ptr_char id(e->getAttributeNS(nullptr, _id));\r
211         pair< PropertySet*,vector<const SecurityPolicyRule*> >& rules = m_policyMap[id.get()];\r
212         rules.first = nullptr;\r
213         auto_ptr<DOMPropertySet> settings(new DOMPropertySet());\r
214         settings->load(e, nullptr, &filter);\r
215         rules.first = settings.release();\r
216 \r
217         // Process PolicyRule elements.\r
218         const DOMElement* rule = XMLHelper::getFirstChildElement(e, PolicyRule);\r
219         while (rule) {\r
220             auto_ptr_char type(rule->getAttributeNS(nullptr, _type));\r
221             try {\r
222                 rules.second.push_back(samlConf.SecurityPolicyRuleManager.newPlugin(type.get(), rule));\r
223             }\r
224             catch (exception& ex) {\r
225                 log.crit("error instantiating policy rule (%s) in policy (%s): %s", type.get(), id.get(), ex.what());\r
226             }\r
227             rule = XMLHelper::getNextSiblingElement(rule, PolicyRule);\r
228         }\r
229 \r
230         if (rules.second.size() == 0) {\r
231             // Process Rule elements.\r
232             log.warn("detected legacy Policy configuration, please convert to new PolicyRule syntax");\r
233             rule = XMLHelper::getFirstChildElement(e, Rule);\r
234             while (rule) {\r
235                 auto_ptr_char type(rule->getAttributeNS(nullptr, _type));\r
236                 try {\r
237                     rules.second.push_back(samlConf.SecurityPolicyRuleManager.newPlugin(type.get(), rule));\r
238                 }\r
239                 catch (exception& ex) {\r
240                     log.crit("error instantiating policy rule (%s) in policy (%s): %s", type.get(), id.get(), ex.what());\r
241                 }\r
242                 rule = XMLHelper::getNextSiblingElement(rule, Rule);\r
243             }\r
244 \r
245             // Manually add a basic Conditions rule.\r
246             log.info("installing a default Conditions rule in policy (%s) for compatibility with legacy configuration", id.get());\r
247             rules.second.push_back(samlConf.SecurityPolicyRuleManager.newPlugin(CONDITIONS_POLICY_RULE, nullptr));\r
248         }\r
249 \r
250         e = XMLHelper::getNextSiblingElement(e, Policy);\r
251     }\r
252 }\r
253 \r
254 pair<bool,DOMElement*> XMLSecurityPolicyProvider::load(bool backup)\r
255 {\r
256     // Load from source using base class.\r
257     pair<bool,DOMElement*> raw = ReloadableXMLFile::load(backup);\r
258 \r
259     // If we own it, wrap it.\r
260     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr);\r
261 \r
262     XMLSecurityPolicyProviderImpl* impl = new XMLSecurityPolicyProviderImpl(raw.second, m_log);\r
263 \r
264     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.\r
265     impl->setDocument(docjanitor.release());\r
266 \r
267     // Perform the swap inside a lock.\r
268     if (m_lock)\r
269         m_lock->wrlock();\r
270     SharedLock locker(m_lock, false);\r
271     delete m_impl;\r
272     m_impl = impl;\r
273 \r
274 \r
275     return make_pair(false,(DOMElement*)nullptr);\r
276 }\r
277 \r
278 pair<bool,DOMElement*> XMLSecurityPolicyProvider::background_load()\r
279 {\r
280     try {\r
281         return load(false);\r
282     }\r
283     catch (long& ex) {\r
284         if (ex == HTTPResponse::XMLTOOLING_HTTP_STATUS_NOTMODIFIED)\r
285             m_log.info("remote resource (%s) unchanged", m_source.c_str());\r
286         if (!m_loaded && !m_backing.empty())\r
287             return load(true);\r
288         throw;\r
289     }\r
290     catch (exception&) {\r
291         if (!m_loaded && !m_backing.empty())\r
292             return load(true);\r
293         throw;\r
294     }\r
295 }\r