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.
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
22 * TransformAttributeResolver.cpp
24 * Attribute Resolver plugin for transforming input values.
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>
42 using namespace shibsp;
43 using namespace xmltooling;
44 using namespace xercesc;
45 using namespace boost;
50 class SHIBSP_DLLLOCAL TransformContext : public ResolutionContext
53 TransformContext(const Session& session) : m_inputAttributes(&session.getAttributes()) {
56 TransformContext(const vector<shibsp::Attribute*>* attributes) : m_inputAttributes(attributes) {
60 for_each(m_attributes.begin(), m_attributes.end(), xmltooling::cleanup<shibsp::Attribute>());
63 const vector<shibsp::Attribute*>* getInputAttributes() const {
64 return m_inputAttributes;
66 vector<shibsp::Attribute*>& getResolvedAttributes() {
69 vector<opensaml::Assertion*>& getResolvedAssertions() {
74 const vector<shibsp::Attribute*>* m_inputAttributes;
75 vector<shibsp::Attribute*> m_attributes;
76 static vector<opensaml::Assertion*> m_assertions; // empty dummy
80 class SHIBSP_DLLLOCAL TransformAttributeResolver : public AttributeResolver
83 TransformAttributeResolver(const DOMElement* e);
84 virtual ~TransformAttributeResolver() {}
92 ResolutionContext* createResolutionContext(
93 const Application& application,
94 const opensaml::saml2md::EntityDescriptor* issuer,
95 const XMLCh* protocol,
96 const opensaml::saml2::NameID* nameid=nullptr,
97 const XMLCh* authncontext_class=nullptr,
98 const XMLCh* authncontext_decl=nullptr,
99 const vector<const opensaml::Assertion*>* tokens=nullptr,
100 const vector<shibsp::Attribute*>* attributes=nullptr
102 // Make sure new method gets run.
103 return createResolutionContext(application, nullptr, issuer, protocol, nameid, authncontext_class, authncontext_decl, tokens, attributes);
106 ResolutionContext* createResolutionContext(
107 const Application& application,
108 const GenericRequest* request,
109 const opensaml::saml2md::EntityDescriptor* issuer,
110 const XMLCh* protocol,
111 const opensaml::saml2::NameID* nameid=nullptr,
112 const XMLCh* authncontext_class=nullptr,
113 const XMLCh* authncontext_decl=nullptr,
114 const vector<const opensaml::Assertion*>* tokens=nullptr,
115 const vector<shibsp::Attribute*>* attributes=nullptr
117 return new TransformContext(attributes);
120 ResolutionContext* createResolutionContext(const Application& application, const Session& session) const {
121 return new TransformContext(session);
124 void resolveAttributes(ResolutionContext& ctx) const;
126 void getAttributeIds(vector<string>& attributes) const {
127 for (vector<regex_t>::const_iterator r = m_regex.begin(); r != m_regex.end(); ++r) {
128 if (!r->get<0>().empty())
129 attributes.push_back(r->get<0>());
136 // dest id, regex to apply, replacement string
137 typedef tuple<string,boost::shared_ptr<RegularExpression>,const XMLCh*> regex_t;
138 vector<regex_t> m_regex;
141 static const XMLCh dest[] = UNICODE_LITERAL_4(d,e,s,t);
142 static const XMLCh match[] = UNICODE_LITERAL_5(m,a,t,c,h);
143 static const XMLCh caseSensitive[] = UNICODE_LITERAL_13(c,a,s,e,S,e,n,s,i,t,i,v,e);
144 static const XMLCh source[] = UNICODE_LITERAL_6(s,o,u,r,c,e);
145 static const XMLCh Regex[] = UNICODE_LITERAL_5(R,e,g,e,x);
147 AttributeResolver* SHIBSP_DLLLOCAL TransformAttributeResolverFactory(const DOMElement* const & e)
149 return new TransformAttributeResolver(e);
154 vector<opensaml::Assertion*> TransformContext::m_assertions;
156 TransformAttributeResolver::TransformAttributeResolver(const DOMElement* e)
157 : m_log(Category::getInstance(SHIBSP_LOGCAT".AttributeResolver.Transform")),
158 m_source(XMLHelper::getAttrString(e, nullptr, source))
160 if (m_source.empty())
161 throw ConfigurationException("Transform AttributeResolver requires source attribute.");
163 e = XMLHelper::getFirstChildElement(e, Regex);
165 if (e->hasChildNodes() && e->hasAttributeNS(nullptr, match)) {
166 const XMLCh* repl(e->getTextContent());
167 string destId(XMLHelper::getAttrString(e, nullptr, dest));
168 bool caseflag(XMLHelper::getAttrBool(e, true, caseSensitive));
171 static XMLCh options[] = { chLatin_i, chNull };
172 boost::shared_ptr<RegularExpression> re(new RegularExpression(e->getAttributeNS(nullptr, match), (caseflag ? &chNull : options)));
173 m_regex.push_back(make_tuple(destId, re, repl));
175 catch (XMLException& ex) {
176 auto_ptr_char msg(ex.getMessage());
177 auto_ptr_char m(e->getAttributeNS(nullptr, match));
178 m_log.error("exception parsing regular expression (%s): %s", m.get(), msg.get());
182 e = XMLHelper::getNextSiblingElement(e, Regex);
186 throw ConfigurationException("Transform AttributeResolver requires at least one Regex element.");
190 void TransformAttributeResolver::resolveAttributes(ResolutionContext& ctx) const
192 TransformContext& tctx = dynamic_cast<TransformContext&>(ctx);
193 if (!tctx.getInputAttributes())
196 for (vector<Attribute*>::const_iterator a = tctx.getInputAttributes()->begin(); a != tctx.getInputAttributes()->end(); ++a) {
197 if (m_source != (*a)->getId() || (*a)->valueCount() == 0) {
201 // We run each transform expression against each value of the input. Each transform either generates
202 // a new attribute from its dest property, or overwrites a SimpleAttribute's values in place.
204 for (vector<regex_t>::const_iterator r = m_regex.begin(); r != m_regex.end(); ++r) {
205 SimpleAttribute* dest = nullptr;
206 auto_ptr<SimpleAttribute> destwrapper;
208 // First tuple element is the destination attribute ID, if any.
209 if (r->get<0>().empty()) {
210 // Can we transform in-place?
211 dest = dynamic_cast<SimpleAttribute*>(*a);
213 m_log.warn("can't transform non-simple attribute (%s) 'in place'", m_source.c_str());
218 // Create a destination attribute.
219 vector<string> ids(1, r->get<0>());
220 destwrapper.reset(new SimpleAttribute(ids));
224 m_log.debug("applying in-place transform to source attribute (%s)", m_source.c_str());
226 m_log.debug("applying transform from source attribute (%s) to dest attribute (%s)", m_source.c_str(), r->get<0>().c_str());
228 for (size_t i = 0; i < (*a)->valueCount(); ++i) {
229 auto_arrayptr<XMLCh> srcval(fromUTF8((*a)->getSerializedValues()[i].c_str()));
231 XMLCh* destval = r->get<1>()->replace(srcval.get(), r->get<2>());
233 auto_arrayptr<char> narrow(toUTF8(destval));
234 XMLString::release(&destval);
237 dest->getValues()[i] = narrow.get();
240 // Add to new object.
241 destwrapper->getValues().push_back(narrow.get());
245 catch (XMLException& ex) {
246 auto_ptr_char msg(ex.getMessage());
247 m_log.error("caught error applying regular expression: %s", msg.get());
251 // Save off new object.
252 if (destwrapper.get()) {
253 ctx.getResolvedAttributes().push_back(destwrapper.get());
254 destwrapper.release();