mirror of
https://github.com/badaix/snapcast.git
synced 2025-06-08 13:51: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)
|
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_; });
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(); });
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue