https://issues.shibboleth.net/jira/browse/SSPCPP-421
[shibboleth/cpp-sp.git] / shibsp / handler / impl / AttributeCheckerHandler.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  * AttributeCheckerHandler.cpp
23  *
24  * Handler for checking a session for required attributes.
25  */
26
27 #include "internal.h"
28 #include "AccessControl.h"
29 #include "Application.h"
30 #include "exceptions.h"
31 #include "ServiceProvider.h"
32 #include "SessionCache.h"
33 #include "SPRequest.h"
34 #include "attribute/Attribute.h"
35 #include "handler/AbstractHandler.h"
36 #include "util/TemplateParameters.h"
37
38 #include <fstream>
39 #include <sstream>
40 #include <boost/bind.hpp>
41 #include <boost/scoped_ptr.hpp>
42 #include <boost/algorithm/string.hpp>
43 #include <xmltooling/XMLToolingConfig.h>
44 #include <xmltooling/util/PathResolver.h>
45 #include <xmltooling/util/XMLHelper.h>
46
47 using namespace shibsp;
48 using namespace xmltooling;
49 using namespace boost;
50 using namespace std;
51
52 namespace shibsp {
53
54 #if defined (_MSC_VER)
55     #pragma warning( push )
56     #pragma warning( disable : 4250 )
57 #endif
58
59     class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
60     {
61     public:
62 #ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
63         short
64 #else
65         FilterAction
66 #endif
67         acceptNode(const DOMNode* node) const {
68             return FILTER_REJECT;
69         }
70     };
71
72     static SHIBSP_DLLLOCAL Blocker g_Blocker;
73
74     class SHIBSP_API AttributeCheckerHandler : public AbstractHandler
75     {
76     public:
77         AttributeCheckerHandler(const DOMElement* e, const char* appId);
78         virtual ~AttributeCheckerHandler() {}
79
80         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
81
82     private:
83         void flushSession(SPRequest& request) const {
84             try {
85                 request.getApplication().getServiceProvider().getSessionCache()->remove(request.getApplication(), request, &request);
86             }
87             catch (std::exception&) {
88             }
89         }
90
91         string m_template;
92         bool m_flushSession;
93         vector<string> m_attributes;
94         scoped_ptr<AccessControl> m_acl;
95     };
96
97 #if defined (_MSC_VER)
98     #pragma warning( pop )
99 #endif
100
101     Handler* SHIBSP_DLLLOCAL AttributeCheckerFactory(const pair<const DOMElement*,const char*>& p)
102     {
103         return new AttributeCheckerHandler(p.first, p.second);
104     }
105
106     static const XMLCh attributes[] =   UNICODE_LITERAL_10(a,t,t,r,i,b,u,t,e,s);
107     static const XMLCh _flushSession[] = UNICODE_LITERAL_12(f,l,u,s,h,S,e,s,s,i,o,n);
108     static const XMLCh _template[] =    UNICODE_LITERAL_8(t,e,m,p,l,a,t,e);
109 };
110
111 AttributeCheckerHandler::AttributeCheckerHandler(const DOMElement* e, const char* appId)
112     : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".AttributeCheckerHandler"), &g_Blocker)
113 {
114     if (!SPConfig::getConfig().isEnabled(SPConfig::InProcess))
115         return;
116     m_template = XMLHelper::getAttrString(e, nullptr, _template);
117     if (m_template.empty())
118         throw ConfigurationException("AttributeChecker missing required template setting.");
119     XMLToolingConfig::getConfig().getPathResolver()->resolve(m_template, PathResolver::XMLTOOLING_CFG_FILE);
120
121     m_flushSession = XMLHelper::getAttrBool(e, false, _flushSession);
122
123     string attrs(XMLHelper::getAttrString(e, nullptr, attributes));
124     if (!attrs.empty()) {
125         split(m_attributes, attrs, is_space(), algorithm::token_compress_on);
126         if (m_attributes.empty())
127             throw ConfigurationException("AttributeChecker unable to parse attributes setting.");
128     }
129     else {
130         m_acl.reset(SPConfig::getConfig().AccessControlManager.newPlugin(XML_ACCESS_CONTROL, e));
131     }
132 }
133
134 pair<bool,long> AttributeCheckerHandler::run(SPRequest& request, bool isHandler) const
135 {
136     // If the checking passes, we route to the return URL, target URL, or homeURL in that order.
137     const char* returnURL = request.getParameter("return");
138     const char* target = request.getParameter("target");
139     if (!returnURL)
140         returnURL = target;
141     if (returnURL)
142         request.getApplication().limitRedirect(request, returnURL);
143     else
144         returnURL = request.getApplication().getString("homeURL").second;
145     if (!returnURL)
146         returnURL = "/";
147        
148     Session* session = nullptr;
149     try {
150         session = request.getSession(true, false, false);
151         if (!session)
152             request.log(SPRequest::SPWarn, "AttributeChecker found session unavailable immediately after creation");
153     }
154     catch (std::exception& ex) {
155         request.log(SPRequest::SPWarn, string("AttributeChecker caught exception accessing session immediately after creation: ") + ex.what());
156     }
157
158     Locker sessionLocker(session, false);
159
160     bool checked = false;
161     if (session) {
162         if (!m_attributes.empty()) {
163             typedef multimap<string,const Attribute*> indexed_t;
164             static indexed_t::const_iterator (indexed_t::* fn)(const string&) const = &indexed_t::find;
165             const indexed_t& indexed = session->getIndexedAttributes();
166             // Look for an attribute in the list that is not in the session multimap.
167             // If that fails, the check succeeds.
168             checked = (
169                 find_if(m_attributes.begin(), m_attributes.end(),
170                     boost::bind(fn, boost::cref(indexed), _1) == indexed.end()) == m_attributes.end()
171                 );
172         }
173         else {
174             checked = (m_acl && m_acl->authorized(request, session) == AccessControl::shib_acl_true);
175         }
176     }
177
178     if (checked) {
179         string loc(returnURL);
180         request.absolutize(loc);
181         return make_pair(true, request.sendRedirect(loc.c_str()));
182     }
183
184     request.setContentType("text/html; charset=UTF-8");
185     request.setResponseHeader("Expires","Wed, 01 Jan 1997 12:00:00 GMT");
186     request.setResponseHeader("Cache-Control","private,no-store,no-cache,max-age=0");
187
188     ifstream infile(m_template.c_str());
189     if (infile) {
190         TemplateParameters tp(nullptr, request.getApplication().getPropertySet("Errors"), session);
191         tp.m_request = &request;
192         stringstream str;
193         XMLToolingConfig::getConfig().getTemplateEngine()->run(infile, str, tp);
194         if (m_flushSession) {
195             sessionLocker.assign(); // unlock the session
196             flushSession(request);
197         }
198         return make_pair(true, request.sendError(str));
199     }
200
201     if (m_flushSession) {
202         sessionLocker.assign(); // unlock the session
203         flushSession(request);
204     }
205     m_log.error("could not process error template (%s)", m_template.c_str());
206     istringstream msg("Internal Server Error. Please contact the site administrator.");
207     return make_pair(true, request.sendResponse(msg));
208 }