mirror of
https://github.com/badaix/snapcast.git
synced 2025-06-03 11:21:41 +02:00
Pass complete Settings struct around
This commit is contained in:
parent
964801896a
commit
c112058998
18 changed files with 584 additions and 140 deletions
|
@ -28,61 +28,227 @@
|
|||
// 3rd party headers
|
||||
|
||||
// standard headers
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
|
||||
|
||||
using namespace std;
|
||||
|
||||
static constexpr auto LOG_TAG = "AuthInfo";
|
||||
|
||||
AuthInfo::AuthInfo(std::string authheader)
|
||||
|
||||
namespace snapcast::error::auth
|
||||
{
|
||||
LOG(INFO, LOG_TAG) << "Authorization: " << authheader << "\n";
|
||||
std::string token(std::move(authheader));
|
||||
static constexpr auto bearer = "bearer"sv;
|
||||
auto pos = utils::string::tolower_copy(token).find(bearer);
|
||||
if (pos != string::npos)
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/// Error category for auth errors
|
||||
struct category : public std::error_category
|
||||
{
|
||||
public:
|
||||
/// @return category name
|
||||
const char* name() const noexcept override;
|
||||
/// @return error message for @p value
|
||||
std::string message(int value) const override;
|
||||
};
|
||||
|
||||
|
||||
const char* category::name() const noexcept
|
||||
{
|
||||
return "auth";
|
||||
}
|
||||
|
||||
std::string category::message(int value) const
|
||||
{
|
||||
switch (static_cast<AuthErrc>(value))
|
||||
{
|
||||
token = token.erase(0, pos + bearer.length());
|
||||
utils::string::trim(token);
|
||||
std::ifstream ifs("certs/snapserver.crt");
|
||||
std::string certificate((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
Jwt jwt;
|
||||
jwt.parse(token, certificate);
|
||||
if (jwt.getExp().has_value())
|
||||
expires_ = jwt.getExp().value();
|
||||
username_ = jwt.getSub().value_or("");
|
||||
LOG(INFO, LOG_TAG) << "Authorization token: " << token << ", user: " << username_ << ", claims: " << jwt.claims.dump() << "\n";
|
||||
}
|
||||
static constexpr auto basic = "basic"sv;
|
||||
pos = utils::string::tolower_copy(token).find(basic);
|
||||
if (pos != string::npos)
|
||||
{
|
||||
token = token.erase(0, pos + basic.length());
|
||||
utils::string::trim(token);
|
||||
username_ = base64_decode(token);
|
||||
std::string password;
|
||||
username_ = utils::string::split_left(username_, ':', password);
|
||||
LOG(INFO, LOG_TAG) << "Authorization basic: " << token << ", user: " << username_ << ", password: " << password << "\n";
|
||||
case AuthErrc::auth_scheme_not_supported:
|
||||
return "Authentication scheme not supported";
|
||||
case AuthErrc::failed_to_create_token:
|
||||
return "Failed to create token";
|
||||
case AuthErrc::unknown_user:
|
||||
return "Unknown user";
|
||||
case AuthErrc::wrong_password:
|
||||
return "Wrong password";
|
||||
case AuthErrc::expired:
|
||||
return "Expired";
|
||||
case AuthErrc::token_validation_failed:
|
||||
return "Token validation failed";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
bool AuthInfo::valid() const
|
||||
const std::error_category& category()
|
||||
{
|
||||
// The category singleton
|
||||
static detail::category instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
} // namespace snapcast::error::auth
|
||||
|
||||
std::error_code make_error_code(AuthErrc errc)
|
||||
{
|
||||
return std::error_code(static_cast<int>(errc), snapcast::error::auth::category());
|
||||
}
|
||||
|
||||
|
||||
AuthInfo::AuthInfo(const ServerSettings& settings) : has_auth_info_(false), settings_(settings)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ErrorCode AuthInfo::validateUser(const std::string& username, const std::optional<std::string>& password) const
|
||||
{
|
||||
auto iter = std::find_if(settings_.users.begin(), settings_.users.end(), [&](const ServerSettings::User& user) { return user.name == username; });
|
||||
if (iter == settings_.users.end())
|
||||
return ErrorCode{AuthErrc::unknown_user};
|
||||
if (password.has_value() && (iter->password != password.value()))
|
||||
return ErrorCode{AuthErrc::wrong_password};
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
ErrorCode AuthInfo::authenticate(const std::string& scheme, const std::string& param)
|
||||
{
|
||||
std::string scheme_normed = utils::string::trim_copy(utils::string::tolower_copy(scheme));
|
||||
std::string param_normed = utils::string::trim_copy(param);
|
||||
if (scheme_normed == "bearer")
|
||||
return authenticateBearer(param_normed);
|
||||
else if (scheme_normed == "basic")
|
||||
return authenticateBasic(param_normed);
|
||||
|
||||
return {AuthErrc::auth_scheme_not_supported, "Scheme must be 'Basic' or 'Bearer'"};
|
||||
}
|
||||
|
||||
|
||||
ErrorCode AuthInfo::authenticate(const std::string& auth)
|
||||
{
|
||||
LOG(INFO, LOG_TAG) << "authenticate: " << auth << "\n";
|
||||
std::string param;
|
||||
std::string scheme = utils::string::split_left(utils::string::trim_copy(auth), ' ', param);
|
||||
return authenticate(scheme, param);
|
||||
}
|
||||
|
||||
|
||||
ErrorCode AuthInfo::authenticateBasic(const std::string& credentials)
|
||||
{
|
||||
has_auth_info_ = 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);
|
||||
return ec;
|
||||
}
|
||||
|
||||
|
||||
ErrorCode AuthInfo::authenticateBearer(const std::string& token)
|
||||
{
|
||||
has_auth_info_ = false;
|
||||
std::ifstream ifs(settings_.ssl.certificate);
|
||||
std::string certificate((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
Jwt jwt;
|
||||
if (!jwt.parse(token, certificate))
|
||||
return {AuthErrc::token_validation_failed};
|
||||
if (jwt.getExp().has_value())
|
||||
expires_ = jwt.getExp().value();
|
||||
username_ = jwt.getSub().value_or("");
|
||||
|
||||
LOG(INFO, LOG_TAG) << "Authorization token: " << token << ", user: " << username_ << ", claims: " << jwt.claims.dump() << "\n";
|
||||
|
||||
if (auto ec = validateUser(username_); ec)
|
||||
return ec;
|
||||
|
||||
if (isExpired())
|
||||
return {AuthErrc::expired};
|
||||
|
||||
has_auth_info_ = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
ErrorOr<std::string> AuthInfo::getToken(const std::string& username, const std::string& password) const
|
||||
{
|
||||
ErrorCode ec = validateUser(username, password);
|
||||
if (ec)
|
||||
return ec;
|
||||
|
||||
Jwt jwt;
|
||||
auto now = std::chrono::system_clock::now();
|
||||
jwt.setIat(now);
|
||||
jwt.setExp(now + 10h);
|
||||
jwt.setSub(username);
|
||||
std::ifstream ifs(settings_.ssl.private_key);
|
||||
std::string private_key((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||
if (!ifs.good())
|
||||
return ErrorCode{std::make_error_code(std::errc::io_error), "Failed to read private key file"};
|
||||
// TODO tls: eroor handling
|
||||
std::optional<std::string> token = jwt.getToken(private_key);
|
||||
if (!token.has_value())
|
||||
return ErrorCode{AuthErrc::failed_to_create_token};
|
||||
return token.value();
|
||||
}
|
||||
|
||||
|
||||
bool AuthInfo::isExpired() const
|
||||
{
|
||||
if (expires_.has_value())
|
||||
{
|
||||
LOG(INFO, LOG_TAG) << "Expires in " << std::chrono::duration_cast<std::chrono::seconds>(expires_.value() - std::chrono::system_clock::now()).count()
|
||||
<< " sec\n";
|
||||
return expires_ > std::chrono::system_clock::now();
|
||||
if (std::chrono::system_clock::now() > expires_.value())
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool AuthInfo::hasAuthInfo() const
|
||||
{
|
||||
return has_auth_info_;
|
||||
}
|
||||
|
||||
|
||||
// ErrorCode AuthInfo::isValid(const std::string& command) const
|
||||
// {
|
||||
// std::ignore = command;
|
||||
// if (isExpired())
|
||||
// return {AuthErrc::expired};
|
||||
|
||||
// return {};
|
||||
// }
|
||||
|
||||
const std::string& AuthInfo::username() const
|
||||
{
|
||||
return username_;
|
||||
}
|
||||
|
||||
|
||||
bool AuthInfo::hasPermission(const std::string& resource) const
|
||||
{
|
||||
if (!hasAuthInfo())
|
||||
return false;
|
||||
|
||||
auto iter = std::find_if(settings_.users.begin(), settings_.users.end(), [&](const ServerSettings::User& user) { return user.name == username_; });
|
||||
if (iter == settings_.users.end())
|
||||
return false;
|
||||
|
||||
auto perm_iter = std::find_if(iter->permissions.begin(), iter->permissions.end(),
|
||||
[&](const std::string& permission) { return utils::string::wildcardMatch(permission, resource); });
|
||||
if (perm_iter != iter->permissions.end())
|
||||
{
|
||||
LOG(DEBUG, LOG_TAG) << "Found permission for ressource '" << resource << "': '" << *perm_iter << "'\n";
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue