Use shibboleth-sp as package name for compatibility.
[shibboleth/cpp-sp.git] / shibsp / AbstractSPRequest.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  * AbstractSPRequest.cpp
23  *
24  * Abstract base for SPRequest implementations.
25  */
26
27 #include "internal.h"
28 #include "exceptions.h"
29 #include "AbstractSPRequest.h"
30 #include "Application.h"
31 #include "GSSRequest.h"
32 #include "ServiceProvider.h"
33 #include "SessionCache.h"
34 #include "util/CGIParser.h"
35
36 #include <boost/lexical_cast.hpp>
37
38 using namespace shibsp;
39 using namespace opensaml;
40 using namespace xmltooling;
41 using namespace std;
42
43 SPRequest::SPRequest()
44 {
45 }
46
47 SPRequest::~SPRequest()
48 {
49 }
50
51 string SPRequest::getSecureHeader(const char* name) const
52 {
53     return getHeader(name);
54 }
55
56 void SPRequest::setAuthType(const char* authtype)
57 {
58 }
59
60 #ifdef SHIBSP_HAVE_GSSAPI
61 GSSRequest::GSSRequest()
62 {
63 }
64
65 GSSRequest::~GSSRequest()
66 {
67 }
68
69 gss_name_t GSSRequest::getGSSName() const
70 {
71     return GSS_C_NO_NAME;
72 }
73 #endif
74
75 AbstractSPRequest::AbstractSPRequest(const char* category)
76     : m_sp(SPConfig::getConfig().getServiceProvider()),
77         m_mapper(nullptr), m_app(nullptr), m_sessionTried(false), m_session(nullptr),
78         m_log(&Category::getInstance(category))
79 {
80     m_sp->lock();
81 }
82
83 AbstractSPRequest::~AbstractSPRequest()
84 {
85     if (m_session)
86         m_session->unlock();
87     if (m_mapper)
88         m_mapper->unlock();
89     if (m_sp)
90         m_sp->unlock();
91 }
92
93 const ServiceProvider& AbstractSPRequest::getServiceProvider() const
94 {
95     return *m_sp;
96 }
97
98 RequestMapper::Settings AbstractSPRequest::getRequestSettings() const
99 {
100     if (!m_mapper) {
101         // Map request to application and content settings.
102         m_mapper = m_sp->getRequestMapper();
103         m_mapper->lock();
104         m_settings = m_mapper->getSettings(*this);
105
106         if (reinterpret_cast<Category*>(m_log)->isDebugEnabled()) {
107             reinterpret_cast<Category*>(m_log)->debug(
108                 "mapped %s to %s", getRequestURL(), m_settings.first->getString("applicationId").second
109                 );
110         }
111     }
112     return m_settings;
113 }
114
115 const Application& AbstractSPRequest::getApplication() const
116 {
117     if (!m_app) {
118         // Now find the application from the URL settings
119         m_app = m_sp->getApplication(getRequestSettings().first->getString("applicationId").second);
120         if (!m_app)
121             throw ConfigurationException("Unable to map non-default applicationId to an ApplicationOverride, check configuration.");
122     }
123     return *m_app;
124 }
125
126 Session* AbstractSPRequest::getSession(bool checkTimeout, bool ignoreAddress, bool cache)
127 {
128     // Only attempt this once.
129     if (cache && m_sessionTried)
130         return m_session;
131     else if (cache)
132         m_sessionTried = true;
133
134     // Need address checking and timeout settings.
135     time_t timeout = 3600;
136     if (checkTimeout || !ignoreAddress) {
137         const PropertySet* props = getApplication().getPropertySet("Sessions");
138         if (props) {
139             if (checkTimeout) {
140                 pair<bool,unsigned int> p = props->getUnsignedInt("timeout");
141                 if (p.first)
142                     timeout = p.second;
143             }
144             pair<bool,bool> pcheck = props->getBool("consistentAddress");
145             if (pcheck.first)
146                 ignoreAddress = !pcheck.second;
147         }
148     }
149
150     // The cache will either silently pass a session or nullptr back, or throw an exception out.
151     Session* session = getServiceProvider().getSessionCache()->find(
152         getApplication(), *this, (ignoreAddress ? nullptr : getRemoteAddr().c_str()), (checkTimeout ? &timeout : nullptr)
153         );
154     if (cache)
155         m_session = session;
156     return session;
157 }
158
159 static char _x2c(const char *what)
160 {
161     register char digit;
162
163     digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
164     digit *= 16;
165     digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
166     return(digit);
167 }
168
169 void AbstractSPRequest::setRequestURI(const char* uri)
170 {
171     // Fix for bug 574, secadv 20061002
172     // Unescape URI up to query string delimiter by looking for %XX escapes.
173     // Adapted from Apache's util.c, ap_unescape_url function.
174     if (uri) {
175         while (*uri) {
176             if (*uri == '?') {
177                 m_uri += uri;
178                 break;
179             }
180             else if (*uri != '%') {
181                 m_uri += *uri;
182             }
183             else {
184                 ++uri;
185                 if (!isxdigit(*uri) || !isxdigit(*(uri+1)))
186                     throw ConfigurationException("Bad request, contained unsupported encoded characters.");
187                 m_uri += _x2c(uri);
188                 ++uri;
189             }
190             ++uri;
191         }
192     }
193 }
194
195 const char* AbstractSPRequest::getRequestURI() const
196 {
197     return m_uri.c_str();
198 }
199
200 const char* AbstractSPRequest::getRequestURL() const
201 {
202     if (m_url.empty()) {
203         // Compute the full target URL
204         int port = getPort();
205         const char* scheme = getScheme();
206         m_url = string(scheme) + "://" + getHostname();
207         if (!isDefaultPort())
208             m_url += ":" + boost::lexical_cast<string>(port);
209         m_url += m_uri;
210     }
211     return m_url.c_str();
212 }
213
214 string AbstractSPRequest::getRemoteAddr() const
215 {
216     pair<bool,const char*> addr = getRequestSettings().first->getString("REMOTE_ADDR");
217     return addr.first ? getHeader(addr.second) : "";
218 }
219
220 const char* AbstractSPRequest::getParameter(const char* name) const
221 {
222     if (!m_parser.get())
223         m_parser.reset(new CGIParser(*this));
224
225     pair<CGIParser::walker,CGIParser::walker> bounds = m_parser->getParameters(name);
226     return (bounds.first==bounds.second) ? nullptr : bounds.first->second;
227 }
228
229 vector<const char*>::size_type AbstractSPRequest::getParameters(const char* name, vector<const char*>& values) const
230 {
231     if (!m_parser.get())
232         m_parser.reset(new CGIParser(*this));
233
234     pair<CGIParser::walker,CGIParser::walker> bounds = m_parser->getParameters(name);
235     while (bounds.first != bounds.second) {
236         values.push_back(bounds.first->second);
237         ++bounds.first;
238     }
239     return values.size();
240 }
241
242 const char* AbstractSPRequest::getHandlerURL(const char* resource) const
243 {
244     if (!resource)
245         resource = getRequestURL();
246
247     if (!m_handlerURL.empty() && resource && !strcmp(getRequestURL(), resource))
248         return m_handlerURL.c_str();
249
250     // Check for relative URL.
251     string stackresource;
252     if (resource && *resource == '/') {
253         // Compute a URL to the root of the site and point resource at constructed string.
254         int port = getPort();
255         const char* scheme = getScheme();
256         stackresource = string(scheme) + "://" + getHostname();
257         if (!isDefaultPort())
258             stackresource += ":" + boost::lexical_cast<string>(port);
259         stackresource += resource;
260         resource = stackresource.c_str();
261     }
262
263 #ifdef HAVE_STRCASECMP
264     if (!resource || (strncasecmp(resource,"http://",7) && strncasecmp(resource,"https://",8)))
265 #else
266     if (!resource || (strnicmp(resource,"http://",7) && strnicmp(resource,"https://",8)))
267 #endif
268         throw ConfigurationException("Target resource was not an absolute URL.");
269
270     bool ssl_only = true;
271     const char* handler = nullptr;
272     const PropertySet* props = getApplication().getPropertySet("Sessions");
273     if (props) {
274         pair<bool,bool> p = props->getBool("handlerSSL");
275         if (p.first)
276             ssl_only = p.second;
277         pair<bool,const char*> p2 = props->getString("handlerURL");
278         if (p2.first)
279             handler = p2.second;
280     }
281
282     if (!handler) {
283         handler = "/Shibboleth.sso";
284     }
285     else if (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)) {
286         throw ConfigurationException(
287             "Invalid handlerURL property ($1) in <Sessions> element for Application ($2)",
288             params(2, handler ? handler : "null", m_app->getId())
289             );
290     }
291
292     // The "handlerURL" property can be in one of three formats:
293     //
294     // 1) a full URI:       http://host/foo/bar
295     // 2) a hostless URI:   http:///foo/bar
296     // 3) a relative path:  /foo/bar
297     //
298     // #  Protocol  Host        Path
299     // 1  handler   handler     handler
300     // 2  handler   resource    handler
301     // 3  resource  resource    handler
302     //
303     // note: if ssl_only is true, make sure the protocol is https
304
305     const char* path = nullptr;
306
307     // Decide whether to use the handler or the resource for the "protocol"
308     const char* prot;
309     if (*handler != '/') {
310         prot = handler;
311     }
312     else {
313         prot = resource;
314         path = handler;
315     }
316
317     // break apart the "protocol" string into protocol, host, and "the rest"
318     const char* colon = strchr(prot, ':');
319     colon += 3;
320     const char* slash = strchr(colon, '/');
321     if (!path)
322         path = slash;
323
324     // Compute the actual protocol and store in member.
325     if (ssl_only)
326         m_handlerURL.assign("https://");
327     else
328         m_handlerURL.assign(prot, colon-prot);
329
330     // create the "host" from either the colon/slash or from the target string
331     // If prot == handler then we're in either #1 or #2, else #3.
332     // If slash == colon then we're in #2.
333     if (prot != handler || slash == colon) {
334         colon = strchr(resource, ':');
335         colon += 3;      // Get past the ://
336         slash = strchr(colon, '/');
337     }
338     string host(colon, (slash ? slash-colon : strlen(colon)));
339
340     // Build the handler URL
341     m_handlerURL += host + path;
342     return m_handlerURL.c_str();
343 }
344
345 void AbstractSPRequest::log(SPLogLevel level, const std::string& msg) const
346 {
347     reinterpret_cast<Category*>(m_log)->log(
348         (level == SPDebug ? Priority::DEBUG :
349         (level == SPInfo ? Priority::INFO :
350         (level == SPWarn ? Priority::WARN :
351         (level == SPError ? Priority::ERROR : Priority::CRIT)))),
352         msg
353         );
354 }
355
356 bool AbstractSPRequest::isPriorityEnabled(SPLogLevel level) const
357 {
358     return reinterpret_cast<Category*>(m_log)->isPriorityEnabled(
359         (level == SPDebug ? Priority::DEBUG :
360         (level == SPInfo ? Priority::INFO :
361         (level == SPWarn ? Priority::WARN :
362         (level == SPError ? Priority::ERROR : Priority::CRIT))))
363         );
364 }