Name and description change to installers
[shibboleth/sp.git] / plugins / TransformAttributeResolver.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  * TransformAttributeResolver.cpp
23  * 
24  * Attribute Resolver plugin for transforming input values.
25  */
26
27 #include "internal.h"
28
29 #include <algorithm>
30 #include <boost/shared_ptr.hpp>
31 #include <boost/tuple/tuple.hpp>
32 #include <shibsp/exceptions.h>
33 #include <shibsp/SessionCache.h>
34 #include <shibsp/attribute/SimpleAttribute.h>
35 #include <shibsp/attribute/resolver/AttributeResolver.h>
36 #include <shibsp/attribute/resolver/ResolutionContext.h>
37 #include <xmltooling/XMLToolingConfig.h>
38 #include <xmltooling/util/XMLHelper.h>
39 #include <xercesc/util/XMLUniDefs.hpp>
40 #include <xercesc/util/regx/RegularExpression.hpp>
41
42 using namespace shibsp;
43 using namespace xmltooling;
44 using namespace xercesc;
45 using namespace boost;
46 using namespace std;
47
48 namespace shibsp {
49
50     class SHIBSP_DLLLOCAL TransformContext : public ResolutionContext
51     {
52     public:
53         TransformContext(const vector<Attribute*>* attributes) : m_inputAttributes(attributes) {
54         }
55
56         ~TransformContext() {
57             for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<Attribute>());
58         }
59
60         const vector<Attribute*>* getInputAttributes() const {
61             return m_inputAttributes;
62         }
63         vector<Attribute*>& getResolvedAttributes() {
64             return m_attributes;
65         }
66         vector<opensaml::Assertion*>& getResolvedAssertions() {
67             return m_assertions;
68         }
69
70     private:
71         const vector<Attribute*>* m_inputAttributes;
72         vector<Attribute*> m_attributes;
73         static vector<opensaml::Assertion*> m_assertions;   // empty dummy
74     };
75
76
77     class SHIBSP_DLLLOCAL TransformAttributeResolver : public AttributeResolver
78     {
79     public:
80         TransformAttributeResolver(const DOMElement* e);
81         virtual ~TransformAttributeResolver() {}
82
83         Lockable* lock() {
84             return this;
85         }
86         void unlock() {
87         }
88
89         ResolutionContext* createResolutionContext(
90             const Application& application,
91             const opensaml::saml2md::EntityDescriptor* issuer,
92             const XMLCh* protocol,
93             const opensaml::saml2::NameID* nameid=nullptr,
94             const XMLCh* authncontext_class=nullptr,
95             const XMLCh* authncontext_decl=nullptr,
96             const vector<const opensaml::Assertion*>* tokens=nullptr,
97             const vector<Attribute*>* attributes=nullptr
98             ) const {
99             // Make sure new method gets run.
100             return createResolutionContext(application, nullptr, issuer, protocol, nameid, authncontext_class, authncontext_decl, tokens, attributes);
101         }
102
103         ResolutionContext* createResolutionContext(
104             const Application& application,
105             const GenericRequest* request,
106             const opensaml::saml2md::EntityDescriptor* issuer,
107             const XMLCh* protocol,
108             const opensaml::saml2::NameID* nameid=nullptr,
109             const XMLCh* authncontext_class=nullptr,
110             const XMLCh* authncontext_decl=nullptr,
111             const vector<const opensaml::Assertion*>* tokens=nullptr,
112             const vector<Attribute*>* attributes=nullptr
113             ) const {
114             return new TransformContext(attributes);
115         }
116
117         ResolutionContext* createResolutionContext(const Application& application, const Session& session) const {
118             return new TransformContext(&session.getAttributes());
119         }
120
121         void resolveAttributes(ResolutionContext& ctx) const;
122
123         void getAttributeIds(vector<string>& attributes) const {
124             for (vector<regex_t>::const_iterator r = m_regex.begin(); r != m_regex.end(); ++r) {
125                 if (!r->get<0>().empty())
126                     attributes.push_back(r->get<0>());
127             }
128         }
129
130     private:
131         Category& m_log;
132         string m_source;
133         // dest id, regex to apply, replacement string
134         typedef tuple<string,boost::shared_ptr<RegularExpression>,const XMLCh*> regex_t;
135         vector<regex_t> m_regex;
136     };
137
138     static const XMLCh dest[] =             UNICODE_LITERAL_4(d,e,s,t);
139     static const XMLCh match[] =            UNICODE_LITERAL_5(m,a,t,c,h);
140     static const XMLCh caseSensitive[] =    UNICODE_LITERAL_13(c,a,s,e,S,e,n,s,i,t,i,v,e);
141     static const XMLCh source[] =           UNICODE_LITERAL_6(s,o,u,r,c,e);
142     static const XMLCh Regex[] =            UNICODE_LITERAL_5(R,e,g,e,x);
143
144     AttributeResolver* SHIBSP_DLLLOCAL TransformAttributeResolverFactory(const DOMElement* const & e)
145     {
146         return new TransformAttributeResolver(e);
147     }
148
149 };
150
151 vector<opensaml::Assertion*> TransformContext::m_assertions;
152
153 TransformAttributeResolver::TransformAttributeResolver(const DOMElement* e)
154     : m_log(Category::getInstance(SHIBSP_LOGCAT".AttributeResolver.Transform")),
155         m_source(XMLHelper::getAttrString(e, nullptr, source))
156 {
157     if (m_source.empty())
158         throw ConfigurationException("Transform AttributeResolver requires source attribute.");
159
160     e = XMLHelper::getFirstChildElement(e, Regex);
161     while (e) {
162         if (e->hasChildNodes() && e->hasAttributeNS(nullptr, match)) {
163             const XMLCh* repl(e->getTextContent());
164             string destId(XMLHelper::getAttrString(e, nullptr, dest));
165             bool caseflag(XMLHelper::getAttrBool(e, true, caseSensitive));
166             if (repl && *repl) {
167                 try {
168                     static XMLCh options[] = { chLatin_i, chNull };
169                     boost::shared_ptr<RegularExpression> re(new RegularExpression(e->getAttributeNS(nullptr, match), (caseflag ? &chNull : options)));
170                     m_regex.push_back(make_tuple(destId, re, repl));
171                 }
172                 catch (XMLException& ex) {
173                     auto_ptr_char msg(ex.getMessage());
174                     auto_ptr_char m(e->getAttributeNS(nullptr, match));
175                     m_log.error("exception parsing regular expression (%s): %s", m.get(), msg.get());
176                 }
177             }
178         }
179         e = XMLHelper::getNextSiblingElement(e, Regex);
180     }
181
182     if (m_regex.empty())
183         throw ConfigurationException("Transform AttributeResolver requires at least one Regex element.");
184 }
185
186
187 void TransformAttributeResolver::resolveAttributes(ResolutionContext& ctx) const
188 {
189     TransformContext& tctx = dynamic_cast<TransformContext&>(ctx);
190     if (!tctx.getInputAttributes())
191         return;
192
193     for (vector<Attribute*>::const_iterator a = tctx.getInputAttributes()->begin(); a != tctx.getInputAttributes()->end(); ++a) {
194         if (m_source != (*a)->getId() || (*a)->valueCount() == 0) {
195             continue;
196         }
197
198         // We run each transform expression against each value of the input. Each transform either generates
199         // a new attribute from its dest property, or overwrites a SimpleAttribute's values in place.
200
201         for (vector<regex_t>::const_iterator r = m_regex.begin(); r != m_regex.end(); ++r) {
202             SimpleAttribute* dest = nullptr;
203             auto_ptr<SimpleAttribute> destwrapper;
204
205             // First tuple element is the destination attribute ID, if any.
206             if (r->get<0>().empty()) {
207                 // Can we transform in-place?
208                 dest = dynamic_cast<SimpleAttribute*>(*a);
209                 if (!dest) {
210                     m_log.warn("can't transform non-simple attribute (%s) 'in place'", m_source.c_str());
211                     continue;
212                 }
213             }
214             else {
215                 // Create a destination attribute.
216                 vector<string> ids(1, r->get<0>());
217                 destwrapper.reset(new SimpleAttribute(ids));
218             }
219
220             if (dest)
221                 m_log.debug("applying in-place transform to source attribute (%s)", m_source.c_str());
222             else
223                 m_log.debug("applying transform from source attribute (%s) to dest attribute (%s)", m_source.c_str(), r->get<0>().c_str());
224
225             for (size_t i = 0; i < (*a)->valueCount(); ++i) {
226                 try {
227                     auto_arrayptr<XMLCh> srcval(fromUTF8((*a)->getSerializedValues()[i].c_str()));
228                     XMLCh* destval = r->get<1>()->replace(srcval.get(), r->get<2>());
229                     if (destval) {
230                         auto_arrayptr<char> narrow(toUTF8(destval));
231                         XMLString::release(&destval);
232                         if (dest) {
233                             // Modify in place.
234                             dest->getValues()[i] = narrow.get();
235                         }
236                         else {
237                             // Add to new object.
238                             destwrapper->getValues().push_back(narrow.get());
239                         }
240                     }
241                 }
242                 catch (XMLException& ex) {
243                     auto_ptr_char msg(ex.getMessage());
244                     m_log.error("caught error applying regular expression: %s", msg.get());
245                 }
246             }
247
248             // Save off new object.
249             if (destwrapper.get()) {
250                 ctx.getResolvedAttributes().push_back(destwrapper.get());
251                 destwrapper.release();
252             }
253         }
254     }
255 }