Refactor contact handling
[shibboleth/cpp-opensaml.git] / saml / SAMLConfig.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  * SAMLConfig.cpp
23  * 
24  * Library configuration.
25  */
26
27 #include "internal.h"
28
29 #if defined(XMLTOOLING_LOG4SHIB)
30 # ifndef OPENSAML_LOG4SHIB
31 #  error "Logging library mismatch (XMLTooling is using log4shib)."
32 # endif
33 #elif defined(XMLTOOLING_LOG4CPP)
34 # ifndef OPENSAML_LOG4CPP
35 #  error "Logging library mismatch (XMLTooling is using log4cpp)."
36 # endif
37 #else
38 # error "No supported logging library."
39 #endif
40
41 #include "exceptions.h"
42 #include "SAMLConfig.h"
43 #include "binding/ArtifactMap.h"
44 #include "binding/MessageDecoder.h"
45 #include "binding/MessageEncoder.h"
46 #include "binding/SAMLArtifact.h"
47 #include "binding/SecurityPolicyRule.h"
48 #include "saml1/core/Assertions.h"
49 #include "saml1/core/Protocols.h"
50 #include "saml2/core/Protocols.h"
51 #include "saml2/metadata/Metadata.h"
52 #include "saml2/metadata/MetadataFilter.h"
53 #include "saml2/metadata/MetadataProvider.h"
54 #include "util/SAMLConstants.h"
55
56 #include <xmltooling/logging.h>
57 #include <xmltooling/XMLToolingConfig.h>
58 #include <xmltooling/security/SecurityHelper.h>
59 #include <xmltooling/signature/Signature.h>
60 #include <xmltooling/util/NDC.h>
61 #include <xmltooling/util/PathResolver.h>
62 #include <xmltooling/util/Threads.h>
63
64 #include <boost/lambda/bind.hpp>
65 #include <boost/lambda/casts.hpp>
66 #include <boost/lambda/lambda.hpp>
67
68 #include <xsec/enc/XSECCryptoException.hpp>
69 #include <xsec/enc/XSECCryptoProvider.hpp>
70 #include <xsec/utils/XSECPlatformUtils.hpp>
71 #include <xercesc/util/XMLStringTokenizer.hpp>
72
73 using namespace opensaml;
74 using namespace xmlsignature;
75 using namespace xmltooling::logging;
76 using namespace xmltooling;
77 using namespace boost::lambda;
78 using namespace boost;
79 using namespace std;
80
81 // Expose entry points when used as an extension library
82
83 extern "C" int SAML_API xmltooling_extension_init(void*)
84 {
85     if (SAMLConfig::getConfig().init(false))
86         return 0;
87     return -1;
88 }
89
90 extern "C" void SAML_API xmltooling_extension_term()
91 {
92     SAMLConfig::getConfig().term(false);
93 }
94
95 DECL_XMLTOOLING_EXCEPTION_FACTORY(ArtifactException,opensaml);
96 DECL_XMLTOOLING_EXCEPTION_FACTORY(SecurityPolicyException,opensaml);
97 DECL_XMLTOOLING_EXCEPTION_FACTORY(MetadataException,opensaml::saml2md);
98 DECL_XMLTOOLING_EXCEPTION_FACTORY(MetadataFilterException,opensaml::saml2md);
99 DECL_XMLTOOLING_EXCEPTION_FACTORY(BindingException,opensaml);
100 DECL_XMLTOOLING_EXCEPTION_FACTORY(ProfileException,opensaml);
101 DECL_XMLTOOLING_EXCEPTION_FACTORY(FatalProfileException,opensaml);
102 DECL_XMLTOOLING_EXCEPTION_FACTORY(RetryableProfileException,opensaml);
103
104 namespace opensaml {
105    SAMLInternalConfig g_config;
106 }
107
108 SAMLConfig& SAMLConfig::getConfig()
109 {
110     return g_config;
111 }
112
113 SAMLInternalConfig& SAMLInternalConfig::getInternalConfig()
114 {
115     return g_config;
116 }
117
118 SAMLConfig::SAMLConfig() : m_artifactMap(nullptr)
119 {
120 }
121
122 SAMLConfig::~SAMLConfig()
123 {
124     delete m_artifactMap;
125 }
126
127 ArtifactMap* SAMLConfig::getArtifactMap() const
128 {
129     return m_artifactMap;
130 }
131
132 void SAMLConfig::setArtifactMap(ArtifactMap* artifactMap)
133 {
134     delete m_artifactMap;
135     m_artifactMap = artifactMap;
136 }
137
138 SAMLInternalConfig::SAMLInternalConfig() : m_initCount(0), m_lock(Mutex::create())
139 {
140 }
141
142 SAMLInternalConfig::~SAMLInternalConfig()
143 {
144 }
145
146 bool SAMLInternalConfig::init(bool initXMLTooling)
147 {
148 #ifdef _DEBUG
149     xmltooling::NDC ndc("init");
150 #endif
151     Category& log=Category::getInstance(SAML_LOGCAT".Config");
152
153     Lock initLock(m_lock);
154
155     if (m_initCount == INT_MAX) {
156         log.crit("library initialized too many times");
157         return false;
158     }
159
160     if (m_initCount >= 1) {
161         ++m_initCount;
162         return true;
163     }
164
165     log.debug("library initialization started");
166
167     if (initXMLTooling && !XMLToolingConfig::getConfig().init()) {
168         return false;
169     }
170
171     XMLToolingConfig::getConfig().getPathResolver()->setDefaultPackageName("opensaml");
172
173     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(ArtifactException,opensaml);
174     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(SecurityPolicyException,opensaml);
175     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(MetadataException,opensaml::saml2md);
176     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(MetadataFilterException,opensaml::saml2md);
177     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(BindingException,opensaml);
178     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(ProfileException,opensaml);
179     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(FatalProfileException,opensaml);
180     REGISTER_XMLTOOLING_EXCEPTION_FACTORY(RetryableProfileException,opensaml);
181
182     saml1::registerAssertionClasses();
183     saml1p::registerProtocolClasses();
184     saml2::registerAssertionClasses();
185     saml2p::registerProtocolClasses();
186     saml2md::registerMetadataClasses();
187     saml2md::registerMetadataProviders();
188     saml2md::registerMetadataFilters();
189     registerSAMLArtifacts();
190     registerMessageEncoders();
191     registerMessageDecoders();
192     registerSecurityPolicyRules();
193
194     m_contactPriority.push_back(saml2md::ContactPerson::CONTACT_SUPPORT);
195     m_contactPriority.push_back(saml2md::ContactPerson::CONTACT_TECHNICAL);
196
197     log.info("%s library initialization complete", PACKAGE_STRING);
198     ++m_initCount;
199     return true;
200 }
201
202 void SAMLInternalConfig::term(bool termXMLTooling)
203 {
204 #ifdef _DEBUG
205     xmltooling::NDC ndc("term");
206 #endif
207
208     Lock initLock(m_lock);
209     if (m_initCount == 0) {
210         Category::getInstance(SAML_LOGCAT".Config").crit("term without corresponding init");
211         return;
212     }
213     else if (--m_initCount > 0) {
214         return;
215     }
216
217     MessageDecoderManager.deregisterFactories();
218     MessageEncoderManager.deregisterFactories();
219     SecurityPolicyRuleManager.deregisterFactories();
220     SAMLArtifactManager.deregisterFactories();
221     MetadataFilterManager.deregisterFactories();
222     MetadataProviderManager.deregisterFactories();
223
224     delete m_artifactMap;
225     m_artifactMap = nullptr;
226
227     if (termXMLTooling)
228         XMLToolingConfig::getConfig().term();
229     
230     Category::getInstance(SAML_LOGCAT".Config").info("%s library shutdown complete", PACKAGE_STRING);
231 }
232
233 void SAMLInternalConfig::generateRandomBytes(void* buf, unsigned int len)
234 {
235     try {
236         if (XSECPlatformUtils::g_cryptoProvider->getRandom(reinterpret_cast<unsigned char*>(buf),len)<len)
237             throw XMLSecurityException("Unable to generate random data; was PRNG seeded?");
238     }
239     catch (XSECCryptoException& e) {
240         throw XMLSecurityException("Unable to generate random data: $1",params(1,e.getMsg()));
241     }
242 }
243
244 void SAMLInternalConfig::generateRandomBytes(std::string& buf, unsigned int len)
245 {
246     buf.erase();
247     auto_arrayptr<unsigned char> hold(new unsigned char[len]);
248     generateRandomBytes(const_cast<unsigned char*>(hold.get()), len);
249     for (unsigned int i=0; i<len; i++)
250         buf+=(hold.get())[i];
251 }
252
253 XMLCh* SAMLInternalConfig::generateIdentifier()
254 {
255     unsigned char key[17];
256     generateRandomBytes(key,16);
257     
258     char hexform[34];
259     sprintf(hexform,"_%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
260             key[0],key[1],key[2],key[3],key[4],key[5],key[6],key[7],
261             key[8],key[9],key[10],key[11],key[12],key[13],key[14],key[15]);
262     hexform[33]=0;
263     return XMLString::transcode(hexform);
264 }
265
266 string SAMLInternalConfig::hashSHA1(const char* s, bool toHex)
267 {
268     return SecurityHelper::doHash("SHA1", s, strlen(s), toHex);
269 }
270
271 void SAMLInternalConfig::setContactPriority(const XMLCh* contactTypes)
272 {
273     const XMLCh* ctype;
274     m_contactPriority.clear();
275     XMLStringTokenizer tokens(contactTypes);
276     while (tokens.hasMoreTokens()) {
277         ctype = tokens.nextToken();
278         if (ctype && *ctype)
279             m_contactPriority.push_back(ctype);
280     }
281 }
282
283 using namespace saml2md;
284
285 const ContactPerson* SAMLInternalConfig::getContactPerson(const EntityDescriptor& entity) const
286 {
287     for (vector<xstring>::const_iterator ctype = m_contactPriority.begin(); ctype != m_contactPriority.end(); ++ctype) {
288         const ContactPerson* cp = find_if(entity.getContactPersons(), *ctype == lambda::bind(&ContactPerson::getContactType, _1));
289         if (cp)
290             return cp;
291     }
292     return nullptr;
293 }
294
295 const ContactPerson* SAMLInternalConfig::getContactPerson(const RoleDescriptor& role) const
296 {
297     for (vector<xstring>::const_iterator ctype = m_contactPriority.begin(); ctype != m_contactPriority.end(); ++ctype) {
298         const ContactPerson* cp = find_if(role.getContactPersons(), *ctype == lambda::bind(&ContactPerson::getContactType, _1));
299         if (cp)
300             return cp;
301     }
302     return getContactPerson(*(dynamic_cast<const EntityDescriptor*>(role.getParent())));
303 }
304
305 SignableObject::SignableObject()
306 {
307 }
308
309 SignableObject::~SignableObject()
310 {
311 }
312
313 RootObject::RootObject()
314 {
315 }
316
317 RootObject::~RootObject()
318 {
319 }
320
321 Assertion::Assertion()
322 {
323 }
324
325 Assertion::~Assertion()
326 {
327 }
328
329 Status::Status()
330 {
331 }
332
333 Status::~Status()
334 {
335 }
336
337 void opensaml::annotateException(XMLToolingException* e, const EntityDescriptor* entity, const Status* status, bool rethrow)
338 {
339     time_t now = time(nullptr);
340     const RoleDescriptor* role = nullptr;
341     static bool (TimeBoundSAMLObject::* isValid)(time_t) const = &TimeBoundSAMLObject::isValid;
342
343     if (entity) {
344         const XMLObject* r = find_if(
345             entity->getOrderedChildren(),
346             (ll_dynamic_cast<const RoleDescriptor*>(_1) != ((const RoleDescriptor*)nullptr) &&
347                     lambda::bind(isValid, ll_dynamic_cast<const TimeBoundSAMLObject*>(_1), now))
348             );
349         if (r)
350             role = dynamic_cast<const RoleDescriptor*>(r);
351     }
352
353     annotateException(e, role, status, rethrow);
354 }
355
356 void opensaml::annotateException(XMLToolingException* e, const RoleDescriptor* role, const Status* status, bool rethrow)
357 {
358     if (role) {
359         auto_ptr_char id(dynamic_cast<EntityDescriptor*>(role->getParent())->getEntityID());
360         e->addProperty("entityID",id.get());
361
362         const ContactPerson* cp = SAMLConfig::getConfig().getContactPerson(*role);
363         if (cp) {
364             GivenName* fname = cp->getGivenName();
365             SurName* lname = cp->getSurName();
366             auto_ptr_char first(fname ? fname->getName() : nullptr);
367             auto_ptr_char last(lname ? lname->getName() : nullptr);
368             if (first.get() && last.get()) {
369                 string contact=string(first.get()) + ' ' + last.get();
370                 e->addProperty("contactName", contact.c_str());
371             }
372             else if (first.get())
373                 e->addProperty("contactName", first.get());
374             else if (last.get())
375                 e->addProperty("contactName", last.get());
376             const vector<EmailAddress*>& emails=cp->getEmailAddresss();
377             if (!emails.empty()) {
378                 auto_ptr_char email(emails.front()->getAddress());
379                 if (email.get()) {
380                     if (strstr(email.get(), "mailto:") == email.get()) {
381                         e->addProperty("contactEmail", email.get());
382                     }
383                     else {
384                         string addr = string("mailto:") + email.get();
385                         e->addProperty("contactEmail", addr.c_str());
386                     }
387                 }
388             }
389         }
390
391         auto_ptr_char eurl(role->getErrorURL());
392         if (eurl.get()) {
393             e->addProperty("errorURL",eurl.get());
394         }
395     }
396
397     if (status) {
398         auto_ptr_char sc(status->getTopStatus());
399         if (sc.get() && *sc.get())
400             e->addProperty("statusCode", sc.get());
401         if (status->getSubStatus()) {
402             auto_ptr_char sc2(status->getSubStatus());
403             if (sc2.get() && *sc.get())
404                 e->addProperty("statusCode2", sc2.get());
405         }
406         if (status->getMessage()) {
407             auto_ptr_char msg(status->getMessage());
408             if (msg.get() && *msg.get())
409                 e->addProperty("statusMessage", msg.get());
410         }
411     }
412     
413     if (rethrow)
414         e->raise();
415 }