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)
{
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<char>(ifs)), std::istreambuf_iterator<char>());
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_; });

View file

@ -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

View file

@ -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))
{
}

View file

@ -28,9 +28,11 @@
// 3rd party headers
// standard headers
#include <algorithm>
#include <filesystem>
#include <memory>
#include <tuple>
#include <vector>
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<ServerAuthenticateRequest>(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);
}
#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
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<std::shared_ptr<Request>> requests);
private:
std::vector<std::shared_ptr<Request>> requests_;
};
#if 0
/// "Server.GetToken" request
class ServerGetTokenRequest : public Request

View file

@ -145,7 +145,7 @@ void Server::processRequest(const jsonrpcpp::request_ptr& request, AuthInfo& aut
else
{
std::optional<jsonrpcpp::RequestException> 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> 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";

View file

@ -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"));
}
}