From 8f844218beaf8aa1ac7402e73552659ad82d123b Mon Sep 17 00:00:00 2001 From: Matthias Janetschek Date: Fri, 29 May 2015 15:35:53 +0200 Subject: [PATCH] . --- .../service/webresource/DeviceRepoHandler.h | 4 + .../webroot/websocket.html | 11 ++- src/hos_cosgi_webserver/CMakeLists.txt | 1 + .../service/webserver/WebServerService.h | 17 +++- .../webresource/WebResourceHandlerImpl.h | 16 ++++ .../webserver/WebServerServiceImpl.cpp | 50 ++++++++++- .../service/webserver/WebServerServiceImpl.h | 54 ++++++++++++ .../webresource/DirectoryResource.cpp | 58 ++++--------- .../webserver/webresource/DirectoryResource.h | 4 + .../webserver/webresource/WebSocketTest.cpp | 85 +++++++++++++++++++ .../webserver/webresource/WebSocketTest.h | 70 +++++++++++++++ 11 files changed, 321 insertions(+), 49 deletions(-) create mode 100644 src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/WebSocketTest.cpp create mode 100644 src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/WebSocketTest.h diff --git a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h index 80e69a7c..1882a850 100644 --- a/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h +++ b/src/hos_cosgi_devicerepository/src/hos/cosgi/service/webresource/DeviceRepoHandler.h @@ -53,6 +53,10 @@ public: this->service = service; } + virtual bool excludeFromAuthentication() noexcept override { + return false; + } + }; diff --git a/src/hos_cosgi_testwebapp/webroot/websocket.html b/src/hos_cosgi_testwebapp/webroot/websocket.html index 860e5ce2..c19499b3 100644 --- a/src/hos_cosgi_testwebapp/webroot/websocket.html +++ b/src/hos_cosgi_testwebapp/webroot/websocket.html @@ -3,22 +3,27 @@ WebSocketServer diff --git a/src/hos_cosgi_webserver/CMakeLists.txt b/src/hos_cosgi_webserver/CMakeLists.txt index 596668ec..5edd5a20 100644 --- a/src/hos_cosgi_webserver/CMakeLists.txt +++ b/src/hos_cosgi_webserver/CMakeLists.txt @@ -57,6 +57,7 @@ bundle(hos_cosgi_webserver ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/WebServerServiceImpl ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/webresource/DirectoryResource ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/webresource/SchemaResource + ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/webresource/WebSocketTest ${PROJECT_SOURCE_DIR}/src/hos/cosgi/service/webserver/ph7/ph7 DIRECTORIES ${PROJECT_SOURCE_DIR}/certs diff --git a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/WebServerService.h b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/WebServerService.h index 6ac37414..8fe84642 100644 --- a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/WebServerService.h +++ b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/WebServerService.h @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -44,6 +45,7 @@ struct DirectoryResourceEntry { : dir(dir) {} std::string dir; bool enablePHP = false; + bool excludeFromAuthentication = false; }; @@ -59,6 +61,8 @@ public: virtual Poco::Net::HTTPRequestHandler* createRequestHandler( Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept = 0; virtual void setWebServerService(WebServerService* service) noexcept = 0; + + virtual bool excludeFromAuthentication() noexcept = 0; }; @@ -80,7 +84,18 @@ public: virtual void addDirectoryResource(const std::string& baseUri, const DirectoryResourceEntry& entry) = 0; - virtual Poco::Net::HTTPRequestHandler* createDefaultRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl ) = 0; + virtual bool isAuthenticationRequired() noexcept = 0; + + virtual bool checkAuthentication( const Poco::Net::HTTPServerRequest& request ) = 0; + + virtual Poco::Net::HTTPRequestHandler* createDefaultRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept = 0; + + virtual Poco::Net::HTTPRequestHandler* createRequireAuthenticationRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept = 0; + + virtual Poco::Net::HTTPRequestHandler* createErrorHandler( const Poco::Net::HTTPServerRequest& request, + bool ssl, + const std::string& msg = std::string(), + Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR ) noexcept = 0; }; diff --git a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/webresource/WebResourceHandlerImpl.h b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/webresource/WebResourceHandlerImpl.h index 4b68d350..2b13c4d3 100644 --- a/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/webresource/WebResourceHandlerImpl.h +++ b/src/hos_cosgi_webserver/include/hos_cosgi_webserver/hos/cosgi/service/webserver/webresource/WebResourceHandlerImpl.h @@ -66,6 +66,9 @@ public: virtual void setWebServerService(webserver::WebServerService* service) noexcept override { this->service = service; } + virtual bool excludeFromAuthentication() noexcept override { + return false; + } }; @@ -220,6 +223,19 @@ protected: addDebugInfo(out, request, resp); } + void handleRequireAuthenticationResponse( const Poco::Net::HTTPServerRequest& request, + Poco::Net::HTTPServerResponse &resp, + const std::string& realm, + const std::string& body = std::string(), + Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTPStatus::HTTP_UNAUTHORIZED ) { + resp.requireAuthentication(realm); + if (body.empty()) { + resp.send() << "ERROR: " << resp.getReason(); + } else { + resp.send() << body; + } + } + void addDebugInfo(std::ostream& out, const Poco::Net::HTTPServerRequest& req, Poco::Net::HTTPServerResponse &resp) { out << std::endl << std::endl <<"========== DEBUG ==========" << std::endl diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp index 45d3bbb3..aefb13e9 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.cpp @@ -7,7 +7,9 @@ #include #include #include +#include +#include "webresource/WebSocketTest.h" namespace hos { namespace cosgi { @@ -53,16 +55,33 @@ void WebServerServiceImpl::setCoreService(service::core::CoreService* coreServic Poco::Net::HTTPRequestHandler* WebServerServiceImpl::RequestHandlerFactory::createRequestHandler( const Poco::Net::HTTPServerRequest& request) { + WebResourceHandlerFactory* factory = nullptr; + std::string matchUri; // We want a longest prefix match. // Since we have an ordered map, we just need to iterate it in reverse order and return the first match. for (auto i = parent->webResourceFactories.rbegin(); i != parent->webResourceFactories.rend(); i++) { if ( boost::algorithm::starts_with(request.getURI(), i->first) && ( request.getURI().size() == i->first.size() || request.getURI()[i->first.size()] == '/' ) ) { - return i->second->createRequestHandler(Poco::URI(i->first), request, ssl); + factory = i->second; + matchUri = i->first; + break; + } + } + if (factory) { + if ( parent->requireAuthentication + && !factory->excludeFromAuthentication() + && !parent->checkAuthentication(request) ) { + return new RequireAuthenticationRequestHandler(); + } else { + return factory->createRequestHandler(Poco::URI(matchUri), request, ssl); + } + } else { + if ( parent->requireAuthentication && !parent->checkAuthentication(request) ) { + return new RequireAuthenticationRequestHandler(); + } else { + return new DefaultRequestHandler(); } } - // return default handler - return new DefaultRequestHandler(); } @@ -132,6 +151,9 @@ void WebServerServiceImpl::init() noexcept { return new webresource::SchemaResourceHandler(matchUri, nodehandle, &coreService->getSchemaLoader(), this); }) ); + registerWebResource("/websocket", + new webresource::WebSocketTestFactory() + ); initialized = true; ROS_INFO_STREAM("WebServerService: Initialized and Ready."); @@ -176,6 +198,28 @@ Poco::URI WebServerServiceImpl::getAbsoluteURI(const std::string& relpath) { } +bool WebServerServiceImpl::checkAuthentication( const Poco::Net::HTTPServerRequest& request ) { + if (request.hasCredentials()) { + std::string scheme; + std::string authInfo; + request.getCredentials(scheme, authInfo); + if (scheme == Poco::Net::HTTPBasicCredentials::SCHEME) { + Poco::Net::HTTPBasicCredentials cred(authInfo); + auto user = authorizedUsers.find(cred.getUsername()); + if (user != authorizedUsers.end()) { + if (user->second == cred.getPassword()) { + return true; + } + } + } else { + std::stringstream ss; + ss << "Unsupported authentication scheme \"" << scheme << "\"." << std::endl; + throw std::runtime_error(ss.str()); + } + } + return false; +} + } // namespace webserver } // namespace service diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.h b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.h index d770a190..7ad76b65 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.h +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/WebServerServiceImpl.h @@ -58,6 +58,24 @@ private: handleErrorResponse(req, resp, Poco::Net::HTTPResponse::HTTPStatus::HTTP_FORBIDDEN); } }; + class RequireAuthenticationRequestHandler + : public webresource::WebResourceHandlerImpl { + public: + virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override { + handleRequireAuthenticationResponse(req, resp, "Home Operating System Login"); + } + }; + class ErrorRequestHandler + : public webresource::WebResourceHandlerImpl { + public: + std::string msg; + Poco::Net::HTTPResponse::HTTPStatus status; + ErrorRequestHandler(const std::string& msg, Poco::Net::HTTPResponse::HTTPStatus status) + : msg(msg), status(status) {} + virtual void _handleRequest(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override { + handleErrorResponse(req, resp, msg, status); + } + }; public: RequestHandlerFactory(WebServerServiceImpl* parent, bool ssl = false) : parent(parent), ssl(ssl) {} @@ -65,6 +83,12 @@ private: Poco::Net::HTTPRequestHandler* createDefaultRequestHandler( const Poco::Net::HTTPServerRequest& request) { return new DefaultRequestHandler(); } + Poco::Net::HTTPRequestHandler* createRequireAuthenticationRequestHandler( const Poco::Net::HTTPServerRequest& request) { + return new RequireAuthenticationRequestHandler(); + } + Poco::Net::HTTPRequestHandler* createErrorHandler( const Poco::Net::HTTPServerRequest& request, const std::string& msg, Poco::Net::HTTPResponse::HTTPStatus status) { + return new ErrorRequestHandler(msg, status); + } } handlerFactory, handlerFactorySSL; std::string installDir; @@ -79,6 +103,11 @@ private: std::unique_ptr webServer; std::unique_ptr webServerSSL; std::map webResourceFactories; + bool requireAuthentication = false; + std::map authorizedUsers = { + {"user", "secret"}, + {"root", "pass"}, + }; void init() noexcept; @@ -109,6 +138,29 @@ public: } + virtual Poco::Net::HTTPRequestHandler* createRequireAuthenticationRequestHandler( const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept override { + if (ssl) { + return handlerFactorySSL.createRequireAuthenticationRequestHandler(request); + } else { + return handlerFactory.createRequireAuthenticationRequestHandler(request); + } + } + + virtual Poco::Net::HTTPRequestHandler* createErrorHandler( const Poco::Net::HTTPServerRequest& request, + bool ssl, + const std::string& msg = std::string(), + Poco::Net::HTTPResponse::HTTPStatus status = Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR ) noexcept { + if (ssl) { + return handlerFactorySSL.createErrorHandler(request, msg, status); + } else { + return handlerFactory.createErrorHandler(request, msg, status); + } + } + + virtual bool isAuthenticationRequired() noexcept { + return requireAuthentication; + } + /* Internal */ @@ -118,6 +170,8 @@ public: Poco::URI getAbsoluteURI(const std::string& relpath); + bool checkAuthentication( const Poco::Net::HTTPServerRequest& request ) override; + }; diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.cpp b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.cpp index 606fa747..afaa416b 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.cpp +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.cpp @@ -56,9 +56,23 @@ DirectoryResourceHandlerFactory::~DirectoryResourceHandlerFactory() { Poco::Net::HTTPRequestHandler* DirectoryResourceHandlerFactory::createRequestHandler(Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept { auto e = dirMap.find(matchUri.getPath()); if (e != dirMap.end()) { - return new DirectoryResourceHandler(matchUri, this, service, e->second, ssl); + try { + if ( service->isAuthenticationRequired() + && !e->second.excludeFromAuthentication + && !service->checkAuthentication(request) ) { + return service->createRequireAuthenticationRequestHandler(request, ssl); + } else { + return new DirectoryResourceHandler(matchUri, this, service, e->second, ssl); + } + } catch (std::exception& e) { + return service->createErrorHandler(request, ssl, e.what(), Poco::Net::HTTPResponse::HTTP_BAD_REQUEST); + } } else { - return service->createDefaultRequestHandler(request, ssl); + if ( service->isAuthenticationRequired() && !service->checkAuthentication(request) ) { + return service->createRequireAuthenticationRequestHandler(request, ssl); + } else { + return service->createDefaultRequestHandler(request, ssl); + } } } @@ -120,46 +134,6 @@ void DirectoryResourceHandler::serveDirectoy(Poco::Net::HTTPServerRequest &req, void DirectoryResourceHandler::serveRegularFile(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp, const boost::filesystem::path& file) { - - - std::ostream& ostr = std::cout; - ostr << ""; - ostr << ""; - ostr << "WebSocketServer"; - ostr << ""; - ostr << ""; - ostr << ""; - ostr << "

WebSocket Server

"; - ostr << "

Run WebSocket Script

"; - ostr << ""; - ostr << ""; - auto type = parent->contentTypes.find(file.extension().string()); if (type != parent->contentTypes.end()) { resp.setContentType(type->second); diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.h b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.h index 87c70b2f..6e424b8c 100644 --- a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.h +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/DirectoryResource.h @@ -69,6 +69,10 @@ public: this->service = service; } + virtual bool excludeFromAuthentication() noexcept override { + return false; + } + ph7* getPH7Engine() { return pEngine; } diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/WebSocketTest.cpp b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/WebSocketTest.cpp new file mode 100644 index 00000000..18f863ec --- /dev/null +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/WebSocketTest.cpp @@ -0,0 +1,85 @@ + +#include "WebSocketTest.h" +#include "../WebServerServiceImpl.h" +#include +#include +#include +#include +#include +#include +#include "Poco/Net/NetException.h" +#include +#include "Poco/Format.h" + + + +// Service Namespace +namespace hos { +namespace cosgi { +namespace service { +namespace webserver { +namespace webresource { + + +using namespace Poco; + + + + +HOS_SERVICE_IMPL_INFO( WebSocketTestFactory, // Implementation + service::webserver::WebResourceHandlerFactory::SERVICE_NAME, // Service Name + 1, 0 ) // Version + + + + + +Poco::Net::HTTPRequestHandler* WebSocketTestFactory::createRequestHandler(Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept { + + if(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0) + return new WebSocketTestHandler(matchUri, this, service, ssl); + else + return service->createErrorHandler(request, ssl, "Non-Websocket Requests are unsupported."); +} + + + +void WebSocketTestHandler::_handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) { + try { + Net::WebSocket ws(req, resp); + std::cout << "WebSocket connection established." << std::endl; + char buffer[1024]; + int flags; + int n; + do { + n = ws.receiveFrame(buffer, sizeof(buffer), flags); + std::cout << Poco::format("Frame received (length=%d, flags=0x%x).", n, unsigned(flags)) << std::endl; + ws.sendFrame(buffer, n, flags); + //} while (n > 0 || (flags & Net::WebSocket::FRAME_OP_BITMASK) != Net::WebSocket::FRAME_OP_CLOSE); + } while (n > 0 || ((flags & Net::WebSocket::FRAME_OP_BITMASK) != Net::WebSocket::FRAME_OP_CLOSE && (flags & Net::WebSocket::FRAME_OP_BITMASK) != 0)); + std::cout << "WebSocket connection closed." << std::endl; + } catch (Net::WebSocketException& exc) { + std::cout << "WebSocketException caught: " << exc.what() << std::endl; + switch (exc.code()) { + case Net::WebSocket::WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION: + resp.set("Sec-WebSocket-Version", Net::WebSocket::WEBSOCKET_VERSION); + // fallthrough + case Net::WebSocket::WS_ERR_NO_HANDSHAKE: + case Net::WebSocket::WS_ERR_HANDSHAKE_NO_VERSION: + case Net::WebSocket::WS_ERR_HANDSHAKE_NO_KEY: + resp.setStatusAndReason(Net::HTTPResponse::HTTP_BAD_REQUEST); + resp.setContentLength(0); + resp.send(); + break; + } + } +} + + + +} // namespace resources +} // namespace webserver +} // namespace service +} // namespace cosgi +} // namespace hos + diff --git a/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/WebSocketTest.h b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/WebSocketTest.h new file mode 100644 index 00000000..9d06d2fb --- /dev/null +++ b/src/hos_cosgi_webserver/src/hos/cosgi/service/webserver/webresource/WebSocketTest.h @@ -0,0 +1,70 @@ +#pragma once + + +#include +#include +#include +#include +#include +#include +#include + + +// Service Namespace +namespace hos { +namespace cosgi { +namespace service { +namespace webserver { +namespace webresource { + + +using namespace boost; + + + + +class WebSocketTestFactory + : public virtual webserver::WebResourceHandlerFactory, + public implementation::Service { +private: + WebServerService* service = nullptr; + +public: + + virtual Poco::Net::HTTPRequestHandler* createRequestHandler(Poco::URI matchUri, const Poco::Net::HTTPServerRequest& request, bool ssl ) noexcept override; + + virtual void setWebServerService(webserver::WebServerService* service) noexcept override { + this->service = service; + } + + virtual bool excludeFromAuthentication() noexcept override { + return false; + } +}; + + + + +class WebSocketTestHandler + : public WebResourceHandlerImpl { + +private: + Poco::URI matchUri; + WebSocketTestFactory* parent; + WebServerService* service; + bool ssl; + +public: + WebSocketTestHandler(Poco::URI matchUri, WebSocketTestFactory* parent, webserver::WebServerService* service, bool ssl) + : matchUri(matchUri), parent(parent), service(service), ssl(ssl) {} + + virtual void _handleGET(Poco::Net::HTTPServerRequest &req, Poco::Net::HTTPServerResponse &resp) override; +}; + + + +} // namespace resources +} // namespace webserver +} // namespace service +} // namespace cosgi +} // namespace hos