Added marker interface for assertion types.
[shibboleth/sp.git] / shibsp / attribute / resolver / impl / SimpleAttributeResolver.cpp
1 /*\r
2  *  Copyright 2001-2007 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  * SimpleAttributeResolver.cpp\r
19  * \r
20  * AttributeResolver based on a simple mapping of SAML information.\r
21  */\r
22 \r
23 #include "internal.h"\r
24 #include "Application.h"\r
25 #include "SessionCache.h"\r
26 #include "attribute/AttributeDecoder.h"\r
27 #include "attribute/resolver/AttributeResolver.h"\r
28 #include "attribute/resolver/ResolutionContext.h"\r
29 #include "binding/SOAPClient.h"\r
30 #include "util/SPConstants.h"\r
31 \r
32 \r
33 #include <log4cpp/Category.hh>\r
34 #include <saml/binding/SecurityPolicy.h>\r
35 #include <saml/saml1/binding/SAML1SOAPClient.h>\r
36 #include <saml/saml1/core/Assertions.h>\r
37 #include <saml/saml1/core/Protocols.h>\r
38 #include <saml/saml2/binding/SAML2SOAPClient.h>\r
39 #include <saml/saml2/core/Protocols.h>\r
40 #include <saml/saml2/metadata/Metadata.h>\r
41 #include <saml/saml2/metadata/MetadataProvider.h>\r
42 #include <xmltooling/util/NDC.h>\r
43 #include <xmltooling/util/ReloadableXMLFile.h>\r
44 #include <xmltooling/util/XMLHelper.h>\r
45 #include <xercesc/util/XMLUniDefs.hpp>\r
46 \r
47 using namespace shibsp;\r
48 using namespace opensaml::saml1;\r
49 using namespace opensaml::saml1p;\r
50 using namespace opensaml::saml2;\r
51 using namespace opensaml::saml2p;\r
52 using namespace opensaml::saml2md;\r
53 using namespace opensaml;\r
54 using namespace xmltooling;\r
55 using namespace log4cpp;\r
56 using namespace std;\r
57 \r
58 namespace shibsp {\r
59 \r
60     class SHIBSP_DLLLOCAL SimpleContext : public ResolutionContext\r
61     {\r
62     public:\r
63         SimpleContext(const Application& application, const Session& session)\r
64             : m_app(application), m_session(&session), m_client_addr(NULL), m_metadata(NULL), m_entity(NULL),\r
65                 m_nameid(session.getNameID()), m_tokens(NULL) {\r
66         }\r
67         \r
68         SimpleContext(\r
69             const Application& application,\r
70             const char* client_addr,\r
71             const EntityDescriptor* issuer,\r
72             const NameID& nameid,\r
73             const vector<const opensaml::Assertion*>* tokens=NULL\r
74             ) : m_app(application), m_session(NULL), m_client_addr(client_addr), m_entity(issuer), m_nameid(nameid), m_tokens(tokens) {\r
75         }\r
76         \r
77         ~SimpleContext() {\r
78             if (m_metadata)\r
79                 m_metadata->unlock();\r
80             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<shibsp::Attribute>());\r
81             for_each(m_assertions.begin(), m_assertions.end(), xmltooling::cleanup<opensaml::Assertion>());\r
82         }\r
83     \r
84         const Application& getApplication() const {\r
85             return m_app;\r
86         }\r
87         const char* getClientAddress() const {\r
88             return m_session ? m_session->getClientAddress() : m_client_addr;\r
89         }\r
90         const EntityDescriptor* getEntityDescriptor() const {\r
91             if (m_entity)\r
92                 return m_entity;\r
93             if (m_session && m_session->getEntityID()) {\r
94                 m_metadata = m_app.getMetadataProvider();\r
95                 if (m_metadata) {\r
96                     m_metadata->lock();\r
97                     return m_entity = m_metadata->getEntityDescriptor(m_session->getEntityID());\r
98                 }\r
99             }\r
100             return NULL;\r
101         }\r
102         const NameID& getNameID() const {\r
103             return m_nameid;\r
104         }\r
105         const vector<const opensaml::Assertion*>* getTokens() const {\r
106             return m_tokens;\r
107         }\r
108         const Session* getSession() const {\r
109             return m_session;\r
110         }        \r
111         vector<shibsp::Attribute*>& getResolvedAttributes() {\r
112             return m_attributes;\r
113         }\r
114         vector<opensaml::Assertion*>& getResolvedAssertions() {\r
115             return m_assertions;\r
116         }\r
117 \r
118     private:\r
119         const Application& m_app;\r
120         const Session* m_session;\r
121         const char* m_client_addr;\r
122         mutable MetadataProvider* m_metadata;\r
123         mutable const EntityDescriptor* m_entity;\r
124         const NameID& m_nameid;\r
125         const vector<const opensaml::Assertion*>* m_tokens;\r
126         vector<shibsp::Attribute*> m_attributes;\r
127         vector<opensaml::Assertion*> m_assertions;\r
128     };\r
129     \r
130 #if defined (_MSC_VER)\r
131     #pragma warning( push )\r
132     #pragma warning( disable : 4250 )\r
133 #endif\r
134 \r
135     class SimpleResolverImpl\r
136     {\r
137     public:\r
138         SimpleResolverImpl(const DOMElement* e);\r
139         ~SimpleResolverImpl() {\r
140             for_each(m_decoderMap.begin(), m_decoderMap.end(), cleanup_pair<string,AttributeDecoder>());\r
141             if (m_document)\r
142                 m_document->release();\r
143         }\r
144 \r
145         void setDocument(DOMDocument* doc) {\r
146             m_document = doc;\r
147         }\r
148 \r
149         void query(\r
150             ResolutionContext& ctx, const NameIdentifier& nameid, const vector<const char*>* attributes=NULL\r
151             ) const;\r
152         void query(\r
153             ResolutionContext& ctx, const NameID& nameid, const vector<const char*>* attributes=NULL\r
154             ) const;\r
155         void resolve(\r
156             ResolutionContext& ctx, const saml1::Assertion* token, const vector<const char*>* attributes=NULL\r
157             ) const;\r
158         void resolve(\r
159             ResolutionContext& ctx, const saml2::Assertion* token, const vector<const char*>* attributes=NULL\r
160             ) const;\r
161 \r
162         bool m_allowQuery;\r
163     private:\r
164         DOMDocument* m_document;\r
165         map<string,AttributeDecoder*> m_decoderMap;\r
166 #ifdef HAVE_GOOD_STL\r
167         map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> > m_attrMap;\r
168 #else\r
169         map< pair<string,string>,pair<const AttributeDecoder*,string> > m_attrMap;\r
170 #endif\r
171     };\r
172     \r
173     class SimpleResolver : public AttributeResolver, public ReloadableXMLFile\r
174     {\r
175     public:\r
176         SimpleResolver(const DOMElement* e) : ReloadableXMLFile(e), m_impl(NULL) {\r
177             load();\r
178         }\r
179         ~SimpleResolver() {\r
180             delete m_impl;\r
181         }\r
182         \r
183         ResolutionContext* createResolutionContext(\r
184             const Application& application,\r
185             const char* client_addr,\r
186             const EntityDescriptor* issuer,\r
187             const NameID& nameid,\r
188             const vector<const opensaml::Assertion*>* tokens=NULL\r
189             ) const {\r
190             return new SimpleContext(application,client_addr,issuer,nameid,tokens);\r
191         }\r
192 \r
193         ResolutionContext* createResolutionContext(const Application& application, const Session& session) const {\r
194             return new SimpleContext(application,session);\r
195         }\r
196         \r
197         void resolveAttributes(ResolutionContext& ctx, const vector<const char*>* attributes=NULL) const;\r
198 \r
199     protected:\r
200         pair<bool,DOMElement*> load();\r
201         SimpleResolverImpl* m_impl;\r
202     };\r
203 \r
204 #if defined (_MSC_VER)\r
205     #pragma warning( pop )\r
206 #endif\r
207 \r
208     AttributeResolver* SHIBSP_DLLLOCAL SimpleAttributeResolverFactory(const DOMElement* const & e)\r
209     {\r
210         return new SimpleResolver(e);\r
211     }\r
212     \r
213     static const XMLCh SIMPLE_NS[] = {\r
214         chLatin_u, chLatin_r, chLatin_n, chColon, chLatin_m, chLatin_a, chLatin_c, chLatin_e, chColon,\r
215         chLatin_s, chLatin_h, chLatin_i, chLatin_b, chLatin_b, chLatin_o, chLatin_l, chLatin_e, chLatin_t, chLatin_h, chColon,\r
216         chDigit_2, chPeriod, chDigit_0, chColon,\r
217         chLatin_r, chLatin_e, chLatin_s, chLatin_o, chLatin_l, chLatin_v, chLatin_e, chLatin_r, chColon,\r
218         chLatin_s, chLatin_i, chLatin_m, chLatin_p, chLatin_l, chLatin_e, chNull\r
219     };\r
220     static const XMLCh _AttributeDecoder[] =    UNICODE_LITERAL_16(A,t,t,r,i,b,u,t,e,D,e,c,o,d,e,r);\r
221     static const XMLCh _AttributeResolver[] =   UNICODE_LITERAL_17(A,t,t,r,i,b,u,t,e,R,e,s,o,l,v,e,r);\r
222     static const XMLCh allowQuery[] =           UNICODE_LITERAL_10(a,l,l,o,w,Q,u,e,r,y);\r
223     static const XMLCh decoderId[] =            UNICODE_LITERAL_9(d,e,c,o,d,e,r,I,d);\r
224     static const XMLCh _id[] =                  UNICODE_LITERAL_2(i,d);\r
225     static const XMLCh _type[] =                UNICODE_LITERAL_4(t,y,p,e);\r
226 };\r
227 \r
228 SimpleResolverImpl::SimpleResolverImpl(const DOMElement* e) : m_document(NULL), m_allowQuery(true)\r
229 {\r
230 #ifdef _DEBUG\r
231     xmltooling::NDC ndc("SimpleResolverImpl");\r
232 #endif\r
233     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
234     \r
235     if (!XMLHelper::isNodeNamed(e, SIMPLE_NS, _AttributeResolver))\r
236         throw ConfigurationException("Simple resolver requires resolver:AttributeResolver at root of configuration.");\r
237     \r
238     const XMLCh* flag = e->getAttributeNS(NULL,allowQuery);\r
239     if (flag && (*flag==chLatin_f || *flag==chDigit_0)) {\r
240         log.info("SAML attribute queries disabled");\r
241         m_allowQuery = false;\r
242     }\r
243 \r
244     DOMElement* child = XMLHelper::getFirstChildElement(e, SIMPLE_NS, _AttributeDecoder);\r
245     while (child) {\r
246         auto_ptr_char id(child->getAttributeNS(NULL, _id));\r
247         auto_ptr_char type(child->getAttributeNS(NULL, _type));\r
248         try {\r
249             log.info("building AttributeDecoder (%s) of type %s", id.get(), type.get());\r
250             m_decoderMap[id.get()] = SPConfig::getConfig().AttributeDecoderManager.newPlugin(type.get(), child);\r
251         }\r
252         catch (exception& ex) {\r
253             log.error("error building AttributeDecoder (%s): %s", id.get(), ex.what());\r
254         }\r
255         child = XMLHelper::getNextSiblingElement(child, SIMPLE_NS, _AttributeDecoder);\r
256     }\r
257     \r
258     child = XMLHelper::getFirstChildElement(e, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME);\r
259     while (child) {\r
260         // Check for missing Name.\r
261         const XMLCh* name = child->getAttributeNS(NULL, saml2::Attribute::NAME_ATTRIB_NAME);\r
262         if (!name || !*name) {\r
263             log.warn("skipping saml:Attribute declared with no Name");\r
264             child = XMLHelper::getNextSiblingElement(child, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME);\r
265             continue;\r
266         }\r
267 \r
268         const AttributeDecoder* decoder=NULL;\r
269         auto_ptr_char id(child->getAttributeNS(NULL, saml2::Attribute::FRIENDLYNAME_ATTRIB_NAME));\r
270         auto_ptr_char d(child->getAttributeNS(SIMPLE_NS, decoderId));\r
271         if (!id.get() || !*id.get() || !d.get() || !*d.get() || !(decoder=m_decoderMap[d.get()])) {\r
272             log.warn("skipping saml:Attribute declared with no FriendlyName or resolvable AttributeDecoder");\r
273             child = XMLHelper::getNextSiblingElement(child, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME);\r
274             continue;\r
275         }\r
276         \r
277         // Empty NameFormat implies the usual Shib URI naming defaults.\r
278         const XMLCh* format = child->getAttributeNS(NULL, saml2::Attribute::NAMEFORMAT_ATTRIB_NAME);\r
279         if (!format || XMLString::equals(format, shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI) ||\r
280                 XMLString::equals(format, saml2::Attribute::URI_REFERENCE))\r
281             format = &chNull;  // ignore default Format/Namespace values\r
282 \r
283         // Fetch/create the map entry and see if it's a duplicate rule.\r
284 #ifdef HAVE_GOOD_STL\r
285         pair<const AttributeDecoder*,string>& decl = m_attrMap[make_pair(name,format)];\r
286 #else\r
287         auto_ptr_char n(name);\r
288         auto_ptr_char f(format);\r
289         pair<const AttributeDecoder*,string>& decl = m_attrMap[make_pair(n.get(),f.get())];\r
290 #endif\r
291         if (decl.first) {\r
292             log.warn("skipping duplicate saml:Attribute declaration (same Name and NameFormat)");\r
293             child = XMLHelper::getNextSiblingElement(child, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME);\r
294             continue;\r
295         }\r
296 \r
297         if (log.isDebugEnabled()) {\r
298 #ifdef HAVE_GOOD_STL\r
299             auto_ptr_char n(name);\r
300             auto_ptr_char f(format);\r
301 #endif\r
302             log.debug("creating declaration for Attribute %s%s%s", n.get(), *f.get() ? ", Format/Namespace:" : "", f.get());\r
303         }\r
304         \r
305         decl.first = decoder;\r
306         decl.second = id.get();\r
307         \r
308         child = XMLHelper::getNextSiblingElement(child, samlconstants::SAML20_NS, saml2::Attribute::LOCAL_NAME);\r
309     }\r
310 }\r
311 \r
312 void SimpleResolverImpl::resolve(\r
313     ResolutionContext& ctx, const saml1::Assertion* token, const vector<const char*>* attributes\r
314     ) const\r
315 {\r
316     set<string> aset;\r
317     if (attributes)\r
318         for(vector<const char*>::const_iterator i=attributes->begin(); i!=attributes->end(); ++i)\r
319             aset.insert(*i);\r
320 \r
321     vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
322 \r
323     auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL);\r
324     const char* relyingParty = ctx.getApplication().getString("providerId").second;\r
325 \r
326 #ifdef HAVE_GOOD_STL\r
327     map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
328 #else\r
329     map< pair<string,string>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
330 #endif\r
331 \r
332     // Check the NameID based on the format.\r
333     const XMLCh* name;\r
334     const XMLCh* format = ctx.getNameID().getFormat();\r
335     if (!format) {\r
336         format = NameID::UNSPECIFIED;\r
337 #ifdef HAVE_GOOD_STL\r
338         if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {\r
339 #else\r
340         auto_ptr_char temp(format);\r
341         if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {\r
342 #endif\r
343             if (aset.empty() || aset.count(rule->second.second)) {\r
344                 resolved.push_back(\r
345                     rule->second.first->decode(\r
346                         rule->second.second.c_str(), &ctx.getNameID(), assertingParty.get(), relyingParty\r
347                         )\r
348                     );\r
349             }\r
350         }\r
351     }\r
352 \r
353     const vector<saml1::AttributeStatement*>& statements = token->getAttributeStatements();\r
354     for (vector<saml1::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {\r
355         const vector<saml1::Attribute*>& attrs = const_cast<const saml1::AttributeStatement*>(*s)->getAttributes();\r
356         for (vector<saml1::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) {\r
357             name = (*a)->getAttributeName();\r
358             format = (*a)->getAttributeNamespace();\r
359             if (!name || !*name)\r
360                 continue;\r
361             if (!format)\r
362                 format = &chNull;\r
363 #ifdef HAVE_GOOD_STL\r
364             if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {\r
365 #else\r
366             auto_ptr_char temp1(name);\r
367             auto_ptr_char temp2(format);\r
368             if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {\r
369 #endif\r
370                 if (aset.empty() || aset.count(rule->second.second)) {\r
371                     resolved.push_back(\r
372                         rule->second.first->decode(rule->second.second.c_str(), *a, assertingParty.get(), relyingParty)\r
373                         );\r
374                 }\r
375             }\r
376         }\r
377     }\r
378 }\r
379 \r
380 void SimpleResolverImpl::resolve(\r
381     ResolutionContext& ctx, const saml2::Assertion* token, const vector<const char*>* attributes\r
382     ) const\r
383 {\r
384     set<string> aset;\r
385     if (attributes)\r
386         for(vector<const char*>::const_iterator i=attributes->begin(); i!=attributes->end(); ++i)\r
387             aset.insert(*i);\r
388 \r
389     vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
390 \r
391     auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL);\r
392     const char* relyingParty = ctx.getApplication().getString("providerId").second;\r
393 \r
394 #ifdef HAVE_GOOD_STL\r
395     map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
396 #else\r
397     map< pair<string,string>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
398 #endif\r
399 \r
400     // Check the NameID based on the format.\r
401     const XMLCh* name;\r
402     const XMLCh* format = ctx.getNameID().getFormat();\r
403     if (!format) {\r
404         format = NameID::UNSPECIFIED;\r
405 #ifdef HAVE_GOOD_STL\r
406         if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {\r
407 #else\r
408         auto_ptr_char temp(format);\r
409         if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {\r
410 #endif\r
411             if (aset.empty() || aset.count(rule->second.second)) {\r
412                 resolved.push_back(\r
413                     rule->second.first->decode(\r
414                         rule->second.second.c_str(), &ctx.getNameID(), assertingParty.get(), relyingParty\r
415                         )\r
416                     );\r
417             }\r
418         }\r
419     }\r
420 \r
421     const vector<saml2::AttributeStatement*>& statements = token->getAttributeStatements();\r
422     for (vector<saml2::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {\r
423         const vector<saml2::Attribute*>& attrs = const_cast<const saml2::AttributeStatement*>(*s)->getAttributes();\r
424         for (vector<saml2::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) {\r
425             name = (*a)->getName();\r
426             format = (*a)->getNameFormat();\r
427             if (!name || !*name)\r
428                 continue;\r
429             if (!format)\r
430                 format = &chNull;\r
431 #ifdef HAVE_GOOD_STL\r
432             if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {\r
433 #else\r
434             auto_ptr_char temp1(name);\r
435             auto_ptr_char temp2(format);\r
436             if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {\r
437 #endif\r
438                 if (aset.empty() || aset.count(rule->second.second)) {\r
439                     resolved.push_back(\r
440                         rule->second.first->decode(rule->second.second.c_str(), *a, assertingParty.get(), relyingParty)\r
441                         );\r
442                 }\r
443             }\r
444         }\r
445     }\r
446 }\r
447 \r
448 void SimpleResolverImpl::query(ResolutionContext& ctx, const NameIdentifier& nameid, const vector<const char*>* attributes) const\r
449 {\r
450 #ifdef _DEBUG\r
451     xmltooling::NDC ndc("query");\r
452 #endif\r
453     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
454 \r
455     const EntityDescriptor* entity = ctx.getEntityDescriptor();\r
456     if (!entity) {\r
457         log.debug("no issuer information available, skipping query");\r
458         return;\r
459     }\r
460 \r
461     int version = 1;\r
462     const AttributeAuthorityDescriptor* AA = entity->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);\r
463     if (!AA) {\r
464         AA = entity->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);\r
465         version = 0;\r
466     }\r
467     if (!AA) {\r
468         log.info("no SAML 1.x AttributeAuthority role found in metadata");\r
469         return;\r
470     }\r
471 \r
472     SecurityPolicy policy;\r
473     shibsp::SOAPClient soaper(ctx.getApplication(),policy);\r
474 \r
475     auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP);\r
476     saml1p::Response* response=NULL;\r
477     const vector<AttributeService*>& endpoints=AA->getAttributeServices();\r
478     for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {\r
479         try {\r
480             if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
481                 continue;\r
482             auto_ptr_char loc((*ep)->getLocation());\r
483             auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second);\r
484             saml1::Subject* subject = saml1::SubjectBuilder::buildSubject();\r
485             subject->setNameIdentifier(nameid.cloneNameIdentifier());\r
486             saml1p::AttributeQuery* query = saml1p::AttributeQueryBuilder::buildAttributeQuery();\r
487             query->setSubject(subject);\r
488             Request* request = RequestBuilder::buildRequest();\r
489             request->setAttributeQuery(query);\r
490             query->setResource(issuer.get());\r
491             request->setMinorVersion(version);\r
492             SAML1SOAPClient client(soaper);\r
493             client.sendSAML(request, *AA, loc.get());\r
494             response = client.receiveSAML();\r
495         }\r
496         catch (exception& ex) {\r
497             log.error("exception making SAML query: %s", ex.what());\r
498             soaper.reset();\r
499         }\r
500     }\r
501 \r
502     if (!response) {\r
503         log.error("unable to successfully query for attributes");\r
504         return;\r
505     }\r
506 \r
507     time_t now = time(NULL);\r
508     const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA);\r
509     const vector<saml1::Assertion*>& assertions = const_cast<const saml1p::Response*>(response)->getAssertions();\r
510     if (assertions.size()==1) {\r
511         auto_ptr<saml1p::Response> wrapper(response);\r
512         saml1::Assertion* newtoken = assertions.front();\r
513         if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer())) {\r
514             log.error("assertion issued by someone other than AA, rejecting it");\r
515             return;\r
516         }\r
517         try {\r
518             tokval->validate(newtoken);\r
519         }\r
520         catch (exception& ex) {\r
521             log.error("assertion failed validation check: %s", ex.what());\r
522         }\r
523         newtoken->detach();\r
524         wrapper.release();\r
525         ctx.getResolvedAssertions().push_back(newtoken);\r
526         resolve(ctx, newtoken, attributes);\r
527     }\r
528     else {\r
529         auto_ptr<saml1p::Response> wrapper(response);\r
530         for (vector<saml1::Assertion*>::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) {\r
531             if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer())) {\r
532                 log.error("assertion issued by someone other than AA, rejecting it");\r
533                 continue;\r
534             }\r
535             try {\r
536                 tokval->validate(*a);\r
537             }\r
538             catch (exception& ex) {\r
539                 log.error("assertion failed validation check: %s", ex.what());\r
540             }\r
541             resolve(ctx, *a, attributes);\r
542             ctx.getResolvedAssertions().push_back((*a)->cloneAssertion());\r
543         }\r
544     }\r
545 }\r
546 \r
547 void SimpleResolverImpl::query(ResolutionContext& ctx, const NameID& nameid, const vector<const char*>* attributes) const\r
548 {\r
549 #ifdef _DEBUG\r
550     xmltooling::NDC ndc("query");\r
551 #endif\r
552     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
553 \r
554     const EntityDescriptor* entity = ctx.getEntityDescriptor();\r
555     if (!entity) {\r
556         log.debug("no issuer information available, skipping query");\r
557         return;\r
558     }\r
559     const AttributeAuthorityDescriptor* AA = entity->getAttributeAuthorityDescriptor(samlconstants::SAML20P_NS);\r
560     if (!AA) {\r
561         log.info("no SAML 2 AttributeAuthority role found in metadata");\r
562         return;\r
563     }\r
564 \r
565     SecurityPolicy policy;\r
566     shibsp::SOAPClient soaper(ctx.getApplication(),policy);\r
567 \r
568     auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);\r
569     saml2p::StatusResponseType* srt=NULL;\r
570     const vector<AttributeService*>& endpoints=AA->getAttributeServices();\r
571     for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !srt && ep!=endpoints.end(); ++ep) {\r
572         try {\r
573             if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
574                 continue;\r
575             auto_ptr_char loc((*ep)->getLocation());\r
576             auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second);\r
577             saml2::Subject* subject = saml2::SubjectBuilder::buildSubject();\r
578             subject->setNameID(nameid.cloneNameID());\r
579             saml2p::AttributeQuery* query = saml2p::AttributeQueryBuilder::buildAttributeQuery();\r
580             query->setSubject(subject);\r
581             Issuer* iss = IssuerBuilder::buildIssuer();\r
582             query->setIssuer(iss);\r
583             iss->setName(issuer.get());\r
584             SAML2SOAPClient client(soaper);\r
585             client.sendSAML(query, *AA, loc.get());\r
586             srt = client.receiveSAML();\r
587         }\r
588         catch (exception& ex) {\r
589             log.error("exception making SAML query: %s", ex.what());\r
590             soaper.reset();\r
591         }\r
592     }\r
593 \r
594     if (!srt) {\r
595         log.error("unable to successfully query for attributes");\r
596         return;\r
597     }\r
598     saml2p::Response* response = dynamic_cast<saml2p::Response*>(srt);\r
599     if (!response) {\r
600         delete srt;\r
601         log.error("message was not a samlp:Response");\r
602         return;\r
603     }\r
604 \r
605     time_t now = time(NULL);\r
606     const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA);\r
607     const vector<saml2::Assertion*>& assertions = const_cast<const saml2p::Response*>(response)->getAssertions();\r
608     if (assertions.size()==1) {\r
609         auto_ptr<saml2p::Response> wrapper(response);\r
610         saml2::Assertion* newtoken = assertions.front();\r
611         if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer() ? newtoken->getIssuer()->getName() : NULL)) {\r
612             log.error("assertion issued by someone other than AA, rejecting it");\r
613             return;\r
614         }\r
615         try {\r
616             tokval->validate(newtoken);\r
617         }\r
618         catch (exception& ex) {\r
619             log.error("assertion failed validation check: %s", ex.what());\r
620         }\r
621         newtoken->detach();\r
622         wrapper.release();\r
623         ctx.getResolvedAssertions().push_back(newtoken);\r
624         resolve(ctx, newtoken, attributes);\r
625     }\r
626     else {\r
627         auto_ptr<saml2p::Response> wrapper(response);\r
628         for (vector<saml2::Assertion*>::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) {\r
629             if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer() ? (*a)->getIssuer()->getName() : NULL)) {\r
630                 log.error("assertion issued by someone other than AA, rejecting it");\r
631                 return;\r
632             }\r
633             try {\r
634                 tokval->validate(*a);\r
635             }\r
636             catch (exception& ex) {\r
637                 log.error("assertion failed validation check: %s", ex.what());\r
638             }\r
639             resolve(ctx, *a, attributes);\r
640             ctx.getResolvedAssertions().push_back((*a)->cloneAssertion());\r
641         }\r
642     }\r
643 }\r
644 \r
645 void SimpleResolver::resolveAttributes(ResolutionContext& ctx, const vector<const char*>* attributes) const\r
646 {\r
647 #ifdef _DEBUG\r
648     xmltooling::NDC ndc("resolveAttributes");\r
649 #endif\r
650     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
651     \r
652     log.debug("examining tokens to resolve");\r
653 \r
654     bool query = m_impl->m_allowQuery;\r
655     const saml1::Assertion* token1;\r
656     const saml2::Assertion* token2;\r
657     if (ctx.getTokens()) {\r
658         for (vector<const opensaml::Assertion*>::const_iterator t = ctx.getTokens()->begin(); t!=ctx.getTokens()->end(); ++t) {\r
659             token2 = dynamic_cast<const saml2::Assertion*>(*t);\r
660             if (token2 && !token2->getAttributeStatements().empty()) {\r
661                 log.debug("resolving SAML 2 token with an AttributeStatement");\r
662                 m_impl->resolve(ctx, token2, attributes);\r
663                 query = false;\r
664             }\r
665             else {\r
666                 token1 = dynamic_cast<const saml1::Assertion*>(*t);\r
667                 if (token1 && !token1->getAttributeStatements().empty()) {\r
668                     log.debug("resolving SAML 1 token with an AttributeStatement");\r
669                     m_impl->resolve(ctx, token1, attributes);\r
670                     query = false;\r
671                 }\r
672             }\r
673         }\r
674     }\r
675 \r
676     if (query) {\r
677         if (token1 && !token1->getAuthenticationStatements().empty()) {\r
678             log.debug("attempting SAML 1.x attribute query");\r
679             return m_impl->query(ctx, *(token1->getAuthenticationStatements().front()->getSubject()->getNameIdentifier()), attributes);\r
680         }\r
681         log.debug("attempting SAML 2.0 attribute query");\r
682         m_impl->query(ctx, ctx.getNameID(), attributes);\r
683     }\r
684 }\r
685 \r
686 pair<bool,DOMElement*> SimpleResolver::load()\r
687 {\r
688     // Load from source using base class.\r
689     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();\r
690     \r
691     // If we own it, wrap it.\r
692     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);\r
693 \r
694     SimpleResolverImpl* impl = new SimpleResolverImpl(raw.second);\r
695     \r
696     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.\r
697     impl->setDocument(docjanitor.release());\r
698 \r
699     delete m_impl;\r
700     m_impl = impl;\r
701 \r
702     return make_pair(false,(DOMElement*)NULL);\r
703 }\r