Improve property inheritance, first batch of SessionInitiators, rename providerId.
[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 "ServiceProvider.h"\r
26 #include "SessionCache.h"\r
27 #include "attribute/AttributeDecoder.h"\r
28 #include "attribute/resolver/AttributeResolver.h"\r
29 #include "attribute/resolver/ResolutionContext.h"\r
30 #include "binding/SOAPClient.h"\r
31 #include "util/SPConstants.h"\r
32 \r
33 \r
34 #include <log4cpp/Category.hh>\r
35 #include <saml/binding/SecurityPolicy.h>\r
36 #include <saml/saml1/binding/SAML1SOAPClient.h>\r
37 #include <saml/saml1/core/Assertions.h>\r
38 #include <saml/saml1/core/Protocols.h>\r
39 #include <saml/saml1/profile/AssertionValidator.h>\r
40 #include <saml/saml2/binding/SAML2SOAPClient.h>\r
41 #include <saml/saml2/core/Protocols.h>\r
42 #include <saml/saml2/metadata/Metadata.h>\r
43 #include <saml/saml2/metadata/MetadataProvider.h>\r
44 #include <saml/saml2/profile/AssertionValidator.h>\r
45 #include <xmltooling/util/NDC.h>\r
46 #include <xmltooling/util/ReloadableXMLFile.h>\r
47 #include <xmltooling/util/XMLHelper.h>\r
48 #include <xercesc/util/XMLUniDefs.hpp>\r
49 \r
50 using namespace shibsp;\r
51 using namespace opensaml::saml1;\r
52 using namespace opensaml::saml1p;\r
53 using namespace opensaml::saml2;\r
54 using namespace opensaml::saml2p;\r
55 using namespace opensaml::saml2md;\r
56 using namespace opensaml;\r
57 using namespace xmltooling;\r
58 using namespace log4cpp;\r
59 using namespace std;\r
60 \r
61 namespace shibsp {\r
62 \r
63     class SHIBSP_DLLLOCAL SimpleContext : public ResolutionContext\r
64     {\r
65     public:\r
66         SimpleContext(const Application& application, const Session& session)\r
67             : m_app(application), m_session(&session), m_client_addr(NULL), m_metadata(NULL), m_entity(NULL),\r
68                 m_nameid(session.getNameID()), m_tokens(NULL) {\r
69         }\r
70         \r
71         SimpleContext(\r
72             const Application& application,\r
73             const char* client_addr,\r
74             const EntityDescriptor* issuer,\r
75             const NameID* nameid,\r
76             const vector<const opensaml::Assertion*>* tokens=NULL\r
77             ) : m_app(application), m_session(NULL), m_client_addr(client_addr), m_metadata(NULL), m_entity(issuer),\r
78                 m_nameid(nameid), m_tokens(tokens) {\r
79         }\r
80         \r
81         ~SimpleContext() {\r
82             if (m_metadata)\r
83                 m_metadata->unlock();\r
84             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<shibsp::Attribute>());\r
85             for_each(m_assertions.begin(), m_assertions.end(), xmltooling::cleanup<opensaml::Assertion>());\r
86         }\r
87     \r
88         const Application& getApplication() const {\r
89             return m_app;\r
90         }\r
91         const char* getClientAddress() const {\r
92             return m_session ? m_session->getClientAddress() : m_client_addr;\r
93         }\r
94         const EntityDescriptor* getEntityDescriptor() const {\r
95             if (m_entity)\r
96                 return m_entity;\r
97             if (m_session && m_session->getEntityID()) {\r
98                 m_metadata = m_app.getMetadataProvider();\r
99                 if (m_metadata) {\r
100                     m_metadata->lock();\r
101                     return m_entity = m_metadata->getEntityDescriptor(m_session->getEntityID());\r
102                 }\r
103             }\r
104             return NULL;\r
105         }\r
106         const NameID* getNameID() const {\r
107             return m_nameid;\r
108         }\r
109         const vector<const opensaml::Assertion*>* getTokens() const {\r
110             return m_tokens;\r
111         }\r
112         const Session* getSession() const {\r
113             return m_session;\r
114         }        \r
115         vector<shibsp::Attribute*>& getResolvedAttributes() {\r
116             return m_attributes;\r
117         }\r
118         vector<opensaml::Assertion*>& getResolvedAssertions() {\r
119             return m_assertions;\r
120         }\r
121 \r
122     private:\r
123         const Application& m_app;\r
124         const Session* m_session;\r
125         const char* m_client_addr;\r
126         mutable MetadataProvider* m_metadata;\r
127         mutable const EntityDescriptor* m_entity;\r
128         const NameID* m_nameid;\r
129         const vector<const opensaml::Assertion*>* m_tokens;\r
130         vector<shibsp::Attribute*> m_attributes;\r
131         vector<opensaml::Assertion*> m_assertions;\r
132     };\r
133     \r
134 #if defined (_MSC_VER)\r
135     #pragma warning( push )\r
136     #pragma warning( disable : 4250 )\r
137 #endif\r
138 \r
139     class SimpleResolverImpl\r
140     {\r
141     public:\r
142         SimpleResolverImpl(const DOMElement* e);\r
143         ~SimpleResolverImpl() {\r
144             for_each(m_decoderMap.begin(), m_decoderMap.end(), cleanup_pair<string,AttributeDecoder>());\r
145             if (m_document)\r
146                 m_document->release();\r
147         }\r
148 \r
149         void setDocument(DOMDocument* doc) {\r
150             m_document = doc;\r
151         }\r
152 \r
153         void query(\r
154             ResolutionContext& ctx, const NameIdentifier& nameid, const set<string>* attributes=NULL\r
155             ) const;\r
156         void query(\r
157             ResolutionContext& ctx, const NameID& nameid, const set<string>* attributes=NULL\r
158             ) const;\r
159         void resolve(\r
160             ResolutionContext& ctx, const saml1::Assertion* token, const set<string>* attributes=NULL\r
161             ) const;\r
162         void resolve(\r
163             ResolutionContext& ctx, const saml2::Assertion* token, const set<string>* attributes=NULL\r
164             ) const;\r
165 \r
166         bool m_allowQuery;\r
167 \r
168     private:\r
169         void populateQuery(saml1p::AttributeQuery& query, const string& id) const;\r
170         void populateQuery(saml2p::AttributeQuery& query, const string& id) const;\r
171 \r
172         DOMDocument* m_document;\r
173         map<string,AttributeDecoder*> m_decoderMap;\r
174 #ifdef HAVE_GOOD_STL\r
175         typedef map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> > attrmap_t;\r
176 #else\r
177         typedef map< pair<string,string>,pair<const AttributeDecoder*,string> > attrmap_t;\r
178 #endif\r
179         attrmap_t m_attrMap;\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 vector<const opensaml::Assertion*>* tokens=NULL\r
198             ) const {\r
199             return new SimpleContext(application,client_addr,issuer,nameid,tokens);\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 set<string>* 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, saml2::Attribute::LOCAL_NAME);\r
268     while (child) {\r
269         // Check for missing Name.\r
270         const XMLCh* name = child->getAttributeNS(NULL, 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, 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, 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, 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, saml2::Attribute::NAMEFORMAT_ATTRIB_NAME);\r
288         if (!format || XMLString::equals(format, shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI) ||\r
289                 XMLString::equals(format, 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, saml2::Attribute::LOCAL_NAME);\r
303             continue;\r
304         }\r
305 \r
306         if (log.isInfoEnabled()) {\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.info("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, saml2::Attribute::LOCAL_NAME);\r
318     }\r
319 }\r
320 \r
321 void SimpleResolverImpl::resolve(\r
322     ResolutionContext& ctx, const saml1::Assertion* token, const set<string>* attributes\r
323     ) const\r
324 {\r
325     vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
326 \r
327     auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL);\r
328     const char* relyingParty = ctx.getApplication().getString("entityID").second;\r
329 \r
330 #ifdef HAVE_GOOD_STL\r
331     map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
332 #else\r
333     map< pair<string,string>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
334 #endif\r
335 \r
336     const XMLCh* name;\r
337     const XMLCh* format;\r
338     \r
339     // Check the NameID based on the format.\r
340     if (ctx.getNameID()) {\r
341         format = ctx.getNameID()->getFormat();\r
342         if (!format || !*format)\r
343             format = NameID::UNSPECIFIED;\r
344 #ifdef HAVE_GOOD_STL\r
345         if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {\r
346 #else\r
347         auto_ptr_char temp(format);\r
348         if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {\r
349 #endif\r
350             if (!attributes || attributes->count(rule->second.second)) {\r
351                 resolved.push_back(\r
352                     rule->second.first->decode(\r
353                         rule->second.second.c_str(), ctx.getNameID(), assertingParty.get(), relyingParty\r
354                         )\r
355                     );\r
356             }\r
357         }\r
358     }\r
359 \r
360     const vector<saml1::AttributeStatement*>& statements = token->getAttributeStatements();\r
361     for (vector<saml1::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {\r
362         const vector<saml1::Attribute*>& attrs = const_cast<const saml1::AttributeStatement*>(*s)->getAttributes();\r
363         for (vector<saml1::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) {\r
364             name = (*a)->getAttributeName();\r
365             format = (*a)->getAttributeNamespace();\r
366             if (!name || !*name)\r
367                 continue;\r
368             if (!format || XMLString::equals(format, shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI))\r
369                 format = &chNull;\r
370 #ifdef HAVE_GOOD_STL\r
371             if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {\r
372 #else\r
373             auto_ptr_char temp1(name);\r
374             auto_ptr_char temp2(format);\r
375             if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {\r
376 #endif\r
377                 if (!attributes || attributes->count(rule->second.second)) {\r
378                     resolved.push_back(\r
379                         rule->second.first->decode(rule->second.second.c_str(), *a, assertingParty.get(), relyingParty)\r
380                         );\r
381                 }\r
382             }\r
383         }\r
384     }\r
385 }\r
386 \r
387 void SimpleResolverImpl::resolve(\r
388     ResolutionContext& ctx, const saml2::Assertion* token, const set<string>* attributes\r
389     ) const\r
390 {\r
391     vector<shibsp::Attribute*>& resolved = ctx.getResolvedAttributes();\r
392 \r
393     auto_ptr_char assertingParty(ctx.getEntityDescriptor() ? ctx.getEntityDescriptor()->getEntityID() : NULL);\r
394     const char* relyingParty = ctx.getApplication().getString("entityID").second;\r
395 \r
396 #ifdef HAVE_GOOD_STL\r
397     map< pair<xstring,xstring>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
398 #else\r
399     map< pair<string,string>,pair<const AttributeDecoder*,string> >::const_iterator rule;\r
400 #endif\r
401 \r
402     const XMLCh* name;\r
403     const XMLCh* format;\r
404     \r
405     // Check the NameID based on the format.\r
406     if (ctx.getNameID()) {\r
407         format = ctx.getNameID()->getFormat();\r
408         if (!format || !*format)\r
409             format = NameID::UNSPECIFIED;\r
410 #ifdef HAVE_GOOD_STL\r
411         if ((rule=m_attrMap.find(make_pair(format,xstring()))) != m_attrMap.end()) {\r
412 #else\r
413         auto_ptr_char temp(format);\r
414         if ((rule=m_attrMap.find(make_pair(temp.get(),string()))) != m_attrMap.end()) {\r
415 #endif\r
416             if (!attributes || attributes->count(rule->second.second)) {\r
417                 resolved.push_back(\r
418                     rule->second.first->decode(\r
419                         rule->second.second.c_str(), ctx.getNameID(), assertingParty.get(), relyingParty\r
420                         )\r
421                     );\r
422             }\r
423         }\r
424     }\r
425 \r
426     const vector<saml2::AttributeStatement*>& statements = token->getAttributeStatements();\r
427     for (vector<saml2::AttributeStatement*>::const_iterator s = statements.begin(); s!=statements.end(); ++s) {\r
428         const vector<saml2::Attribute*>& attrs = const_cast<const saml2::AttributeStatement*>(*s)->getAttributes();\r
429         for (vector<saml2::Attribute*>::const_iterator a = attrs.begin(); a!=attrs.end(); ++a) {\r
430             name = (*a)->getName();\r
431             format = (*a)->getNameFormat();\r
432             if (!name || !*name)\r
433                 continue;\r
434             if (!format || !*format)\r
435                 format = saml2::Attribute::UNSPECIFIED;\r
436             else if (XMLString::equals(format, saml2::Attribute::URI_REFERENCE))\r
437                 format = &chNull;\r
438 #ifdef HAVE_GOOD_STL\r
439             if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {\r
440 #else\r
441             auto_ptr_char temp1(name);\r
442             auto_ptr_char temp2(format);\r
443             if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {\r
444 #endif\r
445                 if (!attributes || attributes->count(rule->second.second)) {\r
446                     resolved.push_back(\r
447                         rule->second.first->decode(rule->second.second.c_str(), *a, assertingParty.get(), relyingParty)\r
448                         );\r
449                 }\r
450             }\r
451         }\r
452 \r
453         const vector<saml2::EncryptedAttribute*>& encattrs = const_cast<const saml2::AttributeStatement*>(*s)->getEncryptedAttributes();\r
454         if (!encattrs.empty()) {\r
455             const XMLCh* recipient = ctx.getApplication().getXMLString("entityID").second;\r
456             CredentialResolver* cr = ctx.getApplication().getCredentialResolver();\r
457             if (!cr) {\r
458                 Category::getInstance(SHIBSP_LOGCAT".AttributeResolver").warn(\r
459                     "found encrypted attributes, but no CredentialResolver was available"\r
460                     );\r
461                 return;\r
462             }\r
463 \r
464             // We look up credentials based on the peer who did the encrypting.\r
465             CredentialCriteria cc;\r
466             cc.setPeerName(assertingParty.get());\r
467 \r
468             Locker credlocker(cr);\r
469             for (vector<saml2::EncryptedAttribute*>::const_iterator ea = encattrs.begin(); ea!=encattrs.end(); ++ea) {\r
470                 auto_ptr<XMLObject> decrypted((*ea)->decrypt(*cr, recipient, &cc));\r
471                 const saml2::Attribute* decattr = dynamic_cast<const saml2::Attribute*>(decrypted.get());\r
472                 name = decattr->getName();\r
473                 format = decattr->getNameFormat();\r
474                 if (!name || !*name)\r
475                     continue;\r
476                 if (!format || !*format)\r
477                     format = saml2::Attribute::UNSPECIFIED;\r
478                 else if (XMLString::equals(format, saml2::Attribute::URI_REFERENCE))\r
479                     format = &chNull;\r
480 #ifdef HAVE_GOOD_STL\r
481                 if ((rule=m_attrMap.find(make_pair(name,format))) != m_attrMap.end()) {\r
482 #else\r
483                 auto_ptr_char temp1(name);\r
484                 auto_ptr_char temp2(format);\r
485                 if ((rule=m_attrMap.find(make_pair(temp1.get(),temp2.get()))) != m_attrMap.end()) {\r
486 #endif\r
487                     if (!attributes || attributes->count(rule->second.second)) {\r
488                         resolved.push_back(\r
489                             rule->second.first->decode(rule->second.second.c_str(), decattr, assertingParty.get(), relyingParty)\r
490                             );\r
491                     }\r
492                 }\r
493             }\r
494         }\r
495     }\r
496 }\r
497 \r
498 void SimpleResolverImpl::query(ResolutionContext& ctx, const NameIdentifier& nameid, const set<string>* attributes) const\r
499 {\r
500 #ifdef _DEBUG\r
501     xmltooling::NDC ndc("query");\r
502 #endif\r
503     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
504 \r
505     const EntityDescriptor* entity = ctx.getEntityDescriptor();\r
506     if (!entity) {\r
507         log.debug("no issuer information available, skipping query");\r
508         return;\r
509     }\r
510 \r
511     int version = 1;\r
512     const AttributeAuthorityDescriptor* AA = entity->getAttributeAuthorityDescriptor(samlconstants::SAML11_PROTOCOL_ENUM);\r
513     if (!AA) {\r
514         AA = entity->getAttributeAuthorityDescriptor(samlconstants::SAML10_PROTOCOL_ENUM);\r
515         version = 0;\r
516     }\r
517     if (!AA) {\r
518         log.info("no SAML 1.x AttributeAuthority role found in metadata");\r
519         return;\r
520     }\r
521 \r
522     shibsp::SecurityPolicy policy(ctx.getApplication());\r
523     MetadataCredentialCriteria mcc(*AA);\r
524     shibsp::SOAPClient soaper(policy);\r
525     const PropertySet* policySettings = ctx.getApplication().getServiceProvider().getPolicySettings(ctx.getApplication().getString("policyId").second);\r
526     pair<bool,bool> signedAssertions = policySettings->getBool("signedAssertions");\r
527 \r
528     auto_ptr_XMLCh binding(samlconstants::SAML1_BINDING_SOAP);\r
529     saml1p::Response* response=NULL;\r
530     const vector<AttributeService*>& endpoints=AA->getAttributeServices();\r
531     for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !response && ep!=endpoints.end(); ++ep) {\r
532         try {\r
533             if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
534                 continue;\r
535             auto_ptr_char loc((*ep)->getLocation());\r
536             auto_ptr_XMLCh issuer(ctx.getApplication().getString("entityID").second);\r
537             saml1::Subject* subject = saml1::SubjectBuilder::buildSubject();\r
538             subject->setNameIdentifier(nameid.cloneNameIdentifier());\r
539             saml1p::AttributeQuery* query = saml1p::AttributeQueryBuilder::buildAttributeQuery();\r
540             query->setSubject(subject);\r
541             Request* request = RequestBuilder::buildRequest();\r
542             request->setAttributeQuery(query);\r
543             query->setResource(issuer.get());\r
544             request->setMinorVersion(version);\r
545             if (attributes) {\r
546                 for (set<string>::const_iterator a = attributes->begin(); a!=attributes->end(); ++a)\r
547                     populateQuery(*query, *a);\r
548             }\r
549 \r
550             SAML1SOAPClient client(soaper);\r
551             client.sendSAML(request, mcc, loc.get());\r
552             response = client.receiveSAML();\r
553         }\r
554         catch (exception& ex) {\r
555             log.error("exception making SAML query: %s", ex.what());\r
556             soaper.reset();\r
557         }\r
558     }\r
559 \r
560     if (!response) {\r
561         log.error("unable to successfully query for attributes");\r
562         return;\r
563     }\r
564 \r
565     const vector<saml1::Assertion*>& assertions = const_cast<const saml1p::Response*>(response)->getAssertions();\r
566     if (assertions.size()>1)\r
567         log.warn("simple resolver only supports one assertion in the query response");\r
568 \r
569     auto_ptr<saml1p::Response> wrapper(response);\r
570     saml1::Assertion* newtoken = assertions.front();\r
571 \r
572     if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) {\r
573         log.error("assertion unsigned, rejecting it based on signedAssertions policy");\r
574         return;\r
575     }\r
576 \r
577     try {\r
578         policy.evaluate(*newtoken);\r
579         if (!policy.isSecure())\r
580             throw SecurityPolicyException("Security of SAML 1.x query result not established.");\r
581         saml1::AssertionValidator tokval(ctx.getApplication().getAudiences(), time(NULL));\r
582         tokval.validateAssertion(*newtoken);\r
583     }\r
584     catch (exception& ex) {\r
585         log.error("assertion failed policy/validation: %s", ex.what());\r
586     }\r
587     newtoken->detach();\r
588     wrapper.release();\r
589     ctx.getResolvedAssertions().push_back(newtoken);\r
590     resolve(ctx, newtoken, attributes);\r
591 }\r
592 \r
593 void SimpleResolverImpl::populateQuery(saml1p::AttributeQuery& query, const string& id) const\r
594 {\r
595     for (attrmap_t::const_iterator i = m_attrMap.begin(); i!=m_attrMap.end(); ++i) {\r
596         if (i->second.second == id) {\r
597             AttributeDesignator* a = AttributeDesignatorBuilder::buildAttributeDesignator();\r
598 #ifdef HAVE_GOOD_STL\r
599             a->setAttributeName(i->first.first.c_str());\r
600             a->setAttributeNamespace(i->first.second.empty() ? shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI : i->first.second.c_str());\r
601 #else\r
602             auto_ptr_XMLCh n(i->first.first.c_str());\r
603             a->setAttributeName(n.get());\r
604             if (i->first.second.empty())\r
605                 a->setAttributeNamespace(shibspconstants::SHIB1_ATTRIBUTE_NAMESPACE_URI);\r
606             else {\r
607                 auto_ptr_XMLCh ns(i->first.second.c_str());\r
608                 a->setAttributeNamespace(ns.get());\r
609             }\r
610 #endif\r
611             query.getAttributeDesignators().push_back(a);\r
612         }\r
613     }\r
614 }\r
615 \r
616 void SimpleResolverImpl::query(ResolutionContext& ctx, const NameID& nameid, const set<string>* attributes) const\r
617 {\r
618 #ifdef _DEBUG\r
619     xmltooling::NDC ndc("query");\r
620 #endif\r
621     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
622 \r
623     const EntityDescriptor* entity = ctx.getEntityDescriptor();\r
624     if (!entity) {\r
625         log.debug("no issuer information available, skipping query");\r
626         return;\r
627     }\r
628     const AttributeAuthorityDescriptor* AA = entity->getAttributeAuthorityDescriptor(samlconstants::SAML20P_NS);\r
629     if (!AA) {\r
630         log.info("no SAML 2 AttributeAuthority role found in metadata");\r
631         return;\r
632     }\r
633 \r
634     shibsp::SecurityPolicy policy(ctx.getApplication());\r
635     MetadataCredentialCriteria mcc(*AA);\r
636     shibsp::SOAPClient soaper(policy);\r
637     const PropertySet* policySettings = ctx.getApplication().getServiceProvider().getPolicySettings(ctx.getApplication().getString("policyId").second);\r
638     pair<bool,bool> signedAssertions = policySettings->getBool("signedAssertions");\r
639 \r
640     auto_ptr_XMLCh binding(samlconstants::SAML20_BINDING_SOAP);\r
641     saml2p::StatusResponseType* srt=NULL;\r
642     const vector<AttributeService*>& endpoints=AA->getAttributeServices();\r
643     for (vector<AttributeService*>::const_iterator ep=endpoints.begin(); !srt && ep!=endpoints.end(); ++ep) {\r
644         try {\r
645             if (!XMLString::equals((*ep)->getBinding(),binding.get()))\r
646                 continue;\r
647             auto_ptr_char loc((*ep)->getLocation());\r
648             auto_ptr_XMLCh issuer(ctx.getApplication().getString("entityID").second);\r
649             saml2::Subject* subject = saml2::SubjectBuilder::buildSubject();\r
650             subject->setNameID(nameid.cloneNameID());\r
651             saml2p::AttributeQuery* query = saml2p::AttributeQueryBuilder::buildAttributeQuery();\r
652             query->setSubject(subject);\r
653             Issuer* iss = IssuerBuilder::buildIssuer();\r
654             query->setIssuer(iss);\r
655             iss->setName(issuer.get());\r
656             if (attributes) {\r
657                 for (set<string>::const_iterator a = attributes->begin(); a!=attributes->end(); ++a)\r
658                     populateQuery(*query, *a);\r
659             }\r
660 \r
661             SAML2SOAPClient client(soaper);\r
662             client.sendSAML(query, mcc, loc.get());\r
663             srt = client.receiveSAML();\r
664         }\r
665         catch (exception& ex) {\r
666             log.error("exception making SAML query: %s", ex.what());\r
667             soaper.reset();\r
668         }\r
669     }\r
670 \r
671     if (!srt) {\r
672         log.error("unable to successfully query for attributes");\r
673         return;\r
674     }\r
675     saml2p::Response* response = dynamic_cast<saml2p::Response*>(srt);\r
676     if (!response) {\r
677         delete srt;\r
678         log.error("message was not a samlp:Response");\r
679         return;\r
680     }\r
681 \r
682     const vector<saml2::Assertion*>& assertions = const_cast<const saml2p::Response*>(response)->getAssertions();\r
683     if (assertions.size()>1)\r
684         log.warn("simple resolver only supports one assertion in the query response");\r
685 \r
686     auto_ptr<saml2p::Response> wrapper(response);\r
687     saml2::Assertion* newtoken = assertions.front();\r
688 \r
689     if (!newtoken->getSignature() && signedAssertions.first && signedAssertions.second) {\r
690         log.error("assertion unsigned, rejecting it based on signedAssertions policy");\r
691         return;\r
692     }\r
693 \r
694     try {\r
695         policy.evaluate(*newtoken);\r
696         if (!policy.isSecure())\r
697             throw SecurityPolicyException("Security of SAML 2.0 query result not established.");\r
698         saml2::AssertionValidator tokval(ctx.getApplication().getAudiences(), time(NULL));\r
699         tokval.validateAssertion(*newtoken);\r
700     }\r
701     catch (exception& ex) {\r
702         log.error("assertion failed policy/validation: %s", ex.what());\r
703     }\r
704     newtoken->detach();\r
705     wrapper.release();\r
706     ctx.getResolvedAssertions().push_back(newtoken);\r
707     resolve(ctx, newtoken, attributes);\r
708 }\r
709 \r
710 void SimpleResolverImpl::populateQuery(saml2p::AttributeQuery& query, const string& id) const\r
711 {\r
712     for (attrmap_t::const_iterator i = m_attrMap.begin(); i!=m_attrMap.end(); ++i) {\r
713         if (i->second.second == id) {\r
714             saml2::Attribute* a = saml2::AttributeBuilder::buildAttribute();\r
715 #ifdef HAVE_GOOD_STL\r
716             a->setName(i->first.first.c_str());\r
717             a->setNameFormat(i->first.second.empty() ? saml2::Attribute::URI_REFERENCE : i->first.second.c_str());\r
718 #else\r
719             auto_ptr_XMLCh n(i->first.first.c_str());\r
720             a->setName(n.get());\r
721             if (i->first.second.empty())\r
722                 a->setNameFormat(saml2::Attribute::URI_REFERENCE);\r
723             else {\r
724                 auto_ptr_XMLCh ns(i->first.second.c_str());\r
725                 a->setNameFormat(ns.get());\r
726             }\r
727 #endif\r
728             query.getAttributes().push_back(a);\r
729         }\r
730     }\r
731 }\r
732 \r
733 void SimpleResolver::resolveAttributes(ResolutionContext& ctx, const set<string>* attributes) const\r
734 {\r
735 #ifdef _DEBUG\r
736     xmltooling::NDC ndc("resolveAttributes");\r
737 #endif\r
738     Category& log=Category::getInstance(SHIBSP_LOGCAT".AttributeResolver");\r
739     \r
740     log.debug("examining tokens to resolve");\r
741 \r
742     bool query = m_impl->m_allowQuery;\r
743     const saml1::Assertion* token1;\r
744     const saml2::Assertion* token2;\r
745     if (ctx.getTokens()) {\r
746         for (vector<const opensaml::Assertion*>::const_iterator t = ctx.getTokens()->begin(); t!=ctx.getTokens()->end(); ++t) {\r
747             token2 = dynamic_cast<const saml2::Assertion*>(*t);\r
748             if (token2 && !token2->getAttributeStatements().empty()) {\r
749                 log.debug("resolving SAML 2 token with an AttributeStatement");\r
750                 m_impl->resolve(ctx, token2, attributes);\r
751                 query = false;\r
752             }\r
753             else {\r
754                 token1 = dynamic_cast<const saml1::Assertion*>(*t);\r
755                 if (token1 && !token1->getAttributeStatements().empty()) {\r
756                     log.debug("resolving SAML 1 token with an AttributeStatement");\r
757                     m_impl->resolve(ctx, token1, attributes);\r
758                     query = false;\r
759                 }\r
760             }\r
761         }\r
762     }\r
763 \r
764     if (query) {\r
765         if (token1 && !token1->getAuthenticationStatements().empty()) {\r
766             const AuthenticationStatement* statement = token1->getAuthenticationStatements().front();\r
767             if (statement && statement->getSubject() && statement->getSubject()->getNameIdentifier()) {\r
768                 log.debug("attempting SAML 1.x attribute query");\r
769                 return m_impl->query(ctx, *(statement->getSubject()->getNameIdentifier()), attributes);\r
770             }\r
771         }\r
772         else if (token2 && ctx.getNameID()) {\r
773             log.debug("attempting SAML 2.0 attribute query");\r
774             return m_impl->query(ctx, *ctx.getNameID(), attributes);\r
775         }\r
776         log.warn("can't attempt attribute query, no identifier in assertion subject");\r
777     }\r
778 }\r
779 \r
780 pair<bool,DOMElement*> SimpleResolver::load()\r
781 {\r
782     // Load from source using base class.\r
783     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();\r
784     \r
785     // If we own it, wrap it.\r
786     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);\r
787 \r
788     SimpleResolverImpl* impl = new SimpleResolverImpl(raw.second);\r
789     \r
790     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.\r
791     impl->setDocument(docjanitor.release());\r
792 \r
793     delete m_impl;\r
794     m_impl = impl;\r
795 \r
796     return make_pair(false,(DOMElement*)NULL);\r
797 }\r