First set of logout base classes and non-building draft of SP-initiated logout.
[shibboleth/sp.git] / shibsp / AbstractSPRequest.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /**
18  * AbstractSPRequest.cpp
19  * 
20  * Abstract base for SPRequest implementations  
21  */
22
23 #include "internal.h"
24 #include "AbstractSPRequest.h"
25 #include "Application.h"
26 #include "ServiceProvider.h"
27 #include "SessionCache.h"
28
29 #include <log4cpp/Category.hh>
30
31 using namespace shibsp;
32 using namespace opensaml;
33 using namespace xmltooling;
34 using namespace log4cpp;
35 using namespace std;
36
37 AbstractSPRequest::AbstractSPRequest()
38     : m_sp(NULL), m_mapper(NULL), m_app(NULL), m_sessionTried(false), m_session(NULL),
39         m_log(&Category::getInstance(SHIBSP_LOGCAT".SPRequest")), m_parser(NULL)
40 {
41     m_sp=SPConfig::getConfig().getServiceProvider();
42     m_sp->lock();
43 }
44
45 AbstractSPRequest::~AbstractSPRequest()
46 {
47     if (m_session)
48         m_session->unlock();
49     if (m_mapper)
50         m_mapper->unlock();
51     if (m_sp)
52         m_sp->unlock();
53     delete m_parser;
54 }
55
56 RequestMapper::Settings AbstractSPRequest::getRequestSettings() const
57 {
58     if (m_mapper)
59         return m_settings;
60
61     // Map request to application and content settings.
62     m_mapper=m_sp->getRequestMapper();
63     m_mapper->lock();
64     return m_settings = m_mapper->getSettings(*this);
65
66 }
67
68 const Application& AbstractSPRequest::getApplication() const
69 {
70     if (!m_app) {
71         // Now find the application from the URL settings
72         m_app=m_sp->getApplication(getRequestSettings().first->getString("applicationId").second);
73         if (!m_app)
74             throw ConfigurationException("Unable to map request to application settings, check configuration.");
75     }    
76     return *m_app;
77 }
78
79 Session* AbstractSPRequest::getSession(bool checkTimeout, bool ignoreAddress, bool cache) const
80 {
81     // Only attempt this once.
82     if (cache && m_sessionTried)
83         return m_session;
84     else if (cache)
85         m_sessionTried = true;
86
87     // Get session ID from cookie.
88     const Application& app = getApplication();
89     pair<string,const char*> shib_cookie = app.getCookieNameProps("_shibsession_");
90     const char* session_id = getCookie(shib_cookie.first.c_str());
91     if (!session_id || !*session_id)
92         return NULL;
93
94     // Need address checking and timeout settings.
95     time_t timeout=0;
96     if (checkTimeout || !ignoreAddress) {
97         const PropertySet* props=app.getPropertySet("Sessions");
98         if (props) {
99             if (checkTimeout) {
100                 pair<bool,unsigned int> p=props->getUnsignedInt("timeout");
101                 if (p.first)
102                     timeout = p.second;
103             }
104             pair<bool,bool> pcheck=props->getBool("consistentAddress");
105             if (pcheck.first)
106                 ignoreAddress = !pcheck.second;
107         }
108     }
109
110     // The cache will either silently pass a session or NULL back, or throw an exception out.
111     Session* session = getServiceProvider().getSessionCache()->find(
112         session_id, app, ignoreAddress ? NULL : getRemoteAddr().c_str(), checkTimeout ? &timeout : NULL
113         );
114     if (cache)
115         m_session = session;
116     return session;
117 }
118
119 const char* AbstractSPRequest::getRequestURL() const {
120     if (m_url.empty()) {
121         // Compute the full target URL
122         int port = getPort();
123         const char* scheme = getScheme();
124         m_url = string(scheme) + "://" + getHostname();
125         if ((!strcmp(scheme,"http") && port!=80) || (!strcmp(scheme,"https") && port!=443)) { 
126             ostringstream portstr;
127             portstr << port;
128             m_url += ":" + portstr.str();
129         }
130         scheme = getRequestURI();
131         if (scheme)
132             m_url += scheme;
133     }
134     return m_url.c_str();
135 }
136
137 const char* AbstractSPRequest::getParameter(const char* name) const
138 {
139     if (!m_parser)
140         m_parser=new CGIParser(*this);
141     
142     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
143     return (bounds.first==bounds.second) ? NULL : bounds.first->second;
144 }
145
146 vector<const char*>::size_type AbstractSPRequest::getParameters(const char* name, vector<const char*>& values) const
147 {
148     if (!m_parser)
149         m_parser=new CGIParser(*this);
150
151     pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
152     while (bounds.first!=bounds.second) {
153         values.push_back(bounds.first->second);
154         ++bounds.first;
155     }
156     return values.size();
157 }
158
159 const char* AbstractSPRequest::getCookie(const char* name) const
160 {
161     if (m_cookieMap.empty()) {
162         string cookies=getHeader("Cookie");
163
164         string::size_type pos=0,cname,namelen,val,vallen;
165         while (pos !=string::npos && pos < cookies.length()) {
166             while (isspace(cookies[pos])) pos++;
167             cname=pos;
168             pos=cookies.find_first_of("=",pos);
169             if (pos == string::npos)
170                 break;
171             namelen=pos-cname;
172             pos++;
173             if (pos==cookies.length())
174                 break;
175             val=pos;
176             pos=cookies.find_first_of(";",pos);
177             if (pos != string::npos) {
178                 vallen=pos-val;
179                 pos++;
180                 m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val,vallen)));
181             }
182             else
183                 m_cookieMap.insert(make_pair(cookies.substr(cname,namelen),cookies.substr(val)));
184         }
185     }
186     map<string,string>::const_iterator lookup=m_cookieMap.find(name);
187     return (lookup==m_cookieMap.end()) ? NULL : lookup->second.c_str();
188 }
189
190 const char* AbstractSPRequest::getHandlerURL(const char* resource) const
191 {
192     if (!resource)
193         resource = getRequestURL();
194
195     if (!m_handlerURL.empty() && resource && !strcmp(getRequestURL(),resource))
196         return m_handlerURL.c_str();
197         
198 #ifdef HAVE_STRCASECMP
199     if (!resource || (strncasecmp(resource,"http://",7) && strncasecmp(resource,"https://",8)))
200 #else
201     if (!resource || (strnicmp(resource,"http://",7) && strnicmp(resource,"https://",8)))
202 #endif
203         throw ConfigurationException("Target resource was not an absolute URL.");
204
205     bool ssl_only=false;
206     const char* handler=NULL;
207     const PropertySet* props=m_app->getPropertySet("Sessions");
208     if (props) {
209         pair<bool,bool> p=props->getBool("handlerSSL");
210         if (p.first)
211             ssl_only=p.second;
212         pair<bool,const char*> p2=props->getString("handlerURL");
213         if (p2.first)
214             handler=p2.second;
215     }
216     
217     // Should never happen...
218     if (!handler || (*handler!='/' && strncmp(handler,"http:",5) && strncmp(handler,"https:",6)))
219         throw ConfigurationException(
220             "Invalid handlerURL property ($1) in Application ($2)",
221             params(2, handler ? handler : "null", m_app->getId())
222             );
223
224     // The "handlerURL" property can be in one of three formats:
225     //
226     // 1) a full URI:       http://host/foo/bar
227     // 2) a hostless URI:   http:///foo/bar
228     // 3) a relative path:  /foo/bar
229     //
230     // #  Protocol  Host        Path
231     // 1  handler   handler     handler
232     // 2  handler   resource    handler
233     // 3  resource  resource    handler
234     //
235     // note: if ssl_only is true, make sure the protocol is https
236
237     const char* path = NULL;
238
239     // Decide whether to use the handler or the resource for the "protocol"
240     const char* prot;
241     if (*handler != '/') {
242         prot = handler;
243     }
244     else {
245         prot = resource;
246         path = handler;
247     }
248
249     // break apart the "protocol" string into protocol, host, and "the rest"
250     const char* colon=strchr(prot,':');
251     colon += 3;
252     const char* slash=strchr(colon,'/');
253     if (!path)
254         path = slash;
255
256     // Compute the actual protocol and store in member.
257     if (ssl_only)
258         m_handlerURL.assign("https://");
259     else
260         m_handlerURL.assign(prot, colon-prot);
261
262     // create the "host" from either the colon/slash or from the target string
263     // If prot == handler then we're in either #1 or #2, else #3.
264     // If slash == colon then we're in #2.
265     if (prot != handler || slash == colon) {
266         colon = strchr(resource, ':');
267         colon += 3;      // Get past the ://
268         slash = strchr(colon, '/');
269     }
270     string host(colon, (slash ? slash-colon : strlen(colon)));
271
272     // Build the handler URL
273     m_handlerURL += host + path;
274     return m_handlerURL.c_str();
275 }
276
277 void AbstractSPRequest::log(SPLogLevel level, const std::string& msg) const
278 {
279     reinterpret_cast<Category*>(m_log)->log(
280         (level == SPDebug ? log4cpp::Priority::DEBUG :
281         (level == SPInfo ? log4cpp::Priority::INFO :
282         (level == SPWarn ? log4cpp::Priority::WARN :
283         (level == SPError ? log4cpp::Priority::ERROR : log4cpp::Priority::CRIT)))),
284         msg
285         );
286 }
287
288 bool AbstractSPRequest::isPriorityEnabled(SPLogLevel level) const
289 {
290     return reinterpret_cast<Category*>(m_log)->isPriorityEnabled(
291         (level == SPDebug ? log4cpp::Priority::DEBUG :
292         (level == SPInfo ? log4cpp::Priority::INFO :
293         (level == SPWarn ? log4cpp::Priority::WARN :
294         (level == SPError ? log4cpp::Priority::ERROR : log4cpp::Priority::CRIT))))
295         );
296 }