Add "General.GetRPCCommands" RPC command

This commit is contained in:
badaix 2025-02-04 21:11:32 +01:00
parent 6b94392f2c
commit f264080c77
7 changed files with 106 additions and 20 deletions

View file

@ -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) ErrorCode AuthInfo::authenticateBasic(const std::string& credentials)
{ {
has_auth_info_ = false; is_authenticated_ = false;
std::string username = base64_decode(credentials); std::string username = base64_decode(credentials);
std::string password; std::string password;
username_ = utils::string::split_left(username, ':', password); username_ = utils::string::split_left(username, ':', password);
auto ec = validateUser(username_, password); auto ec = validateUser(username_, password);
LOG(INFO, LOG_TAG) << "Authorization basic: " << credentials << ", user: " << username_ << ", password: " << password << "\n"; 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; return ec;
} }
#if 0 #if 0
ErrorCode AuthInfo::authenticateBearer(const std::string& token) ErrorCode AuthInfo::authenticateBearer(const std::string& token)
{ {
has_auth_info_ = false; is_authenticated_ = false;
std::ifstream ifs(settings_.ssl.certificate); std::ifstream ifs(settings_.ssl.certificate);
std::string certificate((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); std::string certificate((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
Jwt jwt; Jwt jwt;
@ -171,7 +171,7 @@ ErrorCode AuthInfo::authenticateBearer(const std::string& token)
if (isExpired()) if (isExpired())
return {AuthErrc::expired}; return {AuthErrc::expired};
has_auth_info_ = true; is_authenticated_ = true;
return {}; 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) if (!settings_.enabled)
return true; return true;
if (!hasAuthInfo()) if (!isAuthenticated())
return false; return false;
const auto& user_iter = std::find_if(settings_.users.begin(), settings_.users.end(), [&](const auto& user) { return user.name == username_; }); const auto& user_iter = std::find_if(settings_.users.begin(), settings_.users.end(), [&](const auto& user) { return user.name == username_; });

View file

@ -71,8 +71,8 @@ public:
/// d'tor /// d'tor
virtual ~AuthInfo() = default; virtual ~AuthInfo() = default;
/// @return if authentication info is available /// @return if user is authenticated
bool hasAuthInfo() const; bool isAuthenticated() const;
// ErrorCode isValid(const std::string& command) const; // ErrorCode isValid(const std::string& command) const;
/// @return the username /// @return the username
const std::string& username() const; const std::string& username() const;
@ -92,8 +92,8 @@ public:
bool hasPermission(const std::string& resource) const; bool hasPermission(const std::string& resource) const;
private: private:
/// has auth info /// is authenticated
bool has_auth_info_; bool is_authenticated_;
/// auth user name /// auth user name
std::string username_; std::string username_;
/// optional token expiration /// optional token expiration

View file

@ -143,6 +143,7 @@ struct Host : public JsonConfigItem
/// Client config /// Client config
struct ClientConfig : public JsonConfigItem struct ClientConfig : public JsonConfigItem
{ {
/// c'tor
ClientConfig() : volume(100) ClientConfig() : volume(100)
{ {
} }
@ -207,6 +208,7 @@ struct Snapcast : public JsonConfigItem
/// Snapclient config /// Snapclient config
struct Snapclient : public Snapcast struct Snapclient : public Snapcast
{ {
/// c'tor
explicit Snapclient(std::string _name = "", std::string _version = "") : Snapcast(std::move(_name), std::move(_version)) explicit Snapclient(std::string _name = "", std::string _version = "") : Snapcast(std::move(_name), std::move(_version))
{ {
} }

View file

@ -28,9 +28,11 @@
// 3rd party headers // 3rd party headers
// standard headers // standard headers
#include <algorithm>
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <tuple> #include <tuple>
#include <vector>
static constexpr auto LOG_TAG = "ControlRequest"; static constexpr auto LOG_TAG = "ControlRequest";
@ -110,6 +112,18 @@ ControlRequestFactory::ControlRequestFactory(const Server& server)
add_request(std::make_shared<ServerDeleteClientRequest>(server)); add_request(std::make_shared<ServerDeleteClientRequest>(server));
add_request(std::make_shared<ServerAuthenticateRequest>(server)); add_request(std::make_shared<ServerAuthenticateRequest>(server));
// add_request(std::make_shared<ServerGetTokenRequest>(server)); // add_request(std::make_shared<ServerGetTokenRequest>(server));
// General requests
auto get_rpc_cmds = std::make_shared<GeneralGetRpcCommands>(server);
add_request(get_rpc_cmds);
std::vector<std::shared_ptr<Request>> 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); on_response(std::move(response), nullptr);
} }
#endif #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
// }
// // <major>: backwards incompatible change
// result["major"] = 23;
// // <minor>: feature addition to the API
// result["minor"] = 0;
// // <patch>: bugfix release
// result["patch"] = 0;
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
on_response(std::move(response), nullptr);
}
bool GeneralGetRpcCommands::hasPermission(const AuthInfo& authinfo) const
{
return authinfo.isAuthenticated();
}
void GeneralGetRpcCommands::setCommands(std::vector<std::shared_ptr<Request>> requests)
{
requests_ = std::move(requests);
std::sort(requests_.begin(), requests_.end(), [](const auto& lhs, const auto& rhs) { return lhs->method() < rhs->method(); });
}

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast 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 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 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; 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 /// @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 /// @return the name of the method
const std::string& method() const; 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<std::shared_ptr<Request>> requests);
private:
std::vector<std::shared_ptr<Request>> requests_;
};
#if 0 #if 0
/// "Server.GetToken" request /// "Server.GetToken" request
class ServerGetTokenRequest : public Request class ServerGetTokenRequest : public Request

View file

@ -145,7 +145,7 @@ void Server::processRequest(const jsonrpcpp::request_ptr& request, AuthInfo& aut
else else
{ {
std::optional<jsonrpcpp::RequestException> e; std::optional<jsonrpcpp::RequestException> e;
if (!authinfo.hasAuthInfo()) if (!authinfo.isAuthenticated())
e.emplace(jsonrpcpp::Error("Unauthorized", 401), request->id()); e.emplace(jsonrpcpp::Error("Unauthorized", 401), request->id());
else else
e.emplace(jsonrpcpp::Error("Forbidden", 403), request->id()); e.emplace(jsonrpcpp::Error("Forbidden", 403), request->id());
@ -204,7 +204,7 @@ void Server::onMessageReceived(std::shared_ptr<ControlSession> controlSession, c
processRequest(request, controlSession->authinfo, processRequest(request, controlSession->authinfo,
[this, controlSession, response_handler](const jsonrpcpp::entity_ptr& response, const jsonrpcpp::notification_ptr& notification) [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() // LOG(INFO, LOG_TAG) << "Request auth info - username: " << controlSession->authinfo->username()
// << ", valid: " << controlSession->authinfo->valid() << "\n"; // << ", valid: " << controlSession->authinfo->valid() << "\n";

View file

@ -697,7 +697,7 @@ TEST_CASE("Auth")
AuthInfo auth(settings); AuthInfo auth(settings);
auto ec = auth.authenticateBasic(base64_encode("badaix:secret")); auto ec = auth.authenticateBasic(base64_encode("badaix:secret"));
REQUIRE(!ec); REQUIRE(!ec);
REQUIRE(auth.hasAuthInfo()); REQUIRE(auth.isAuthenticated());
REQUIRE(auth.hasPermission("stream")); REQUIRE(auth.hasPermission("stream"));
} }
@ -710,7 +710,7 @@ TEST_CASE("Auth")
AuthInfo auth(settings); AuthInfo auth(settings);
auto ec = auth.authenticateBasic(base64_encode("badaix:secret")); auto ec = auth.authenticateBasic(base64_encode("badaix:secret"));
REQUIRE(!ec); REQUIRE(!ec);
REQUIRE(auth.hasAuthInfo()); REQUIRE(auth.isAuthenticated());
REQUIRE(!auth.hasPermission("stream")); REQUIRE(!auth.hasPermission("stream"));
} }
@ -722,12 +722,12 @@ TEST_CASE("Auth")
AuthInfo auth(settings); AuthInfo auth(settings);
auto ec = auth.authenticateBasic(base64_encode("badaix:wrong_password")); auto ec = auth.authenticateBasic(base64_encode("badaix:wrong_password"));
REQUIRE(ec == AuthErrc::wrong_password); REQUIRE(ec == AuthErrc::wrong_password);
REQUIRE(!auth.hasAuthInfo()); REQUIRE(!auth.isAuthenticated());
REQUIRE(!auth.hasPermission("stream")); REQUIRE(!auth.hasPermission("stream"));
ec = auth.authenticateBasic(base64_encode("unknown_user:secret")); ec = auth.authenticateBasic(base64_encode("unknown_user:secret"));
REQUIRE(ec == AuthErrc::unknown_user); REQUIRE(ec == AuthErrc::unknown_user);
REQUIRE(!auth.hasAuthInfo()); REQUIRE(!auth.isAuthenticated());
REQUIRE(!auth.hasPermission("stream")); REQUIRE(!auth.hasPermission("stream"));
} }
} }