mirror of
https://github.com/badaix/snapcast.git
synced 2025-06-07 05:11:43 +02:00
Add "General.GetRPCCommands" RPC command
This commit is contained in:
parent
6b94392f2c
commit
f264080c77
7 changed files with 106 additions and 20 deletions
|
@ -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_; });
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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(); });
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue