diff --git a/server/authinfo.cpp b/server/authinfo.cpp index 4881d8fd..01493e71 100644 --- a/server/authinfo.cpp +++ b/server/authinfo.cpp @@ -98,7 +98,7 @@ std::error_code make_error_code(AuthErrc errc) } -AuthInfo::AuthInfo(ServerSettings::Authorization settings) : has_auth_info_(false), settings_(std::move(settings)) +AuthInfo::AuthInfo(ServerSettings::Authorization settings) : is_authenticated_(false), settings_(std::move(settings)) { } @@ -139,21 +139,21 @@ ErrorCode AuthInfo::authenticate(const std::string& auth) ErrorCode AuthInfo::authenticateBasic(const std::string& credentials) { - has_auth_info_ = false; + is_authenticated_ = false; std::string username = base64_decode(credentials); std::string password; username_ = utils::string::split_left(username, ':', password); auto ec = validateUser(username_, password); LOG(INFO, LOG_TAG) << "Authorization basic: " << credentials << ", user: " << username_ << ", password: " << password << "\n"; - has_auth_info_ = (ec.value() == 0); + is_authenticated_ = (ec.value() == 0); return ec; } #if 0 ErrorCode AuthInfo::authenticateBearer(const std::string& token) { - has_auth_info_ = false; + is_authenticated_ = false; std::ifstream ifs(settings_.ssl.certificate); std::string certificate((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); Jwt jwt; @@ -171,7 +171,7 @@ ErrorCode AuthInfo::authenticateBearer(const std::string& token) if (isExpired()) return {AuthErrc::expired}; - has_auth_info_ = true; + is_authenticated_ = true; return {}; } @@ -213,9 +213,9 @@ bool AuthInfo::isExpired() const } -bool AuthInfo::hasAuthInfo() const +bool AuthInfo::isAuthenticated() const { - return has_auth_info_; + return is_authenticated_; } @@ -239,7 +239,7 @@ bool AuthInfo::hasPermission(const std::string& resource) const if (!settings_.enabled) return true; - if (!hasAuthInfo()) + if (!isAuthenticated()) return false; const auto& user_iter = std::find_if(settings_.users.begin(), settings_.users.end(), [&](const auto& user) { return user.name == username_; }); diff --git a/server/authinfo.hpp b/server/authinfo.hpp index 47f2d3ce..28abc617 100644 --- a/server/authinfo.hpp +++ b/server/authinfo.hpp @@ -71,8 +71,8 @@ public: /// d'tor virtual ~AuthInfo() = default; - /// @return if authentication info is available - bool hasAuthInfo() const; + /// @return if user is authenticated + bool isAuthenticated() const; // ErrorCode isValid(const std::string& command) const; /// @return the username const std::string& username() const; @@ -92,8 +92,8 @@ public: bool hasPermission(const std::string& resource) const; private: - /// has auth info - bool has_auth_info_; + /// is authenticated + bool is_authenticated_; /// auth user name std::string username_; /// optional token expiration diff --git a/server/config.hpp b/server/config.hpp index 32149280..4aee75ee 100644 --- a/server/config.hpp +++ b/server/config.hpp @@ -143,6 +143,7 @@ struct Host : public JsonConfigItem /// Client config struct ClientConfig : public JsonConfigItem { + /// c'tor ClientConfig() : volume(100) { } @@ -207,6 +208,7 @@ struct Snapcast : public JsonConfigItem /// Snapclient config struct Snapclient : public Snapcast { + /// c'tor explicit Snapclient(std::string _name = "", std::string _version = "") : Snapcast(std::move(_name), std::move(_version)) { } diff --git a/server/control_requests.cpp b/server/control_requests.cpp index 7eceaaf1..4b3f028a 100644 --- a/server/control_requests.cpp +++ b/server/control_requests.cpp @@ -28,9 +28,11 @@ // 3rd party headers // standard headers +#include #include #include #include +#include static constexpr auto LOG_TAG = "ControlRequest"; @@ -110,6 +112,18 @@ ControlRequestFactory::ControlRequestFactory(const Server& server) add_request(std::make_shared(server)); add_request(std::make_shared(server)); // add_request(std::make_shared(server)); + + // General requests + auto get_rpc_cmds = std::make_shared(server); + add_request(get_rpc_cmds); + + std::vector> requests; + for (const auto& [key, value] : request_map_) + { + std::ignore = key; + requests.push_back(value); + } + get_rpc_cmds->setCommands(requests); } @@ -911,4 +925,57 @@ void ServerGetTokenRequest::execute(const jsonrpcpp::request_ptr& request, AuthI on_response(std::move(response), nullptr); } + #endif + +GeneralGetRpcCommands::GeneralGetRpcCommands(const Server& server) : Request(server, "General.GetRPCCommands") +{ +} + +void GeneralGetRpcCommands::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) +{ + // clang-format off + // Request: {"id":8,"jsonrpc":"2.0","method":"General.GetRPCCommands"} + // Response: {"id":8,"jsonrpc":"2.0","result":{"major":2,"minor":0,"patch":0}} + // clang-format on + + std::ignore = request; + std::ignore = authinfo; + std::ignore = on_response; + Json result; + Json commands = Json::array(); + + for (const auto& req : requests_) + { + Json jreq; + jreq["method"] = req->method(); + jreq["hasPermission"] = req->hasPermission(authinfo); + commands.push_back(jreq); + } + result["commands"] = commands; + + // for (const auto& [key, value]: request_map_) + // { + // key + // } + // // : backwards incompatible change + // result["major"] = 23; + // // : feature addition to the API + // result["minor"] = 0; + // // : bugfix release + // result["patch"] = 0; + auto response = std::make_shared(*request, result); + on_response(std::move(response), nullptr); +} + +bool GeneralGetRpcCommands::hasPermission(const AuthInfo& authinfo) const +{ + return authinfo.isAuthenticated(); +} + + +void GeneralGetRpcCommands::setCommands(std::vector> requests) +{ + requests_ = std::move(requests); + std::sort(requests_.begin(), requests_.end(), [](const auto& lhs, const auto& rhs) { return lhs->method() < rhs->method(); }); +} diff --git a/server/control_requests.hpp b/server/control_requests.hpp index 483999eb..428a03e7 100644 --- a/server/control_requests.hpp +++ b/server/control_requests.hpp @@ -1,6 +1,6 @@ /*** This file is part of snapcast - Copyright (C) 2014-2024 Johannes Pohl + Copyright (C) 2014-2025 Johannes Pohl This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -55,7 +55,7 @@ public: virtual void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) = 0; /// @return true if the user has the permission for the request - bool hasPermission(const AuthInfo& authinfo) const; + virtual bool hasPermission(const AuthInfo& authinfo) const; /// @return the name of the method const std::string& method() const; @@ -312,6 +312,23 @@ public: }; +/// "General.GetRpcCommands" request +class GeneralGetRpcCommands : public Request +{ +public: + /// c'tor + explicit GeneralGetRpcCommands(const Server& server); + void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override; + bool hasPermission(const AuthInfo& authinfo) const override; + + /// Set available @p requests + void setCommands(std::vector> requests); + +private: + std::vector> requests_; +}; + + #if 0 /// "Server.GetToken" request class ServerGetTokenRequest : public Request diff --git a/server/server.cpp b/server/server.cpp index 5bc7cf00..7dbcfd2e 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -145,7 +145,7 @@ void Server::processRequest(const jsonrpcpp::request_ptr& request, AuthInfo& aut else { std::optional e; - if (!authinfo.hasAuthInfo()) + if (!authinfo.isAuthenticated()) e.emplace(jsonrpcpp::Error("Unauthorized", 401), request->id()); else e.emplace(jsonrpcpp::Error("Forbidden", 403), request->id()); @@ -204,7 +204,7 @@ void Server::onMessageReceived(std::shared_ptr controlSession, c processRequest(request, controlSession->authinfo, [this, controlSession, response_handler](const jsonrpcpp::entity_ptr& response, const jsonrpcpp::notification_ptr& notification) { - // if (controlSession->authinfo.hasAuthInfo()) + // if (controlSession->authinfo.isAuthenticated()) // { // LOG(INFO, LOG_TAG) << "Request auth info - username: " << controlSession->authinfo->username() // << ", valid: " << controlSession->authinfo->valid() << "\n"; diff --git a/test/test_main.cpp b/test/test_main.cpp index 7609b46f..59f8375a 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -697,7 +697,7 @@ TEST_CASE("Auth") AuthInfo auth(settings); auto ec = auth.authenticateBasic(base64_encode("badaix:secret")); REQUIRE(!ec); - REQUIRE(auth.hasAuthInfo()); + REQUIRE(auth.isAuthenticated()); REQUIRE(auth.hasPermission("stream")); } @@ -710,7 +710,7 @@ TEST_CASE("Auth") AuthInfo auth(settings); auto ec = auth.authenticateBasic(base64_encode("badaix:secret")); REQUIRE(!ec); - REQUIRE(auth.hasAuthInfo()); + REQUIRE(auth.isAuthenticated()); REQUIRE(!auth.hasPermission("stream")); } @@ -722,12 +722,12 @@ TEST_CASE("Auth") AuthInfo auth(settings); auto ec = auth.authenticateBasic(base64_encode("badaix:wrong_password")); REQUIRE(ec == AuthErrc::wrong_password); - REQUIRE(!auth.hasAuthInfo()); + REQUIRE(!auth.isAuthenticated()); REQUIRE(!auth.hasPermission("stream")); ec = auth.authenticateBasic(base64_encode("unknown_user:secret")); REQUIRE(ec == AuthErrc::unknown_user); - REQUIRE(!auth.hasAuthInfo()); + REQUIRE(!auth.isAuthenticated()); REQUIRE(!auth.hasPermission("stream")); } }