Glue SOAP client to SP config, expand policy settings.
[shibboleth/sp.git] / shibsp / binding / impl / SOAPClient.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  * SOAPClient.cpp
19  * 
20  * Specialized SOAPClient for SP environment.
21  */
22
23 #include "internal.h"
24 #include "exceptions.h"
25 #include "ServiceProvider.h"
26 #include "binding/SOAPClient.h"
27
28 #include <log4cpp/Category.hh>
29 #include <saml/saml2/metadata/Metadata.h>
30 #include <xmltooling/soap/SOAP.h>
31 #include <xmltooling/soap/HTTPSOAPTransport.h>
32 #include <xmltooling/util/NDC.h>
33
34 using namespace shibsp;
35 using namespace opensaml::saml2md;
36 using namespace xmlsignature;
37 using namespace xmltooling;
38 using namespace log4cpp;
39 using namespace std;
40
41 SOAPClient::SOAPClient(const Application& application, opensaml::SecurityPolicy& policy)
42     : opensaml::SOAPClient(policy), m_app(application), m_settings(NULL), m_credUse(NULL), m_credResolver(NULL)
43 {
44     SPConfig& conf = SPConfig::getConfig();
45     pair<bool,const char*> policyId = m_app.getString("policyId");
46     m_settings = conf.getServiceProvider()->getPolicySettings(policyId.second);
47     const vector<const opensaml::SecurityPolicyRule*>& rules = conf.getServiceProvider()->getPolicyRules(policyId.second);
48     for (vector<const opensaml::SecurityPolicyRule*>::const_iterator rule=rules.begin(); rule!=rules.end(); ++rule)
49         policy.addRule(*rule);
50     policy.setMetadataProvider(application.getMetadataProvider());
51     policy.setTrustEngine(application.getTrustEngine());
52 }
53
54 namespace {
55     class SHIBSP_DLLLOCAL _addcert : public binary_function<X509Data*,XSECCryptoX509*,void> {
56     public:
57         void operator()(X509Data* bag, XSECCryptoX509* cert) const {
58             safeBuffer& buf=cert->getDEREncodingSB();
59             X509Certificate* x=X509CertificateBuilder::buildX509Certificate();
60             x->setValue(buf.sbStrToXMLCh());
61             bag->getX509Certificates().push_back(x);
62         }
63     };
64 };
65
66 void SOAPClient::send(const soap11::Envelope& env, const KeyInfoSource& peer, const char* endpoint)
67 {
68     if (!m_peer)
69         m_peer = dynamic_cast<const RoleDescriptor*>(&peer);
70  
71     if (m_peer) {
72         const EntityDescriptor* entity = m_peer ? dynamic_cast<const EntityDescriptor*>(m_peer->getParent()) : NULL;
73         m_credUse = entity ? m_app.getCredentialUse(entity) : NULL;
74     }
75     
76     // Check for message signing requirements.   
77     if (m_credUse) {
78         pair<bool,bool> flag = m_credUse->getBool("signRequests");
79         if (flag.first && flag.second) {
80             CredentialResolver* cr=NULL;
81             pair<bool,const char*> cred = m_credUse->getString("Signing");
82             if (cred.first && (cr==SPConfig::getConfig().getServiceProvider()->getCredentialResolver(cred.second))) {
83                 // Looks like we're supposed to sign, so check for message.
84                 const vector<XMLObject*>& bodies=const_cast<const soap11::Body*>(env.getBody())->getUnknownXMLObjects();
85                 if (!bodies.empty()) {
86                     opensaml::SignableObject* msg = dynamic_cast<opensaml::SignableObject*>(bodies.front());
87                     if (msg) {
88                         // Build a Signature.
89                         Signature* sig = SignatureBuilder::buildSignature();
90                         msg->setSignature(sig);
91                         pair<bool,const XMLCh*> alg = m_credUse->getXMLString("sigAlgorithm");
92                         if (alg.first)
93                             sig->setSignatureAlgorithm(alg.second);
94                         Locker locker(cr);
95                         sig->setSigningKey(cr->getKey());
96                     
97                         // Build KeyInfo.
98                         const vector<XSECCryptoX509*>& certs = cr->getCertificates();
99                         if (!certs.empty()) {
100                             KeyInfo* keyInfo=KeyInfoBuilder::buildKeyInfo();
101                             sig->setKeyInfo(keyInfo);
102                             X509Data* x509Data=X509DataBuilder::buildX509Data();
103                             keyInfo->getX509Datas().push_back(x509Data);
104                             for_each(certs.begin(),certs.end(),bind1st(_addcert(),x509Data));
105                         }
106
107                         // Sign it. The marshalling step in the base class should be a no-op.
108                         vector<Signature*> sigs(1,sig);
109                         env.marshall((DOMDocument*)NULL,&sigs);
110                     }
111                 }
112             }
113         }
114     }
115     
116     opensaml::SOAPClient::send(env, peer, endpoint);
117 }
118
119 void SOAPClient::prepareTransport(const SOAPTransport& transport)
120 {
121 #ifdef _DEBUG
122     xmltooling::NDC("prepareTransport");
123 #endif
124     Category& log=Category::getInstance(SHIBSP_LOGCAT".SOAPClient");
125     log.debug("prepping SOAP transport for use by application (%s)", m_app.getId());
126
127     pair<bool,bool> flag = m_settings->getBool("requireConfidentiality");
128     if ((!flag.first || flag.second) && !transport.isConfidential())
129         throw opensaml::BindingException("Transport confidentiality required, but not available."); 
130
131     flag = m_settings->getBool("validate");
132     setValidating(flag.first && flag.second);
133     flag = m_settings->getBool("requireTransportAuth");
134     forceTransportAuthentication(!flag.first || flag.second);
135
136     opensaml::SOAPClient::prepareTransport(transport);
137
138     if (!m_credUse) {
139         const EntityDescriptor* entity = m_peer ? dynamic_cast<const EntityDescriptor*>(m_peer->getParent()) : NULL;
140         m_credUse = entity ? m_app.getCredentialUse(entity) : NULL;
141     }
142     if (m_credUse) {
143         pair<bool,const char*> authType=m_credUse->getString("authType");
144         if (authType.first) {
145             SOAPTransport::transport_auth_t type=SOAPTransport::transport_auth_none;
146             pair<bool,const char*> username=m_credUse->getString("authUsername");
147             pair<bool,const char*> password=m_credUse->getString("authPassword");
148             if (!username.first || !password.first)
149                 log.error("transport authType (%s) specified but authUsername or authPassword was missing", authType.second);
150             else if (!strcmp(authType.second,"basic"))
151                 type = SOAPTransport::transport_auth_basic;
152             else if (!strcmp(authType.second,"digest"))
153                 type = SOAPTransport::transport_auth_digest;
154             else if (!strcmp(authType.second,"ntlm"))
155                 type = SOAPTransport::transport_auth_ntlm;
156             else if (!strcmp(authType.second,"gss"))
157                 type = SOAPTransport::transport_auth_gss;
158             else
159                 log.error("unknown authType (%s) specified in CredentialUse element", authType.second);
160             if (type > SOAPTransport::transport_auth_none) {
161                 if (transport.setAuth(type,username.second,password.second))
162                     log.debug("configured for transport authentication (method=%s, username=%s)", authType.second, username.second);
163                 else
164                     log.error("failed to configure transport authentication (method=%s)", authType.second);
165             }
166         }
167         
168         authType = m_credUse->getString("TLS");
169         if (authType.first) {
170             m_credResolver = SPConfig::getConfig().getServiceProvider()->getCredentialResolver(authType.second);
171             if (m_credResolver) {
172                 m_credResolver->lock();
173                 if (!transport.setCredentialResolver(m_credResolver)) {
174                     m_credResolver->unlock();
175                     m_credResolver = NULL;
176                     log.error("failed to load CredentialResolver into SOAPTransport");
177                 }
178             }
179             else {
180                 log.error("unable to access CredentialResolver (%s)", authType.second);
181             }
182         }
183     } 
184
185     transport.setConnectTimeout(m_settings->getUnsignedInt("connectTimeout").second);
186     transport.setTimeout(m_settings->getUnsignedInt("timeout").second);
187
188     const HTTPSOAPTransport* http = dynamic_cast<const HTTPSOAPTransport*>(&transport);
189     if (http)
190         http->setRequestHeader("Shibboleth", PACKAGE_VERSION);
191 }
192
193 void SOAPClient::reset()
194 {
195     m_credUse = NULL;
196     if (m_credResolver)
197         m_credResolver->unlock();
198     m_credResolver = NULL;
199     opensaml::SOAPClient::reset();
200 }