Change audience handling and validators to separate out entityID.
[shibboleth/sp.git] / shibsp / handler / impl / StatusHandler.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  * StatusHandler.cpp
19  * 
20  * Handler for exposing information about the internals of the SP.
21  */
22
23 #include "internal.h"
24 #include "Application.h"
25 #include "exceptions.h"
26 #include "ServiceProvider.h"
27 #include "handler/AbstractHandler.h"
28 #include "handler/RemotedHandler.h"
29 #include "util/CGIParser.h"
30
31 using namespace shibsp;
32 #ifndef SHIBSP_LITE
33 # include "SessionCache.h"
34 # include <saml/version.h>
35 using namespace opensaml::saml2md;
36 using namespace opensaml;
37 using namespace xmlsignature;
38 #endif
39 using namespace xmltooling;
40 using namespace std;
41
42 namespace shibsp {
43
44 #if defined (_MSC_VER)
45     #pragma warning( push )
46     #pragma warning( disable : 4250 )
47 #endif
48
49     class SHIBSP_DLLLOCAL Blocker : public DOMNodeFilter
50     {
51     public:
52         short acceptNode(const DOMNode* node) const {
53             return FILTER_REJECT;
54         }
55     };
56
57     static SHIBSP_DLLLOCAL Blocker g_Blocker;
58
59     class SHIBSP_API StatusHandler : public AbstractHandler, public RemotedHandler
60     {
61     public:
62         StatusHandler(const DOMElement* e, const char* appId);
63         virtual ~StatusHandler() {}
64
65         pair<bool,long> run(SPRequest& request, bool isHandler=true) const;
66         void receive(DDF& in, ostream& out);
67
68     private:
69         pair<bool,long> processMessage(const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse) const;
70
71         set<string> m_acl;
72     };
73
74 #if defined (_MSC_VER)
75     #pragma warning( pop )
76 #endif
77
78     Handler* SHIBSP_DLLLOCAL StatusHandlerFactory(const pair<const DOMElement*,const char*>& p)
79     {
80         return new StatusHandler(p.first, p.second);
81     }
82
83 #ifndef XMLTOOLING_NO_XMLSEC
84     vector<XSECCryptoX509*> g_NoCerts;
85 #else
86     vector<string> g_NoCerts;
87 #endif
88
89     static char _x2c(const char *what)
90     {
91         register char digit;
92
93         digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A')+10 : (what[0] - '0'));
94         digit *= 16;
95         digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A')+10 : (what[1] - '0'));
96         return(digit);
97     }
98
99     class DummyRequest : public HTTPRequest
100     {
101     public:
102         DummyRequest(const char* url) : m_parser(NULL), m_url(url), m_scheme(NULL), m_query(NULL), m_port(0) {
103 #ifdef HAVE_STRCASECMP
104             if (url && !strncasecmp(url,"http://",7)) {
105                 m_scheme="http";
106                 url+=7;
107             }
108             else if (url && !strncasecmp(url,"https://",8)) {
109                 m_scheme="https";
110                 url+=8;
111             }
112             else
113 #else
114             if (url && !strnicmp(url,"http://",7)) {
115                 m_scheme="http";
116                 m_port = 80;
117                 url+=7;
118             }
119             else if (url && !strnicmp(url,"https://",8)) {
120                 m_scheme="https";
121                 m_port = 443;
122                 url+=8;
123             }
124             else
125 #endif
126                 throw invalid_argument("Target parameter was not an absolute URL.");
127
128             m_query = strchr(url,'?');
129             if (m_query)
130                 m_query++;
131
132             const char* slash = strchr(url, '/');
133             const char* colon = strchr(url, ':');
134             if (colon && colon < slash) {
135                 m_hostname.assign(url, colon-url);
136                 string port(colon + 1, slash - colon);
137                 m_port = atoi(port.c_str());
138             }
139             else {
140                 m_hostname.assign(url, slash - url);
141             }
142
143             while (*slash) {
144                 if (*slash == '?') {
145                     m_uri += slash;
146                     break;
147                 }
148                 else if (*slash == ';') {
149                     // If this is Java being stupid, skip everything up to the query string, if any.
150                     if (!strncmp(slash, ";jsessionid=", 12)) {
151                         if (slash = strchr(slash, '?'))
152                             m_uri += slash;
153                         break;
154                     }
155                     else {
156                         m_uri += *slash;
157                     }
158                 }
159                 else if (*slash != '%') {
160                     m_uri += *slash;
161                 }
162                 else {
163                     ++slash;
164                     if (!isxdigit(*slash) || !isxdigit(*(slash+1)))
165                         throw invalid_argument("Bad request, contained unsupported encoded characters.");
166                     m_uri += _x2c(slash);
167                     ++slash;
168                 }
169                 ++slash;
170             }
171         }
172
173         ~DummyRequest() {
174             delete m_parser;
175         }
176
177         const char* getRequestURL() const {
178             return m_url;
179         }
180         const char* getScheme() const {
181             return m_scheme;
182         }
183         const char* getHostname() const {
184             return m_hostname.c_str();
185         }
186         int getPort() const {
187             return m_port;
188         }
189         const char* getRequestURI() const {
190             return m_uri.c_str();
191         }
192         const char* getMethod() const {
193             return "GET";
194         }
195         string getContentType() const {
196             return "";
197         }
198         long getContentLength() const {
199             return 0;
200         }
201         string getRemoteAddr() const {
202             return "";
203         }
204         string getRemoteUser() const {
205             return "";
206         }
207         const char* getRequestBody() const {
208             return NULL;
209         }
210         const char* getQueryString() const {
211             return m_query;
212         }
213         const char* getParameter(const char* name) const
214         {
215             if (!m_parser)
216                 m_parser=new CGIParser(*this);
217             
218             pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
219             return (bounds.first==bounds.second) ? NULL : bounds.first->second;
220         }
221         vector<const char*>::size_type getParameters(const char* name, vector<const char*>& values) const
222         {
223             if (!m_parser)
224                 m_parser=new CGIParser(*this);
225
226             pair<CGIParser::walker,CGIParser::walker> bounds=m_parser->getParameters(name);
227             while (bounds.first!=bounds.second) {
228                 values.push_back(bounds.first->second);
229                 ++bounds.first;
230             }
231             return values.size();
232         }
233         string getHeader(const char* name) const {
234             return "";
235         }
236         virtual const
237 #ifndef XMLTOOLING_NO_XMLSEC
238             std::vector<XSECCryptoX509*>&
239 #else
240             std::vector<std::string>& 
241 #endif
242             getClientCertificates() const {
243                 return g_NoCerts;
244         }
245
246     private:
247         mutable CGIParser* m_parser;
248         const char* m_url;
249         const char* m_scheme;
250         const char* m_query;
251         int m_port;
252         string m_hostname,m_uri;
253     };
254 };
255
256 StatusHandler::StatusHandler(const DOMElement* e, const char* appId)
257     : AbstractHandler(e, Category::getInstance(SHIBSP_LOGCAT".StatusHandler"), &g_Blocker)
258 {
259     string address(appId);
260     address += getString("Location").second;
261     setAddress(address.c_str());
262     if (SPConfig::getConfig().isEnabled(SPConfig::InProcess)) {
263         pair<bool,const char*> acl = getString("acl");
264         if (acl.first) {
265             string aclbuf=acl.second;
266             int j = 0;
267             for (unsigned int i=0;  i < aclbuf.length();  i++) {
268                 if (aclbuf.at(i)==' ') {
269                     m_acl.insert(aclbuf.substr(j, i-j));
270                     j = i+1;
271                 }
272             }
273             m_acl.insert(aclbuf.substr(j, aclbuf.length()-j));
274         }
275     }
276 }
277
278 pair<bool,long> StatusHandler::run(SPRequest& request, bool isHandler) const
279 {
280     SPConfig& conf = SPConfig::getConfig();
281     if (conf.isEnabled(SPConfig::InProcess)) {
282         if (!m_acl.empty() && m_acl.count(request.getRemoteAddr()) == 0) {
283             m_log.error("status handler request blocked from invalid address (%s)", request.getRemoteAddr().c_str());
284             istringstream msg("Status Handler Blocked");
285             return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_UNAUTHORIZED));
286         }
287     }
288
289     const char* target = request.getParameter("target");
290     if (target) {
291         // RequestMap query, so handle it inproc.
292         DummyRequest dummy(target);
293         RequestMapper::Settings settings = request.getApplication().getServiceProvider().getRequestMapper()->getSettings(dummy);
294         map<string,const char*> props;
295         settings.first->getAll(props);
296
297         request.setContentType("text/xml");
298         stringstream msg;
299         msg << "<StatusHandler>";
300             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
301 #ifndef SHIBSP_LITE
302                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
303                 << "' OpenSAML-C='" << OPENSAML_FULLVERSIONDOT
304 #endif
305                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
306             msg << "<RequestSettings";
307             for (map<string,const char*>::const_iterator p = props.begin(); p != props.end(); ++p)
308                 msg << ' ' << p->first << "='" << p->second << "'";
309             msg << '>' << target << "</RequestSettings>";
310             msg << "<Status><OK/></Status>";
311         msg << "</StatusHandler>";
312         return make_pair(true,request.sendResponse(msg));
313     }
314     
315     try {
316         if (conf.isEnabled(SPConfig::OutOfProcess)) {
317             // When out of process, we run natively and directly process the message.
318             return processMessage(request.getApplication(), request, request);
319         }
320         else {
321             // When not out of process, we remote all the message processing.
322             DDF out,in = wrap(request);
323             DDFJanitor jin(in), jout(out);            
324             out=request.getServiceProvider().getListenerService()->send(in);
325             return unwrap(request, out);
326         }
327     }
328     catch (XMLToolingException& ex) {
329         m_log.error("error while processing request: %s", ex.what());
330         request.setContentType("text/xml");
331         stringstream msg;
332         msg << "<StatusHandler>";
333             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
334 #ifndef SHIBSP_LITE
335                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
336                 << "' OpenSAML-C='" << OPENSAML_FULLVERSIONDOT
337 #endif
338                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
339             msg << "<Status><Exception type='" << ex.getClassName() << "'>" << ex.what() << "</Exception></Status>";
340         msg << "</StatusHandler>";
341         return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
342     }
343     catch (exception& ex) {
344         m_log.error("error while processing request: %s", ex.what());
345         request.setContentType("text/xml");
346         stringstream msg;
347         msg << "<StatusHandler>";
348             msg << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
349 #ifndef SHIBSP_LITE
350                 << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
351                 << "' OpenSAML-C='" << OPENSAML_FULLVERSIONDOT
352 #endif
353                 << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
354             msg << "<Status><Exception type='std::exception'>" << ex.what() << "</Exception></Status>";
355         msg << "</StatusHandler>";
356         return make_pair(true,request.sendResponse(msg, HTTPResponse::XMLTOOLING_HTTP_STATUS_ERROR));
357     }
358 }
359
360 void StatusHandler::receive(DDF& in, ostream& out)
361 {
362     // Find application.
363     const char* aid=in["application_id"].string();
364     const Application* app=aid ? SPConfig::getConfig().getServiceProvider()->getApplication(aid) : NULL;
365     if (!app) {
366         // Something's horribly wrong.
367         m_log.error("couldn't find application (%s) for status request", aid ? aid : "(missing)");
368         throw ConfigurationException("Unable to locate application for status request, deleted?");
369     }
370     
371     // Wrap a response shim.
372     DDF ret(NULL);
373     DDFJanitor jout(ret);
374     auto_ptr<HTTPRequest> req(getRequest(in));
375     auto_ptr<HTTPResponse> resp(getResponse(ret));
376         
377     // Since we're remoted, the result should either be a throw, a false/0 return,
378     // which we just return as an empty structure, or a response/redirect,
379     // which we capture in the facade and send back.
380     processMessage(*app, *req.get(), *resp.get());
381     out << ret;
382 }
383
384 pair<bool,long> StatusHandler::processMessage(
385     const Application& application, const HTTPRequest& httpRequest, HTTPResponse& httpResponse
386     ) const
387 {
388 #ifndef SHIBSP_LITE
389     m_log.debug("processing status request");
390
391     stringstream s;
392     s << "<StatusHandler>";
393     const char* status = "<OK/>";
394
395     s << "<Version Xerces-C='" << XERCES_FULLVERSIONDOT
396         << "' XML-Security-C='" << XSEC_FULLVERSIONDOT
397         << "' OpenSAML-C='" << OPENSAML_FULLVERSIONDOT
398         << "' Shibboleth='" << PACKAGE_VERSION << "'/>";
399
400     const char* param = NULL;
401     if (param) {
402     }
403     else {
404         // General configuration and status report.
405         try {
406             SessionCache* sc = application.getServiceProvider().getSessionCache(false);
407             if (sc) {
408                 sc->test();
409                 s << "<SessionCache><OK/></SessionCache>";
410             }
411             else {
412                 s << "<SessionCache><None/></SessionCache>";
413             }
414         }
415         catch (XMLToolingException& ex) {
416             s << "<SessionCache><Exception type='" << ex.getClassName() << "'>" << ex.what() << "</Exception></SessionCache>";
417             status = "<Partial/>";
418         }
419         catch (exception& ex) {
420             s << "<SessionCache><Exception type='std::exception'>" << ex.what() << "</Exception></SessionCache>";
421             status = "<Partial/>";
422         }
423
424         const PropertySet* relyingParty=NULL;
425         param=httpRequest.getParameter("entityID");
426         if (param) {
427             MetadataProvider* m = application.getMetadataProvider();
428             Locker mlock(m);
429             relyingParty = application.getRelyingParty(m->getEntityDescriptor(MetadataProvider::Criteria(param)).first);
430         }
431         else {
432             relyingParty = application.getRelyingParty(NULL);
433         }
434
435         s << "<Application id='" << application.getId() << "' entityID='" << relyingParty->getString("entityID").second << "'/>";
436
437         s << "<Handlers>";
438         vector<const Handler*> handlers;
439         application.getHandlers(handlers);
440         for (vector<const Handler*>::const_iterator h = handlers.begin(); h != handlers.end(); ++h) {
441             s << "<Handler type='" << (*h)->getType() << "' Location='" << (*h)->getString("Location").second << "'";
442             if ((*h)->getString("Binding").first)
443                 s << " Binding='" << (*h)->getString("Binding").second << "'";
444             s << "/>";
445         }
446         s << "</Handlers>";
447
448         CredentialResolver* credResolver=application.getCredentialResolver();
449         if (credResolver) {
450             Locker credLocker(credResolver);
451             CredentialCriteria cc;
452             cc.setUsage(Credential::SIGNING_CREDENTIAL);
453             pair<bool,const char*> keyName = relyingParty->getString("keyName");
454             if (keyName.first)
455                 cc.getKeyNames().insert(keyName.second);
456             vector<const Credential*> creds;
457             credResolver->resolve(creds,&cc);
458             for (vector<const Credential*>::const_iterator c = creds.begin(); c != creds.end(); ++c) {
459                 KeyInfo* kinfo = (*c)->getKeyInfo();
460                 if (kinfo) {
461                     auto_ptr<KeyDescriptor> kd(KeyDescriptorBuilder::buildKeyDescriptor());
462                     kd->setUse(KeyDescriptor::KEYTYPE_SIGNING);
463                     kd->setKeyInfo(kinfo);
464                     s << *(kd.get());
465                 }
466             }
467
468             cc.setUsage(Credential::ENCRYPTION_CREDENTIAL);
469             creds.clear();
470             cc.getKeyNames().clear();
471             credResolver->resolve(creds,&cc);
472             for (vector<const Credential*>::const_iterator c = creds.begin(); c != creds.end(); ++c) {
473                 KeyInfo* kinfo = (*c)->getKeyInfo();
474                 if (kinfo) {
475                     auto_ptr<KeyDescriptor> kd(KeyDescriptorBuilder::buildKeyDescriptor());
476                     kd->setUse(KeyDescriptor::KEYTYPE_ENCRYPTION);
477                     kd->setKeyInfo(kinfo);
478                     s << *(kd.get());
479                 }
480             }
481         }
482     }
483
484     s << "<Status>" << status << "</Status></StatusHandler>";
485
486     httpResponse.setContentType("text/xml");
487     return make_pair(true, httpResponse.sendResponse(s));
488 #else
489     return make_pair(false,0L);
490 #endif
491 }