81acf216a3006aafa63b40c1a844da71aa34f4d5
[shibboleth/cpp-sp.git] / shib-target / ArtifactMapper.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 /* ArtifactMapper.cpp - a ShibTarget-aware SAML artifact->binding mapper
18
19    Scott Cantor
20    2/20/05
21
22    $History:$
23 */
24
25 #include "internal.h"
26 #include <shibsp/SPConfig.h>
27 #include <saml/binding/SAMLArtifact.h>
28
29 using namespace shibsp;
30 using namespace shibtarget;
31 using namespace saml;
32 using namespace opensaml::saml2md;
33 using namespace xmltooling;
34 using namespace log4cpp;
35 using namespace std;
36 using xmlsignature::CredentialResolver;
37
38 SAMLResponse* STArtifactMapper::resolve(SAMLRequest* request)
39 {
40     Category& log=Category::getInstance("shibtarget.ArtifactMapper");
41     
42     // First do a search for the issuer.
43     SAMLArtifact* artifact=request->getArtifacts().next();
44     auto_ptr<opensaml::SAMLArtifact> os2art(opensaml::SAMLArtifact::parse(artifact->encode().c_str()));
45
46     MetadataProvider* m=m_app->getMetadataProvider();
47     const EntityDescriptor* entity=m->getEntityDescriptor(os2art.get());
48     if (!entity) {
49         log.error(
50             "metadata lookup failed, unable to determine issuer of artifact (0x%s)",
51             opensaml::SAMLArtifact::toHex(artifact->getBytes()).c_str()
52             );
53         throw MetadataException("Metadata lookup failed, unable to determine artifact issuer");
54     }
55     
56     xmltooling::auto_ptr_char issuer(entity->getEntityID());
57     log.info("lookup succeeded, artifact issued by (%s)", issuer.get());
58     
59     // Sign it?
60     const PropertySet* credUse=m_app->getCredentialUse(entity);
61     pair<bool,bool> signRequest=credUse ? credUse->getBool("signRequest") : make_pair(false,false);
62     pair<bool,const char*> signatureAlg=credUse ? credUse->getString("signatureAlg") : pair<bool,const char*>(false,NULL);
63     if (!signatureAlg.first)
64         signatureAlg.second=URI_ID_RSA_SHA1;
65     pair<bool,const char*> digestAlg=credUse ? credUse->getString("digestAlg") : pair<bool,const char*>(false,NULL);
66     if (!digestAlg.first)
67         digestAlg.second=URI_ID_SHA1;
68     pair<bool,bool> signedResponse=credUse ? credUse->getBool("signedResponse") : make_pair(false,false);
69     pair<bool,const char*> signingCred=credUse ? credUse->getString("Signing") : pair<bool,const char*>(false,NULL);
70     if (signRequest.first && signRequest.second && signingCred.first) {
71         if (request->getMinorVersion()==1) {
72             CredentialResolver* cr=SPConfig::getConfig().getServiceProvider()->getCredentialResolver(signingCred.second);
73             if (cr) {
74                 xmltooling::Locker locker(cr);
75                 request->sign(cr->getKey(),cr->getCertificates(),signatureAlg.second,digestAlg.second);
76             }
77             else
78                 log.error("unable to sign artifact request, specified credential (%s) was not found",signingCred.second);
79         }
80         else
81             log.error("unable to sign SAML 1.0 artifact request, only SAML 1.1 defines signing adequately");
82     }
83
84         SAMLResponse* response = NULL;
85         bool authenticated = false;
86     static const XMLCh https[] = {chLatin_h, chLatin_t, chLatin_t, chLatin_p, chLatin_s, chColon, chNull};
87
88     // Depends on type of artifact.
89     const SAMLArtifactType0001* type1=dynamic_cast<const SAMLArtifactType0001*>(artifact);
90     if (type1) {
91         // With type 01, any endpoint will do.
92         const IDPSSODescriptor* idp=entity->getIDPSSODescriptor(
93             request->getMinorVersion()==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM
94             );
95         if (idp) {
96                     ShibHTTPHook::ShibHTTPHookCallContext callCtx(credUse,idp);
97             const vector<ArtifactResolutionService*>& endpoints=idp->getArtifactResolutionServices();
98             for (vector<ArtifactResolutionService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {
99                 const SAMLBinding* binding = m_app->getBinding((*ep)->getBinding());
100                 if (!binding) {
101                     xmltooling::auto_ptr_char prot((*ep)->getBinding());
102                     log.warn("skipping binding on unsupported protocol (%s)", prot.get());
103                     continue;
104                 }
105                         try {
106                             response = binding->send((*ep)->getLocation(),*request,&callCtx);
107                             if (log.isDebugEnabled())
108                                 log.debugStream() << "SAML response from artifact request:\n" << *response << CategoryStream::ENDLINE;
109                             
110                             if (!response->getAssertions().hasNext()) {
111                                 delete response;
112                                 throw FatalProfileException("No SAML assertions returned in response to artifact profile request.");
113                             }
114                             authenticated = callCtx.isAuthenticated() && !XMLString::compareNString((*ep)->getLocation(),https,6);
115                         }
116                         catch (XMLToolingException& ex) {
117                                 annotateException(&ex,idp); // rethrows it
118                         }
119                 catch (exception& ex) {
120                     opensaml::BindingException ex2(ex.what());
121                     annotateException(&ex2,idp); // rethrows it
122                 }
123             }
124         }
125     }
126     else {
127         const SAMLArtifactType0002* type2=dynamic_cast<const SAMLArtifactType0002*>(artifact);
128         if (type2) {
129             // With type 02, we have to find the matching location.
130             const IDPSSODescriptor* idp=entity->getIDPSSODescriptor(
131                 request->getMinorVersion()==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM
132                 );
133             if (idp) {
134                     ShibHTTPHook::ShibHTTPHookCallContext callCtx(credUse,idp);
135                 const vector<ArtifactResolutionService*>& endpoints=idp->getArtifactResolutionServices();
136                 for (vector<ArtifactResolutionService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {
137                     xmltooling::auto_ptr_char loc((*ep)->getLocation());
138                     if (strcmp(loc.get(),type2->getSourceLocation()))
139                         continue;
140                         const SAMLBinding* binding = m_app->getBinding((*ep)->getBinding());
141                         if (!binding) {
142                             xmltooling::auto_ptr_char prot((*ep)->getBinding());
143                             log.warn("skipping binding on unsupported protocol (%s)", prot.get());
144                             continue;
145                         }
146                                 try {
147                                     response = binding->send((*ep)->getLocation(),*request,&callCtx);
148                                     if (log.isDebugEnabled())
149                                         log.debugStream() << "SAML response from artifact request:\n" << *response << CategoryStream::ENDLINE;
150                                     
151                                     if (!response->getAssertions().hasNext()) {
152                                         delete response;
153                                         throw FatalProfileException("No SAML assertions returned in response to artifact profile request.");
154                                     }
155                         authenticated = callCtx.isAuthenticated() && !XMLString::compareNString((*ep)->getLocation(),https,6);
156                                 }
157                             catch (XMLToolingException& ex) {
158                                     annotateException(&ex,idp); // rethrows it
159                             }
160                     catch (exception& ex) {
161                         opensaml::BindingException ex2(ex.what());
162                         annotateException(&ex2,idp); // rethrows it
163                     }
164                 }
165             }
166         }
167         else {
168             log.error("unrecognized artifact type (0x%s)", SAMLArtifact::toHex(artifact->getTypeCode()).c_str());
169             throw xmltooling::UnknownExtensionException(
170                 string("Received unrecognized artifact type (0x") + SAMLArtifact::toHex(artifact->getTypeCode()) + ")"
171                 );
172         }
173     }
174     
175     if (!response) {
176             log.error("unable to locate acceptable binding/endpoint to resolve artifact");
177             MetadataException ex("Unable to locate acceptable binding/endpoint to resolve artifact.");
178             annotateException(&ex,entity); // throws it
179     }
180     else if (!response->isSigned()) {
181         if (!authenticated || (signedResponse.first && signedResponse.second)) {
182                 log.error("unsigned response obtained, but it must be signed.");
183                 XMLSecurityException ex("Unable to obtain a signed response from artifact request.");
184                     annotateException(&ex,entity); // throws it
185         }
186     }
187     
188     return response;
189 }