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