/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2023 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include namespace { constexpr std::string_view KeyIdentifier = "Identifier"; constexpr std::string_view TcpSocketType = "TcpSocket"; constexpr std::string_view WebSocketType = "WebSocket"; constexpr std::string_view DenyAccess = "Deny"; constexpr std::string_view RequirePassword = "RequirePassword"; constexpr std::string_view AllowAccess = "Allow"; constexpr openspace::properties::Property::PropertyInfo EnabledInfo = { "Enabled", "Enabled", "This setting determines whether this server interface is enabled or not", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo TypeInfo = { "Type", "Type", "Whether the interface is using a Socket or a WebSocket", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo PortInfo = { "Port", "Port", "The network port to use for this sevrer interface", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo DefaultAccessInfo = { "DefaultAccess", "Default Access", "Sets the default access policy: Allow, RequirePassword or Deny", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo AllowAddressesInfo = { "AllowAddresses", "Allow Addresses", "IP addresses or domains that should always be allowed access to this interface", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo RequirePasswordAddressesInfo = { "RequirePasswordAddresses", "Require Password Addresses", "IP addresses or domains that should be allowed access if they provide a " "password", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo DenyAddressesInfo = { "DenyAddresses", "Deny Addresses", "IP addresses or domains that should never be allowed access to this interface", openspace::properties::Property::Visibility::AdvancedUser }; constexpr openspace::properties::Property::PropertyInfo PasswordInfo = { "Password", "Password", "Password for connecting to this interface", openspace::properties::Property::Visibility::AdvancedUser }; } // namespace namespace openspace { std::unique_ptr ServerInterface::createFromDictionary( const ghoul::Dictionary& config) { // TODO: Use documentation to verify dictionary auto si = std::make_unique(config); return si; } ServerInterface::ServerInterface(const ghoul::Dictionary& config) : properties::PropertyOwner({ "", "", "" }) , _socketType(TypeInfo) , _port(PortInfo, 0) , _enabled(EnabledInfo) , _allowAddresses(AllowAddressesInfo) , _requirePasswordAddresses(RequirePasswordAddressesInfo) , _denyAddresses(DenyAddressesInfo) , _defaultAccess(DefaultAccessInfo) , _password(PasswordInfo) { _socketType.addOption( static_cast(InterfaceType::TcpSocket), std::string(TcpSocketType) ); _socketType.addOption( static_cast(InterfaceType::WebSocket), std::string(WebSocketType) ); _defaultAccess.addOption( static_cast(Access::Deny), std::string(DenyAccess) ); _defaultAccess.addOption( static_cast(Access::RequirePassword), std::string(RequirePassword) ); _defaultAccess.addOption( static_cast(Access::Allow), std::string(AllowAccess) ); if (config.hasKey(DefaultAccessInfo.identifier)) { std::string access = config.value(DefaultAccessInfo.identifier); if (access == DenyAccess) { _defaultAccess.setValue(static_cast(Access::Deny)); } else if (access == RequirePassword) { _defaultAccess.setValue(static_cast(Access::RequirePassword)); } else if (access == AllowAccess) { _defaultAccess.setValue(static_cast(Access::Allow)); } } const std::string identifier = config.value(KeyIdentifier); auto readList = [config](const std::string& key, properties::StringListProperty& list) { if (config.hasValue(key)) { const ghoul::Dictionary& dict = config.value(key); std::vector v; for (std::string_view k : dict.keys()) { v.push_back(dict.value(k)); } list.set(v); } }; readList(AllowAddressesInfo.identifier, _allowAddresses); readList(DenyAddressesInfo.identifier, _denyAddresses); readList(RequirePasswordAddressesInfo.identifier, _requirePasswordAddresses); this->setIdentifier(identifier); this->setGuiName(identifier); this->setDescription("Settings for server interface " + identifier); const std::string type = config.value(TypeInfo.identifier); if (type == TcpSocketType) { _type = static_cast(InterfaceType::TcpSocket); } else if (type == WebSocketType) { _type = static_cast(InterfaceType::WebSocket); } if (config.hasValue(PasswordInfo.identifier)) { _password = config.value(PasswordInfo.identifier); } _port = static_cast(config.value(PortInfo.identifier)); _enabled = config.value(EnabledInfo.identifier); auto reinitialize = [this]() { deinitialize(); initialize(); }; _socketType.onChange(reinitialize); _port.onChange(reinitialize); _enabled.onChange(reinitialize); _defaultAccess.onChange(reinitialize); _allowAddresses.onChange(reinitialize); _requirePasswordAddresses.onChange(reinitialize); _denyAddresses.onChange(reinitialize); addProperty(_socketType); addProperty(_port); addProperty(_enabled); addProperty(_defaultAccess); addProperty(_allowAddresses); addProperty(_requirePasswordAddresses); addProperty(_denyAddresses); addProperty(_password); } ServerInterface::~ServerInterface() {} void ServerInterface::initialize() { if (!_enabled) { return; } switch (static_cast(_socketType.value())) { case InterfaceType::TcpSocket: _socketServer = std::make_unique(); break; case InterfaceType::WebSocket: _socketServer = std::make_unique(); break; } _socketServer->listen(_port); } void ServerInterface::deinitialize() { _socketServer->close(); } bool ServerInterface::isEnabled() const { return _enabled.value(); } bool ServerInterface::isActive() const { return _socketServer->isListening(); } int ServerInterface::port() const { return _port; } std::string ServerInterface::password() const { return _password; } bool ServerInterface::clientHasAccessWithoutPassword( const std::string& clientAddress) const { for (const std::string& address : _allowAddresses.value()) { if (clientAddress == address) { return true; } } Access access = static_cast(_defaultAccess.value()); if (access == Access::Allow) { for (const std::string& address : _denyAddresses.value()) { if (clientAddress == address) { return false; } } return true; } return false; } bool ServerInterface::clientIsBlocked(const std::string& clientAddress) const { for (const std::string& address : _denyAddresses.value()) { if (clientAddress == address) { return true; } } Access access = static_cast(_defaultAccess.value()); if (access == Access::Deny) { for (const std::string& address : _allowAddresses.value()) { if (clientAddress == address) { return false; } } for (const std::string& address : _requirePasswordAddresses.value()) { if (clientAddress == address) { return false; } } return true; } return false; } ghoul::io::SocketServer* ServerInterface::server() { return _socketServer.get(); } }