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