1dd3c18a3c315f0635df22eb3d8aafe978a42811
[shibboleth/cpp-sp.git] / shibsp / attribute / DOMAttributeDecoder.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /**
22  * DOMAttributeDecoder.cpp
23  *
24  * Decodes a DOM into an ExtensibleAttribute.
25  */
26
27 #include "internal.h"
28 #include "attribute/AttributeDecoder.h"
29 #include "attribute/ExtensibleAttribute.h"
30
31 #include <saml/saml1/core/Assertions.h>
32 #include <saml/saml2/core/Assertions.h>
33 #include <xmltooling/util/XMLHelper.h>
34
35 using namespace shibsp;
36 using namespace opensaml;
37 using namespace xmltooling;
38 using namespace std;
39
40 namespace shibsp {
41     class SHIBSP_DLLLOCAL DOMAttributeDecoder : virtual public AttributeDecoder
42     {
43     public:
44         DOMAttributeDecoder(const DOMElement* e);
45         ~DOMAttributeDecoder() {}
46
47         // deprecated method
48         Attribute* decode(
49             const vector<string>& ids, const XMLObject* xmlObject, const char* assertingParty=nullptr, const char* relyingParty=nullptr
50             ) const {
51             return decode(nullptr, ids, xmlObject, assertingParty, relyingParty);
52         }
53
54         Attribute* decode(
55             const GenericRequest*, const vector<string>&, const XMLObject*, const char* assertingParty=nullptr, const char* relyingParty=nullptr
56             ) const;
57
58     private:
59         DDF convert(DOMElement* e, bool nameit=true) const;
60         string m_formatter;
61         map<pair<xstring,xstring>,string> m_tagMap;
62     };
63
64     AttributeDecoder* SHIBSP_DLLLOCAL DOMAttributeDecoderFactory(const DOMElement* const & e)
65     {
66         return new DOMAttributeDecoder(e);
67     }
68
69     static const XMLCh Mapping[] =  UNICODE_LITERAL_7(M,a,p,p,i,n,g);
70     static const XMLCh _from[] =    UNICODE_LITERAL_4(f,r,o,m);
71     static const XMLCh _to[] =      UNICODE_LITERAL_2(t,o);
72     static const XMLCh formatter[] =UNICODE_LITERAL_9(f,o,r,m,a,t,t,e,r);
73 };
74
75 DOMAttributeDecoder::DOMAttributeDecoder(const DOMElement* e)
76     : AttributeDecoder(e), m_formatter(XMLHelper::getAttrString(e, nullptr, formatter))
77 {
78     Category& log = Category::getInstance(SHIBSP_LOGCAT".AttributeDecoder.DOM");
79
80     e = XMLHelper::getFirstChildElement(e, Mapping);
81     while (e) {
82         if (e->hasAttributeNS(nullptr, _from) && e->hasAttributeNS(nullptr, _to)) {
83             auto_ptr<xmltooling::QName> f(XMLHelper::getNodeValueAsQName(e->getAttributeNodeNS(nullptr, _from)));
84             auto_ptr_char t(e->getAttributeNS(nullptr, _to));
85             if (f.get() && t.get() && *t.get()) {
86                 if (log.isDebugEnabled())
87                     log.debug("mapping (%s) to (%s)", f->toString().c_str(), t.get());
88                 m_tagMap.insert(
89                     pair< const pair<xstring,xstring>,string>(
90                         pair<xstring,xstring>(f->getLocalPart(), f->hasNamespaceURI() ? f->getNamespaceURI() : &chNull),
91                         t.get()
92                         )
93                     );
94             }
95         }
96         e = XMLHelper::getNextSiblingElement(e, Mapping);
97     }
98 }
99
100 Attribute* DOMAttributeDecoder::decode(
101     const GenericRequest* request, const vector<string>& ids, const XMLObject* xmlObject, const char* assertingParty, const char* relyingParty
102     ) const
103 {
104     Category& log = Category::getInstance(SHIBSP_LOGCAT".AttributeDecoder.DOM");
105
106     if (!xmlObject)
107         return nullptr;
108
109     auto_ptr<ExtensibleAttribute> attr(new ExtensibleAttribute(ids, m_formatter.c_str()));
110     DDF dest = attr->getValues();
111     vector<XMLObject*> genericObjectWrapper;    // used to support stand-alone object decoding
112     pair<vector<XMLObject*>::const_iterator,vector<XMLObject*>::const_iterator> valrange;
113
114     const saml2::Attribute* saml2attr = dynamic_cast<const saml2::Attribute*>(xmlObject);
115     if (saml2attr) {
116         const vector<XMLObject*>& values = saml2attr->getAttributeValues();
117         valrange = valueRange(request, values);
118         if (log.isDebugEnabled()) {
119             auto_ptr_char n(saml2attr->getName());
120             log.debug(
121                 "decoding ExtensibleAttribute (%s) from SAML 2 Attribute (%s) with %lu value(s)",
122                 ids.front().c_str(), n.get() ? n.get() : "unnamed", values.size()
123                 );
124         }
125     }
126     else {
127         const saml1::Attribute* saml1attr = dynamic_cast<const saml1::Attribute*>(xmlObject);
128         if (saml1attr) {
129             const vector<XMLObject*>& values = saml1attr->getAttributeValues();
130             valrange = valueRange(request, values);
131             if (log.isDebugEnabled()) {
132                 auto_ptr_char n(saml1attr->getAttributeName());
133                 log.debug(
134                     "decoding ExtensibleAttribute (%s) from SAML 1 Attribute (%s) with %lu value(s)",
135                     ids.front().c_str(), n.get() ? n.get() : "unnamed", values.size()
136                     );
137             }
138         }
139         else {
140             log.debug("decoding arbitrary XMLObject type (%s)", xmlObject->getElementQName().toString().c_str());
141             genericObjectWrapper.push_back(const_cast<XMLObject*>(xmlObject));
142             valrange.first = genericObjectWrapper.begin();
143             valrange.second = genericObjectWrapper.end();
144         }
145     }
146
147     for (; valrange.first != valrange.second; ++valrange.first) {
148         DOMElement* e = (*valrange.first)->getDOM();
149         if (e) {
150             DDF converted = convert(e, false);
151             if (!converted.isnull())
152                 dest.add(converted);
153         }
154         else
155             log.warn("skipping XMLObject without a backing DOM");
156     }
157
158     return dest.integer() ? _decode(attr.release()) : nullptr;
159 }
160
161 DDF DOMAttributeDecoder::convert(DOMElement* e, bool nameit) const
162 {
163     const XMLCh* nsURI;
164     const XMLCh* local;
165     map<pair<xstring,xstring>,string>::const_iterator mapping;
166     DDF obj = DDF(nullptr).structure();
167
168     if (nameit) {
169         // Name this structure.
170         nsURI = e->getNamespaceURI();
171         local = e->getLocalName();
172         mapping = m_tagMap.find(pair<xstring,xstring>(local,nsURI));
173         if (mapping == m_tagMap.end()) {
174             auto_ptr_char temp(local);
175             obj.name(temp.get());
176         }
177         else {
178             obj.name(mapping->second.c_str());
179         }
180     }
181
182     // Process non-xmlns attributes.
183     DOMNamedNodeMap* attrs = e->getAttributes();
184     for (XMLSize_t a = attrs->getLength(); a > 0; --a) {
185         DOMNode* attr = attrs->item(a-1);
186         nsURI = attr->getNamespaceURI();
187         if (XMLString::equals(nsURI, xmlconstants::XMLNS_NS))
188             continue;
189         local = attr->getLocalName();
190         mapping = m_tagMap.find(pair<xstring,xstring>(local, nsURI ? nsURI : &chNull));
191         if (mapping == m_tagMap.end()) {
192             auto_ptr_char temp(local);
193             obj.addmember(temp.get()).string(toUTF8(attr->getNodeValue(), true), false);
194         }
195         else {
196             obj.addmember(mapping->second.c_str()).string(toUTF8(attr->getNodeValue(), true), false);
197         }
198     }
199
200     DOMElement* child = XMLHelper::getFirstChildElement(e);
201     if (!child && e->hasChildNodes() && e->getFirstChild()->getNodeType() == DOMNode::TEXT_NODE) {
202         // Attach a _text member if a text node is present.
203         obj.addmember("_string").string(toUTF8(e->getFirstChild()->getTextContent(), true), false);
204     }
205     else {
206         while (child) {
207             // Convert the child element.
208             DDF converted = convert(child);
209             if (!converted.isnull()) {
210                 // Now identify it and attach it.
211                 if (obj[converted.name()].isnull()) {
212                     // We're a new child, so just attach as a structure member.
213                     obj.add(converted);
214                 }
215                 else if (obj[converted.name()].islist()) {
216                     // We're already a repeating child, so add it to the list.
217                     obj[converted.name()].add(converted);
218                 }
219                 else if (obj[converted.name()].isstruct()) {
220                     // This is the complex case where we see a child for the second
221                     // time and have to convert a structure member into a named list.
222                     DDF newlist = DDF(converted.name()).list();
223                     newlist.add(obj[converted.name()].remove());
224                     newlist.add(converted);
225                     obj.add(newlist);
226                 }
227             }
228             child = XMLHelper::getNextSiblingElement(child);
229         }
230     }
231
232     // If we're empty, just delete.
233     if (obj.integer() == 0)
234         obj.destroy();
235     return obj;
236 }