Rework decoder handling in simple resolver, add IdP/SP names to decoder API, hook...
[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 _AttributeDecoder[] =    UNICODE_LITERAL_16(A,t,t,r,i,b,u,t,e,D,e,c,o,d,e,r);\r
230     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
231     static const XMLCh allowQuery[] =           UNICODE_LITERAL_10(a,l,l,o,w,Q,u,e,r,y);\r
232     static const XMLCh decoderId[] =            UNICODE_LITERAL_9(d,e,c,o,d,e,r,I,d);\r
233     static const XMLCh _id[] =                  UNICODE_LITERAL_2(i,d);\r
234     static const XMLCh _type[] =                UNICODE_LITERAL_4(t,y,p,e);\r
235 };\r
236 \r
237 SimpleResolverImpl::SimpleResolverImpl(const DOMElement* e) : m_document(NULL), m_allowQuery(true)\r
238 {\r
239 #ifdef _DEBUG\r
240     xmltooling::NDC ndc("SimpleResolverImpl");\r
241 #endif\r
242     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
243     \r
244     if (!XMLHelper::isNodeNamed(e, SIMPLE_NS, _AttributeResolver))\r
245         throw ConfigurationException("Simple resolver requires resolver:AttributeResolver at root of configuration.");\r
246     \r
247     const XMLCh* flag = e->getAttributeNS(NULL,allowQuery);\r
248     if (flag && (*flag==chLatin_f || *flag==chDigit_0)) {\r
249         log.info("SAML attribute queries disabled");\r
250         m_allowQuery = false;\r
251     }\r
252 \r
253     DOMElement* child = XMLHelper::getFirstChildElement(e, SIMPLE_NS, _AttributeDecoder);\r
254     while (child) {\r
255         auto_ptr_char id(child->getAttributeNS(NULL, _id));\r
256         auto_ptr_char type(child->getAttributeNS(NULL, _type));\r
257         try {\r
258             log.info("building AttributeDecoder (%s) of type %s", id.get(), type.get());\r
259             m_decoderMap[id.get()] = SPConfig::getConfig().AttributeDecoderManager.newPlugin(type.get(), child);\r
260         }\r
261         catch (exception& ex) {\r
262             log.error("error building AttributeDecoder (%s): %s", id.get(), ex.what());\r
263         }\r
264         child = XMLHelper::getNextSiblingElement(child, SIMPLE_NS, _AttributeDecoder);\r
265     }\r
266     \r
267     child = XMLHelper::getFirstChildElement(e, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
268     while (child) {\r
269         // Check for missing Name.\r
270         const XMLCh* name = child->getAttributeNS(NULL, opensaml::saml2::Attribute::NAME_ATTRIB_NAME);\r
271         if (!name || !*name) {\r
272             log.warn("skipping saml:Attribute declared with no Name");\r
273             child = XMLHelper::getNextSiblingElement(child, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
274             continue;\r
275         }\r
276 \r
277         const AttributeDecoder* decoder=NULL;\r
278         auto_ptr_char id(child->getAttributeNS(NULL, opensaml::saml2::Attribute::FRIENDLYNAME_ATTRIB_NAME));\r
279         auto_ptr_char d(child->getAttributeNS(SIMPLE_NS, decoderId));\r
280         if (!id.get() || !*id.get() || !d.get() || !*d.get() || !(decoder=m_decoderMap[d.get()])) {\r
281             log.warn("skipping saml:Attribute declared with no FriendlyName or resolvable AttributeDecoder");\r
282             child = XMLHelper::getNextSiblingElement(child, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
283             continue;\r
284         }\r
285         \r
286         // Empty NameFormat implies the usual Shib URI naming defaults.\r
287         const XMLCh* format = child->getAttributeNS(NULL, opensaml::saml2::Attribute::NAMEFORMAT_ATTRIB_NAME);\r
288         if (!format || XMLString::equals(format, shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI) ||\r
289                 XMLString::equals(format, opensaml::saml2::Attribute::URI_REFERENCE))\r
290             format = &chNull;  // ignore default Format/Namespace values\r
291 \r
292         // Fetch/create the map entry and see if it's a duplicate rule.\r
293 #ifdef HAVE_GOOD_STL\r
294         pair<const AttributeDecoder*,string>& decl = m_attrMap[make_pair(name,format)];\r
295 #else\r
296         auto_ptr_char n(name);\r
297         auto_ptr_char f(format);\r
298         pair<const AttributeDecoder*,string>& decl = m_attrMap[make_pair(n.get(),f.get())];\r
299 #endif\r
300         if (decl.first) {\r
301             log.warn("skipping duplicate saml:Attribute declaration (same Name and NameFormat)");\r
302             child = XMLHelper::getNextSiblingElement(child, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
303             continue;\r
304         }\r
305 \r
306         if (log.isDebugEnabled()) {\r
307 #ifdef HAVE_GOOD_STL\r
308             auto_ptr_char n(name);\r
309             auto_ptr_char f(format);\r
310 #endif\r
311             log.debug("creating declaration for Attribute %s%s%s", n.get(), *f.get() ? ", Format/Namespace:" : "", f.get());\r
312         }\r
313         \r
314         decl.first = decoder;\r
315         decl.second = id.get();\r
316         \r
317         child = XMLHelper::getNextSiblingElement(child, samlconstants::SAML20_NS, opensaml::saml2::Attribute::LOCAL_NAME);\r
318     }\r
319 }\r
320 \r
321 void SimpleResolverImpl::resolve(\r
322     ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector<const char*>* attributes\r
323     ) const\r
324 {\r
325     set<string> aset;\r
326     if (attributes)\r
327         for(vector<const char*>::const_iterator i=attributes->begin(); i!=attributes->end(); ++i)\r
328             aset.insert(*i);\r
329 \r
330     vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
331 \r
332     auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL);\r
333     const char* relyingParty = ctx.getApplication().getString("providerId").second;\r
334 \r
335 #ifdef HAVE_GOOD_STL\r
336     map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
337 #else\r
338     map< pair<string,string>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
339 #endif\r
340 \r
341     // Check the NameID based on the format.\r
342     const XMLCh* name;\r
343     const XMLCh* format = ctx.getNameID().getFormat();\r
344     if (!format) {\r
345         format = NameID::UNSPECIFIED;\r
346 #ifdef HAVE_GOOD_STL\r
347         if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {\r
348 #else\r
349         auto_ptr_char temp(format);\r
350         if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {\r
351 #endif\r
352             if (aset.empty() || aset.count(rule->second.second)) {\r
353                 resolved.push_back(\r
354                     rule->second.first->decode(\r
355                         rule->second.second.c_str(), &ctx.getNameID(), assertingParty.get(), relyingParty\r
356                         )\r
357                     );\r
358             }\r
359         }\r
360     }\r
361 \r
362     const vector<opensaml::saml1::AttributeStatement*>& statements = token->getAttributeStatements();\r
363     for (vector<opensaml::saml1::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {\r
364         const vector<opensaml::saml1::Attribute*>& attrs = const_cast<const opensaml::saml1::AttributeStatement*>(*s)->getAttributes();\r
365         for (vector<opensaml::saml1::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) {\r
366             name = (*a)->getAttributeName();\r
367             format = (*a)->getAttributeNamespace();\r
368             if (!name || !*name)\r
369                 continue;\r
370             if (!format)\r
371                 format = &chNull;\r
372 #ifdef HAVE_GOOD_STL\r
373             if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {\r
374 #else\r
375             auto_ptr_char temp1(name);\r
376             auto_ptr_char temp2(format);\r
377             if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {\r
378 #endif\r
379                 if (aset.empty() || aset.count(rule->second.second)) {\r
380                     resolved.push_back(\r
381                         rule->second.first->decode(rule->second.second.c_str(), *a, assertingParty.get(), relyingParty)\r
382                         );\r
383                 }\r
384             }\r
385         }\r
386     }\r
387 }\r
388 \r
389 void SimpleResolverImpl::resolve(\r
390     ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector<const char*>* attributes\r
391     ) const\r
392 {\r
393     set<string> aset;\r
394     if (attributes)\r
395         for(vector<const char*>::const_iterator i=attributes->begin(); i!=attributes->end(); ++i)\r
396             aset.insert(*i);\r
397 \r
398     vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
399 \r
400     auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL);\r
401     const char* relyingParty = ctx.getApplication().getString("providerId").second;\r
402 \r
403 #ifdef HAVE_GOOD_STL\r
404     map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
405 #else\r
406     map< pair<string,string>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
407 #endif\r
408 \r
409     // Check the NameID based on the format.\r
410     const XMLCh* name;\r
411     const XMLCh* format = ctx.getNameID().getFormat();\r
412     if (!format) {\r
413         format = NameID::UNSPECIFIED;\r
414 #ifdef HAVE_GOOD_STL\r
415         if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {\r
416 #else\r
417         auto_ptr_char temp(format);\r
418         if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {\r
419 #endif\r
420             if (aset.empty() || aset.count(rule->second.second)) {\r
421                 resolved.push_back(\r
422                     rule->second.first->decode(\r
423                         rule->second.second.c_str(), &ctx.getNameID(), assertingParty.get(), relyingParty\r
424                         )\r
425                     );\r
426             }\r
427         }\r
428     }\r
429 \r
430     const vector<opensaml::saml2::AttributeStatement*>& statements = token->getAttributeStatements();\r
431     for (vector<opensaml::saml2::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {\r
432         const vector<opensaml::saml2::Attribute*>& attrs = const_cast<const opensaml::saml2::AttributeStatement*>(*s)->getAttributes();\r
433         for (vector<opensaml::saml2::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) {\r
434             name = (*a)->getName();\r
435             format = (*a)->getNameFormat();\r
436             if (!name || !*name)\r
437                 continue;\r
438             if (!format)\r
439                 format = &chNull;\r
440 #ifdef HAVE_GOOD_STL\r
441             if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {\r
442 #else\r
443             auto_ptr_char temp1(name);\r
444             auto_ptr_char temp2(format);\r
445             if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {\r
446 #endif\r
447                 if (aset.empty() || aset.count(rule->second.second)) {\r
448                     resolved.push_back(\r
449                         rule->second.first->decode(rule->second.second.c_str(), *a, assertingParty.get(), relyingParty)\r
450                         );\r
451                 }\r
452             }\r
453         }\r
454     }\r
455 }\r
456 \r
457 void SimpleResolverImpl::query(ResolutionContext& ctx, const opensaml::saml1::Assertion* token, const vector<const char*>* attributes) const\r
458 {\r
459     if (!m_allowQuery)\r
460         return;\r
461 \r
462 #ifdef _DEBUG\r
463     xmltooling::NDC ndc("query");\r
464 #endif\r
465     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
466 \r
467     const EntityDescriptor* entity = ctx.getEntityDescriptor();\r
468     if (!entity) {\r
469         log.debug("no issuer information available, skipping query");\r
470         return;\r
471     }\r
472     const AttributeAuthorityDescriptor* AA =\r
473         entity->getAttributeAuthorityDescriptor(\r
474             token->getMinorVersion().second==1 ? samlconstants::SAML11_PROTOCOL_ENUM : samlconstants::SAML10_PROTOCOL_ENUM\r
475             );\r
476     if (!AA) {\r
477         log.debug("no SAML 1.%d AttributeAuthority role found in metadata", token->getMinorVersion().second);\r
478         return;\r
479     }\r
480 \r
481     SecurityPolicy policy;\r
482     shibsp::SOAPClient soaper(ctx.getApplication(),policy);\r
483 \r
484     auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP);\r
485     opensaml::saml1p::Response* response=NULL;\r
486     const vector<AttributeService*>& endpoints=AA->getAttributeServices();\r
487     for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {\r
488         try {\r
489             if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
490                 continue;\r
491             auto_ptr_char loc((*ep)->getLocation());\r
492             auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second);\r
493             opensaml::saml1::Subject* subject = opensaml::saml1::SubjectBuilder::buildSubject();\r
494             subject->setNameIdentifier(token->getAuthenticationStatements().front()->getSubject()->getNameIdentifier()->cloneNameIdentifier());\r
495             opensaml::saml1p::AttributeQuery* query = opensaml::saml1p::AttributeQueryBuilder::buildAttributeQuery();\r
496             query->setSubject(subject);\r
497             Request* request = RequestBuilder::buildRequest();\r
498             request->setAttributeQuery(query);\r
499             query->setResource(issuer.get());\r
500             request->setMinorVersion(token->getMinorVersion().second);\r
501             SAML1SOAPClient client(soaper);\r
502             client.sendSAML(request, *AA, loc.get());\r
503             response = client.receiveSAML();\r
504         }\r
505         catch (exception& ex) {\r
506             log.error("exception making SAML query: %s", ex.what());\r
507             soaper.reset();\r
508         }\r
509     }\r
510 \r
511     if (!response) {\r
512         log.error("unable to successfully query for attributes");\r
513         return;\r
514     }\r
515 \r
516     time_t now = time(NULL);\r
517     const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA);\r
518     const vector<opensaml::saml1::Assertion*>& assertions = const_cast<const opensaml::saml1p::Response*>(response)->getAssertions();\r
519     if (assertions.size()==1) {\r
520         auto_ptr<opensaml::saml1p::Response> wrapper(response);\r
521         opensaml::saml1::Assertion* newtoken = assertions.front();\r
522         if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer())) {\r
523             log.error("assertion issued by someone other than AA, rejecting it");\r
524             return;\r
525         }\r
526         try {\r
527             tokval->validate(newtoken);\r
528         }\r
529         catch (exception& ex) {\r
530             log.error("assertion failed validation check: %s", ex.what());\r
531         }\r
532         newtoken->detach();\r
533         wrapper.release();\r
534         ctx.getResolvedAssertions().push_back(newtoken);\r
535         resolve(ctx, newtoken, attributes);\r
536     }\r
537     else {\r
538         auto_ptr<opensaml::saml1p::Response> wrapper(response);\r
539         for (vector<opensaml::saml1::Assertion*>::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) {\r
540             if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer())) {\r
541                 log.error("assertion issued by someone other than AA, rejecting it");\r
542                 continue;\r
543             }\r
544             try {\r
545                 tokval->validate(*a);\r
546             }\r
547             catch (exception& ex) {\r
548                 log.error("assertion failed validation check: %s", ex.what());\r
549             }\r
550             resolve(ctx, *a, attributes);\r
551             ctx.getResolvedAssertions().push_back((*a)->cloneAssertion());\r
552         }\r
553     }\r
554 }\r
555 \r
556 void SimpleResolverImpl::query(ResolutionContext& ctx, const opensaml::saml2::Assertion* token, const vector<const char*>* attributes) const\r
557 {\r
558     if (!m_allowQuery)\r
559         return;\r
560 \r
561 #ifdef _DEBUG\r
562     xmltooling::NDC ndc("query");\r
563 #endif\r
564     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
565 \r
566     const EntityDescriptor* entity = ctx.getEntityDescriptor();\r
567     if (!entity) {\r
568         log.debug("no issuer information available, skipping query");\r
569         return;\r
570     }\r
571     const AttributeAuthorityDescriptor* AA = entity->getAttributeAuthorityDescriptor(samlconstants::SAML20P_NS);\r
572     if (!AA) {\r
573         log.debug("no SAML 2 AttributeAuthority role found in metadata");\r
574         return;\r
575     }\r
576 \r
577     SecurityPolicy policy;\r
578     shibsp::SOAPClient soaper(ctx.getApplication(),policy);\r
579 \r
580     auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);\r
581     opensaml::saml2p::StatusResponseType* srt=NULL;\r
582     const vector<AttributeService*>& endpoints=AA->getAttributeServices();\r
583     for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !srt && ep!=endpoints.end(); ++ep) {\r
584         try {\r
585             if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
586                 continue;\r
587             auto_ptr_char loc((*ep)->getLocation());\r
588             auto_ptr_XMLCh issuer(ctx.getApplication().getString("providerId").second);\r
589             opensaml::saml2::Subject* subject = opensaml::saml2::SubjectBuilder::buildSubject();\r
590             subject->setNameID(token->getSubject()->getNameID()->cloneNameID());\r
591             opensaml::saml2p::AttributeQuery* query = opensaml::saml2p::AttributeQueryBuilder::buildAttributeQuery();\r
592             query->setSubject(subject);\r
593             Issuer* iss = IssuerBuilder::buildIssuer();\r
594             query->setIssuer(iss);\r
595             iss->setName(issuer.get());\r
596             SAML2SOAPClient client(soaper);\r
597             client.sendSAML(query, *AA, loc.get());\r
598             srt = client.receiveSAML();\r
599         }\r
600         catch (exception& ex) {\r
601             log.error("exception making SAML query: %s", ex.what());\r
602             soaper.reset();\r
603         }\r
604     }\r
605 \r
606     if (!srt) {\r
607         log.error("unable to successfully query for attributes");\r
608         return;\r
609     }\r
610     opensaml::saml2p::Response* response = dynamic_cast<opensaml::saml2p::Response*>(srt);\r
611     if (!response) {\r
612         delete srt;\r
613         log.error("message was not a samlp:Response");\r
614         return;\r
615     }\r
616 \r
617     time_t now = time(NULL);\r
618     const Validator* tokval = ctx.getApplication().getTokenValidator(now, AA);\r
619     const vector<opensaml::saml2::Assertion*>& assertions = const_cast<const opensaml::saml2p::Response*>(response)->getAssertions();\r
620     if (assertions.size()==1) {\r
621         auto_ptr<opensaml::saml2p::Response> wrapper(response);\r
622         opensaml::saml2::Assertion* newtoken = assertions.front();\r
623         if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, newtoken->getIssuer() ? newtoken->getIssuer()->getName() : NULL)) {\r
624             log.error("assertion issued by someone other than AA, rejecting it");\r
625             return;\r
626         }\r
627         try {\r
628             tokval->validate(newtoken);\r
629         }\r
630         catch (exception& ex) {\r
631             log.error("assertion failed validation check: %s", ex.what());\r
632         }\r
633         newtoken->detach();\r
634         wrapper.release();\r
635         ctx.getResolvedAssertions().push_back(newtoken);\r
636         resolve(ctx, newtoken, attributes);\r
637     }\r
638     else {\r
639         auto_ptr<opensaml::saml2p::Response> wrapper(response);\r
640         for (vector<opensaml::saml2::Assertion*>::const_iterator a = assertions.begin(); a!=assertions.end(); ++a) {\r
641             if (!XMLString::equals(policy.getIssuer() ? policy.getIssuer()->getName() : NULL, (*a)->getIssuer() ? (*a)->getIssuer()->getName() : NULL)) {\r
642                 log.error("assertion issued by someone other than AA, rejecting it");\r
643                 return;\r
644             }\r
645             try {\r
646                 tokval->validate(*a);\r
647             }\r
648             catch (exception& ex) {\r
649                 log.error("assertion failed validation check: %s", ex.what());\r
650             }\r
651             resolve(ctx, *a, attributes);\r
652             ctx.getResolvedAssertions().push_back((*a)->cloneAssertion());\r
653         }\r
654     }\r
655 }\r
656 \r
657 void SimpleResolver::resolveAttributes(ResolutionContext& ctx, const vector<const char*>* attributes) const\r
658 {\r
659 #ifdef _DEBUG\r
660     xmltooling::NDC ndc("resolveAttributes");\r
661 #endif\r
662     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
663     \r
664     log.debug("examining incoming SSO token");\r
665 \r
666     const opensaml::RootObject* token = ctx.getSSOToken();\r
667     if (!token) {\r
668         log.warn("no SSO token supplied to resolver, returning nothing");\r
669         return;\r
670     }\r
671     const opensaml::saml2::Assertion* token2 = dynamic_cast<const opensaml::saml2::Assertion*>(token);\r
672     if (token2) {\r
673         if (!token2->getAttributeStatements().empty()) {\r
674             log.debug("found SAML 2 SSO token with an AttributeStatement");\r
675             return m_impl->resolve(ctx, token2, attributes);\r
676         }\r
677         return m_impl->query(ctx, token2, attributes);\r
678     }\r
679 \r
680     const opensaml::saml1::Assertion* token1 = dynamic_cast<const opensaml::saml1::Assertion*>(token);\r
681     if (token1) {\r
682         if (!token1->getAttributeStatements().empty()) {\r
683             log.debug("found SAML 1 SSO token with an AttributeStatement");\r
684             return m_impl->resolve(ctx, token1, attributes);\r
685         }\r
686         return m_impl->query(ctx, token1, attributes);\r
687     }\r
688 \r
689     log.warn("unrecognized token type, returning nothing");\r
690 }\r
691 \r
692 pair<bool,DOMElement*> SimpleResolver::load()\r
693 {\r
694     // Load from source using base class.\r
695     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();\r
696     \r
697     // If we own it, wrap it.\r
698     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);\r
699 \r
700     SimpleResolverImpl* impl = new SimpleResolverImpl(raw.second);\r
701     \r
702     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.\r
703     impl->setDocument(docjanitor.release());\r
704 \r
705     delete m_impl;\r
706     m_impl = impl;\r
707 \r
708     return make_pair(false,(DOMElement*)NULL);\r
709 }\r