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