Change license header.
[shibboleth/cpp-sp.git] / shibsp / attribute / ScopedAttributeDecoder.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  * ScopedAttributeDecoder.cpp
23  *
24  * Decodes SAML into ScopedAttributes.
25  */
26
27 #include "internal.h"
28 #include "attribute/AttributeDecoder.h"
29 #include "attribute/ScopedAttribute.h"
30
31 #include <saml/saml1/core/Assertions.h>
32 #include <saml/saml2/core/Assertions.h>
33
34 using namespace shibsp;
35 using namespace opensaml::saml1;
36 using namespace opensaml::saml2;
37 using namespace xmltooling;
38 using namespace std;
39
40 namespace shibsp {
41     static const XMLCh Scope[] =            UNICODE_LITERAL_5(S,c,o,p,e);
42     static const XMLCh scopeDelimiter[] =   UNICODE_LITERAL_14(s,c,o,p,e,D,e,l,i,m,i,t,e,r);
43
44     class SHIBSP_DLLLOCAL ScopedAttributeDecoder : virtual public AttributeDecoder
45     {
46     public:
47         ScopedAttributeDecoder(const DOMElement* e) : AttributeDecoder(e), m_delimiter('@') {
48             if (e && e->hasAttributeNS(nullptr,scopeDelimiter)) {
49                 auto_ptr_char d(e->getAttributeNS(nullptr,scopeDelimiter));
50                 m_delimiter = *(d.get());
51             }
52         }
53         ~ScopedAttributeDecoder() {}
54
55         shibsp::Attribute* decode(
56             const vector<string>& ids, const XMLObject* xmlObject, const char* assertingParty=nullptr, const char* relyingParty=nullptr
57             ) const;
58
59     private:
60         char m_delimiter;
61     };
62
63     AttributeDecoder* SHIBSP_DLLLOCAL ScopedAttributeDecoderFactory(const DOMElement* const & e)
64     {
65         return new ScopedAttributeDecoder(e);
66     }
67 };
68
69 shibsp::Attribute* ScopedAttributeDecoder::decode(
70     const vector<string>& ids, const XMLObject* xmlObject, const char* assertingParty, const char* relyingParty
71     ) const
72 {
73     char* val;
74     char* scope;
75     const XMLCh* xmlscope;
76     xmltooling::QName scopeqname(nullptr,Scope);
77     auto_ptr<ScopedAttribute> scoped(new ScopedAttribute(ids, m_delimiter));
78     vector< pair<string,string> >& dest = scoped->getValues();
79     vector<XMLObject*>::const_iterator v,stop;
80
81     Category& log = Category::getInstance(SHIBSP_LOGCAT".AttributeDecoder.Scoped");
82
83     if (xmlObject && XMLString::equals(opensaml::saml1::Attribute::LOCAL_NAME,xmlObject->getElementQName().getLocalPart())) {
84         const opensaml::saml2::Attribute* saml2attr = dynamic_cast<const opensaml::saml2::Attribute*>(xmlObject);
85         if (saml2attr) {
86             const vector<XMLObject*>& values = saml2attr->getAttributeValues();
87             v = values.begin();
88             stop = values.end();
89             if (log.isDebugEnabled()) {
90                 auto_ptr_char n(saml2attr->getName());
91                 log.debug(
92                     "decoding ScopedAttribute (%s) from SAML 2 Attribute (%s) with %lu value(s)",
93                     ids.front().c_str(), n.get() ? n.get() : "unnamed", values.size()
94                     );
95             }
96         }
97         else {
98             const opensaml::saml1::Attribute* saml1attr = dynamic_cast<const opensaml::saml1::Attribute*>(xmlObject);
99             if (saml1attr) {
100                 const vector<XMLObject*>& values = saml1attr->getAttributeValues();
101                 v = values.begin();
102                 stop = values.end();
103                 if (log.isDebugEnabled()) {
104                     auto_ptr_char n(saml1attr->getAttributeName());
105                     log.debug(
106                         "decoding ScopedAttribute (%s) from SAML 1 Attribute (%s) with %lu value(s)",
107                         ids.front().c_str(), n.get() ? n.get() : "unnamed", values.size()
108                         );
109                 }
110             }
111             else {
112                 log.warn("XMLObject type not recognized by ScopedAttributeDecoder, no values returned");
113                 return nullptr;
114             }
115         }
116
117         for (; v!=stop; ++v) {
118             if (!(*v)->hasChildren()) {
119                 val = toUTF8((*v)->getTextContent());
120                 if (val && *val) {
121                     const AttributeExtensibleXMLObject* aexo=dynamic_cast<const AttributeExtensibleXMLObject*>(*v);
122                     xmlscope = aexo ? aexo->getAttribute(scopeqname) : nullptr;
123                     if (xmlscope && *xmlscope) {
124                         scope = toUTF8(xmlscope);
125                         dest.push_back(pair<string,string>(val,scope));
126                         delete[] scope;
127                     }
128                     else {
129                         scope = strchr(val, m_delimiter);
130                         if (scope) {
131                             *scope++ = 0;
132                             if (*scope)
133                                 dest.push_back(pair<string,string>(val,scope));
134                             else
135                                 log.warn("ignoring unscoped AttributeValue");
136                         }
137                         else {
138                             log.warn("ignoring unscoped AttributeValue");
139                         }
140                     }
141                 }
142                 else {
143                     log.warn("skipping empty AttributeValue");
144                 }
145                 delete[] val;
146             }
147             else {
148                 log.warn("skipping complex AttributeValue");
149             }
150         }
151
152         return dest.empty() ? nullptr : _decode(scoped.release());
153     }
154
155     const NameID* saml2name = dynamic_cast<const NameID*>(xmlObject);
156     if (saml2name) {
157         if (log.isDebugEnabled()) {
158             auto_ptr_char f(saml2name->getFormat());
159             log.debug("decoding ScopedAttribute (%s) from SAML 2 NameID with Format (%s)", ids.front().c_str(), f.get() ? f.get() : "unspecified");
160         }
161         val = toUTF8(saml2name->getName());
162     }
163     else {
164         const NameIdentifier* saml1name = dynamic_cast<const NameIdentifier*>(xmlObject);
165         if (saml1name) {
166             if (log.isDebugEnabled()) {
167                 auto_ptr_char f(saml1name->getFormat());
168                 log.debug(
169                     "decoding ScopedAttribute (%s) from SAML 1 NameIdentifier with Format (%s)",
170                     ids.front().c_str(), f.get() ? f.get() : "unspecified"
171                     );
172             }
173             val = toUTF8(saml1name->getName());
174         }
175         else {
176             log.warn("XMLObject type not recognized by ScopedAttributeDecoder, no values returned");
177             return nullptr;
178         }
179     }
180
181     if (val && *val && *val!=m_delimiter) {
182         scope = strchr(val, m_delimiter);
183         if (scope) {
184             *scope++ = 0;
185             if (*scope)
186                 dest.push_back(pair<string,string>(val,scope));
187             else
188                 log.warn("ignoring NameID with no scope");
189         }
190         else {
191             log.warn("ignoring NameID with no scope delimiter (%c)", m_delimiter);
192         }
193     }
194     else {
195         log.warn("ignoring empty NameID");
196     }
197     delete[] val;
198     return dest.empty() ? nullptr : _decode(scoped.release());
199 }