Changed coding style
This commit is contained in:
@@ -22,29 +22,28 @@
|
||||
namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<API::Subsonic::ProtocolVersion>
|
||||
readAs(std::string_view str)
|
||||
std::optional<API::Subsonic::ProtocolVersion> readAs(std::string_view str)
|
||||
{
|
||||
// Expects "X.Y.Z"
|
||||
const auto numbers {StringUtils::splitString(str, ".")};
|
||||
const auto numbers{ StringUtils::splitString(str, ".") };
|
||||
if (numbers.size() < 2 || numbers.size() > 3)
|
||||
return std::nullopt;
|
||||
|
||||
API::Subsonic::ProtocolVersion version;
|
||||
|
||||
auto number {StringUtils::readAs<unsigned>(numbers[0])};
|
||||
auto number{ StringUtils::readAs<unsigned>(numbers[0]) };
|
||||
if (!number)
|
||||
return std::nullopt;
|
||||
version.major = *number;
|
||||
|
||||
number = {StringUtils::readAs<unsigned>(numbers[1])};
|
||||
number = { StringUtils::readAs<unsigned>(numbers[1]) };
|
||||
if (!number)
|
||||
return std::nullopt;
|
||||
version.minor = *number;
|
||||
|
||||
if (numbers.size() == 3)
|
||||
{
|
||||
number = {StringUtils::readAs<unsigned>(numbers[2])};
|
||||
number = { StringUtils::readAs<unsigned>(numbers[2]) };
|
||||
if (!number)
|
||||
return std::nullopt;
|
||||
version.patch = *number;
|
||||
|
||||
@@ -25,16 +25,17 @@ namespace API::Subsonic
|
||||
{
|
||||
struct ProtocolVersion
|
||||
{
|
||||
unsigned major {};
|
||||
unsigned minor {};
|
||||
unsigned patch {};
|
||||
unsigned major{};
|
||||
unsigned minor{};
|
||||
unsigned patch{};
|
||||
};
|
||||
|
||||
static inline constexpr ProtocolVersion defaultServerProtocolVersion {1, 16, 0};
|
||||
static inline constexpr ProtocolVersion defaultServerProtocolVersion{ 1, 16, 0 };
|
||||
}
|
||||
|
||||
namespace StringUtils
|
||||
{
|
||||
template<> std::optional<API::Subsonic::ProtocolVersion> readAs(std::string_view str);
|
||||
template<>
|
||||
std::optional<API::Subsonic::ProtocolVersion> readAs(std::string_view str);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,32 +26,27 @@
|
||||
|
||||
namespace API::Subsonic
|
||||
{
|
||||
std::string
|
||||
idToString(Database::ArtistId id)
|
||||
std::string idToString(Database::ArtistId id)
|
||||
{
|
||||
return "ar-" + id.toString();
|
||||
}
|
||||
|
||||
std::string
|
||||
idToString(Database::ReleaseId id)
|
||||
std::string idToString(Database::ReleaseId id)
|
||||
{
|
||||
return "al-" + id.toString();
|
||||
}
|
||||
|
||||
std::string
|
||||
idToString(RootId)
|
||||
std::string idToString(RootId)
|
||||
{
|
||||
return "root";
|
||||
}
|
||||
|
||||
std::string
|
||||
idToString(Database::TrackId id)
|
||||
std::string idToString(Database::TrackId id)
|
||||
{
|
||||
return "tr-" + id.toString();
|
||||
}
|
||||
|
||||
std::string
|
||||
idToString(Database::TrackListId id)
|
||||
std::string idToString(Database::TrackListId id)
|
||||
{
|
||||
return "pl-" + id.toString();
|
||||
}
|
||||
@@ -60,79 +55,74 @@ namespace API::Subsonic
|
||||
namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<Database::ArtistId>
|
||||
readAs(std::string_view str)
|
||||
std::optional<Database::ArtistId> readAs(std::string_view str)
|
||||
{
|
||||
std::vector<std::string_view> values {StringUtils::splitString(str, "-")};
|
||||
std::vector<std::string_view> values{ StringUtils::splitString(str, "-") };
|
||||
if (values.size() != 2)
|
||||
return std::nullopt;
|
||||
|
||||
if (values[0] != "ar")
|
||||
return std::nullopt;
|
||||
|
||||
if (const auto value {StringUtils::readAs<Database::ArtistId::ValueType>(values[1])})
|
||||
return Database::ArtistId {*value};
|
||||
if (const auto value{ StringUtils::readAs<Database::ArtistId::ValueType>(values[1]) })
|
||||
return Database::ArtistId{ *value };
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::optional<Database::ReleaseId>
|
||||
readAs(std::string_view str)
|
||||
std::optional<Database::ReleaseId> readAs(std::string_view str)
|
||||
{
|
||||
std::vector<std::string_view> values {StringUtils::splitString(str, "-")};
|
||||
std::vector<std::string_view> values{ StringUtils::splitString(str, "-") };
|
||||
if (values.size() != 2)
|
||||
return std::nullopt;
|
||||
|
||||
if (values[0] != "al")
|
||||
return std::nullopt;
|
||||
|
||||
if (const auto value {StringUtils::readAs<Database::ReleaseId::ValueType>(values[1])})
|
||||
return Database::ReleaseId {*value};
|
||||
if (const auto value{ StringUtils::readAs<Database::ReleaseId::ValueType>(values[1]) })
|
||||
return Database::ReleaseId{ *value };
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::optional<API::Subsonic::RootId>
|
||||
readAs(std::string_view str)
|
||||
std::optional<API::Subsonic::RootId> readAs(std::string_view str)
|
||||
{
|
||||
if (str == "root")
|
||||
return API::Subsonic::RootId {};
|
||||
return API::Subsonic::RootId{};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::optional<Database::TrackId>
|
||||
readAs(std::string_view str)
|
||||
std::optional<Database::TrackId> readAs(std::string_view str)
|
||||
{
|
||||
std::vector<std::string_view> values {StringUtils::splitString(str, "-")};
|
||||
std::vector<std::string_view> values{ StringUtils::splitString(str, "-") };
|
||||
if (values.size() != 2)
|
||||
return std::nullopt;
|
||||
|
||||
if (values[0] != "tr")
|
||||
return std::nullopt;
|
||||
|
||||
if (const auto value {StringUtils::readAs<Database::TrackId::ValueType>(values[1])})
|
||||
return Database::TrackId {*value};
|
||||
if (const auto value{ StringUtils::readAs<Database::TrackId::ValueType>(values[1]) })
|
||||
return Database::TrackId{ *value };
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<>
|
||||
std::optional<Database::TrackListId>
|
||||
readAs(std::string_view str)
|
||||
std::optional<Database::TrackListId> readAs(std::string_view str)
|
||||
{
|
||||
std::vector<std::string_view> values {StringUtils::splitString(str, "-")};
|
||||
std::vector<std::string_view> values{ StringUtils::splitString(str, "-") };
|
||||
if (values.size() != 2)
|
||||
return std::nullopt;
|
||||
|
||||
if (values[0] != "pl")
|
||||
return std::nullopt;
|
||||
|
||||
if (const auto value {StringUtils::readAs<Database::TrackListId::ValueType>(values[1])})
|
||||
return Database::TrackListId {*value};
|
||||
if (const auto value{ StringUtils::readAs<Database::TrackListId::ValueType>(values[1]) })
|
||||
return Database::TrackListId{ *value };
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
@@ -40,23 +40,18 @@ namespace API::Subsonic
|
||||
namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<API::Subsonic::RootId>
|
||||
readAs(std::string_view str);
|
||||
std::optional<API::Subsonic::RootId> readAs(std::string_view str);
|
||||
|
||||
template<>
|
||||
std::optional<Database::ArtistId>
|
||||
readAs(std::string_view str);
|
||||
std::optional<Database::ArtistId> readAs(std::string_view str);
|
||||
|
||||
template<>
|
||||
std::optional<Database::ReleaseId>
|
||||
readAs(std::string_view str);
|
||||
std::optional<Database::ReleaseId> readAs(std::string_view str);
|
||||
|
||||
template<>
|
||||
std::optional<Database::TrackId>
|
||||
readAs(std::string_view str);
|
||||
std::optional<Database::TrackId> readAs(std::string_view str);
|
||||
|
||||
template<>
|
||||
std::optional<Database::TrackListId>
|
||||
readAs(std::string_view str);
|
||||
std::optional<Database::TrackListId> readAs(std::string_view str);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,37 +54,29 @@ using namespace Database;
|
||||
|
||||
namespace API::Subsonic
|
||||
{
|
||||
|
||||
std::unique_ptr<Wt::WResource>
|
||||
createSubsonicResource(Database::Db& db)
|
||||
{
|
||||
std::unique_ptr<Wt::WResource> createSubsonicResource(Database::Db& db)
|
||||
{
|
||||
return std::make_unique<SubsonicResource>(db);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
std::unordered_map<std::string, ProtocolVersion>
|
||||
readConfigProtocolVersions()
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, ProtocolVersion> readConfigProtocolVersions()
|
||||
{
|
||||
std::unordered_map<std::string, ProtocolVersion> res;
|
||||
|
||||
Service<IConfig>::get()->visitStrings("api-subsonic-report-old-server-protocol",
|
||||
[&](std::string_view client)
|
||||
{
|
||||
res.emplace(std::string {client}, ProtocolVersion {1, 12, 0});
|
||||
}, {"DSub"});
|
||||
res.emplace(std::string{ client }, ProtocolVersion{ 1, 12, 0 });
|
||||
}, { "DSub" });
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
SubsonicResource::SubsonicResource(Db& db)
|
||||
: _serverProtocolVersionsByClient {readConfigProtocolVersions()}
|
||||
, _db {db}
|
||||
{
|
||||
}
|
||||
|
||||
static
|
||||
std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap)
|
||||
{
|
||||
std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap)
|
||||
{
|
||||
auto censorValue = [](const std::string& type, const std::string& value) -> std::string
|
||||
{
|
||||
if (type == "p" || type == "password")
|
||||
@@ -113,61 +105,54 @@ std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
checkUserTypeIsAllowed(RequestContext& context, EnumSet<Database::UserType> allowedUserTypes)
|
||||
{
|
||||
auto transaction {context.dbSession.createSharedTransaction()};
|
||||
void checkUserTypeIsAllowed(RequestContext& context, EnumSet<Database::UserType> allowedUserTypes)
|
||||
{
|
||||
auto transaction{ context.dbSession.createSharedTransaction() };
|
||||
|
||||
User::pointer currentUser {User::find(context.dbSession, context.userId)};
|
||||
User::pointer currentUser{ User::find(context.dbSession, context.userId) };
|
||||
if (!currentUser)
|
||||
throw RequestedDataNotFoundError {};
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
if (!allowedUserTypes.contains(currentUser->getType()))
|
||||
throw UserNotAuthorizedError {};
|
||||
}
|
||||
throw UserNotAuthorizedError{};
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
handlePingRequest(RequestContext& context)
|
||||
{
|
||||
Response handlePingRequest(RequestContext& context)
|
||||
{
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
handleGetLicenseRequest(RequestContext& context)
|
||||
{
|
||||
Response response {Response::createOkResponse(context.serverProtocolVersion)};
|
||||
Response handleGetLicenseRequest(RequestContext& context)
|
||||
{
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
|
||||
Response::Node& licenseNode {response.createNode("license")};
|
||||
Response::Node& licenseNode{ response.createNode("license") };
|
||||
licenseNode.setAttribute("licenseExpires", "2025-09-03T14:46:43");
|
||||
licenseNode.setAttribute("email", "foo@bar.com");
|
||||
licenseNode.setAttribute("valid", true);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
handleNotImplemented(RequestContext&)
|
||||
{
|
||||
throw NotImplementedGenericError {};
|
||||
}
|
||||
Response handleNotImplemented(RequestContext&)
|
||||
{
|
||||
throw NotImplementedGenericError{};
|
||||
}
|
||||
|
||||
using RequestHandlerFunc = std::function<Response(RequestContext& context)>;
|
||||
using CheckImplementedFunc = std::function<void()>;
|
||||
struct RequestEntryPointInfo
|
||||
{
|
||||
|
||||
using RequestHandlerFunc = std::function<Response(RequestContext& context)>;
|
||||
using CheckImplementedFunc = std::function<void()>;
|
||||
struct RequestEntryPointInfo
|
||||
{
|
||||
RequestHandlerFunc func;
|
||||
EnumSet<UserType> allowedUserTypes {UserType::DEMO, UserType::REGULAR, UserType::ADMIN};
|
||||
CheckImplementedFunc checkFunc {};
|
||||
};
|
||||
EnumSet<UserType> allowedUserTypes{ UserType::DEMO, UserType::REGULAR, UserType::ADMIN };
|
||||
CheckImplementedFunc checkFunc{};
|
||||
};
|
||||
|
||||
static const std::unordered_map<std::string_view, RequestEntryPointInfo> requestEntryPoints
|
||||
{
|
||||
static const std::unordered_map<std::string_view, RequestEntryPointInfo> requestEntryPoints
|
||||
{
|
||||
// System
|
||||
{"/ping", {handlePingRequest}},
|
||||
{"/getLicense", {handleGetLicenseRequest}},
|
||||
@@ -269,42 +254,49 @@ static const std::unordered_map<std::string_view, RequestEntryPointInfo> request
|
||||
// Media library scanning
|
||||
{"/getScanStatus", {Scan::handleGetScanStatus, {UserType::ADMIN}}},
|
||||
{"/startScan", {Scan::handleStartScan, {UserType::ADMIN}}},
|
||||
};
|
||||
};
|
||||
|
||||
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
|
||||
static std::unordered_map<std::string, MediaRetrievalHandlerFunc> mediaRetrievalHandlers
|
||||
{
|
||||
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
|
||||
static std::unordered_map<std::string, MediaRetrievalHandlerFunc> mediaRetrievalHandlers
|
||||
{
|
||||
// Media retrieval
|
||||
{"/download", handleDownload},
|
||||
{"/stream", handleStream},
|
||||
{"/getCoverArt", handleGetCoverArt},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response)
|
||||
{
|
||||
static std::atomic<std::size_t> curRequestId {};
|
||||
|
||||
const std::size_t requestId {curRequestId++};
|
||||
SubsonicResource::SubsonicResource(Db& db)
|
||||
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
|
||||
, _db{ db }
|
||||
{
|
||||
}
|
||||
|
||||
void SubsonicResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
|
||||
{
|
||||
static std::atomic<std::size_t> curRequestId{};
|
||||
|
||||
const std::size_t requestId{ curRequestId++ };
|
||||
|
||||
LMS_LOG(API_SUBSONIC, DEBUG) << "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap());
|
||||
|
||||
std::string requestPath {request.pathInfo()};
|
||||
std::string requestPath{ request.pathInfo() };
|
||||
if (StringUtils::stringEndsWith(requestPath, ".view"))
|
||||
requestPath.resize(requestPath.length() - 5);
|
||||
|
||||
// Optional parameters
|
||||
const ResponseFormat format {getParameterAs<std::string>(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml};
|
||||
const ResponseFormat format{ getParameterAs<std::string>(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml };
|
||||
|
||||
ProtocolVersion protocolVersion {defaultServerProtocolVersion};
|
||||
ProtocolVersion protocolVersion{ defaultServerProtocolVersion };
|
||||
|
||||
try
|
||||
{
|
||||
// We need to parse client a soon as possible to make sure to answer with the right protocol version
|
||||
protocolVersion = getServerProtocolVersion(getMandatoryParameterAs<std::string>(request.getParameterMap(), "c"));
|
||||
RequestContext requestContext {buildRequestContext(request)};
|
||||
RequestContext requestContext{ buildRequestContext(request) };
|
||||
|
||||
auto itEntryPoint {requestEntryPoints.find(requestPath)};
|
||||
auto itEntryPoint{ requestEntryPoints.find(requestPath) };
|
||||
if (itEntryPoint != requestEntryPoints.end())
|
||||
{
|
||||
if (itEntryPoint->second.checkFunc)
|
||||
@@ -312,7 +304,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
|
||||
|
||||
checkUserTypeIsAllowed(requestContext, itEntryPoint->second.allowedUserTypes);
|
||||
|
||||
Response resp {(itEntryPoint->second.func)(requestContext)};
|
||||
Response resp{ (itEntryPoint->second.func)(requestContext) };
|
||||
|
||||
resp.write(response.out(), format);
|
||||
response.setMimeType(ResponseFormatToMimeType(format));
|
||||
@@ -321,7 +313,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
|
||||
return;
|
||||
}
|
||||
|
||||
auto itStreamHandler {mediaRetrievalHandlers.find(requestPath)};
|
||||
auto itStreamHandler{ mediaRetrievalHandlers.find(requestPath) };
|
||||
if (itStreamHandler != mediaRetrievalHandlers.end())
|
||||
{
|
||||
itStreamHandler->second(requestContext, request, response);
|
||||
@@ -330,52 +322,49 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
|
||||
}
|
||||
|
||||
LMS_LOG(API_SUBSONIC, ERROR) << "Unhandled command '" << requestPath << "'";
|
||||
throw UnknownEntryPointGenericError {};
|
||||
throw UnknownEntryPointGenericError{};
|
||||
}
|
||||
catch (const Error& e)
|
||||
{
|
||||
LMS_LOG(API_SUBSONIC, ERROR) << "Error while processing request '" << requestPath << "'"
|
||||
<< ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]"
|
||||
<< ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'";
|
||||
Response resp {Response::createFailedResponse(protocolVersion, e)};
|
||||
Response resp{ Response::createFailedResponse(protocolVersion, e) };
|
||||
resp.write(response.out(), format);
|
||||
response.setMimeType(ResponseFormatToMimeType(format));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProtocolVersion
|
||||
SubsonicResource::getServerProtocolVersion(const std::string& clientName) const
|
||||
{
|
||||
auto it {_serverProtocolVersionsByClient.find(clientName)};
|
||||
ProtocolVersion SubsonicResource::getServerProtocolVersion(const std::string& clientName) const
|
||||
{
|
||||
auto it{ _serverProtocolVersionsByClient.find(clientName) };
|
||||
if (it == std::cend(_serverProtocolVersionsByClient))
|
||||
return defaultServerProtocolVersion;
|
||||
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SubsonicResource::checkProtocolVersion(ProtocolVersion client, ProtocolVersion server)
|
||||
{
|
||||
void SubsonicResource::checkProtocolVersion(ProtocolVersion client, ProtocolVersion server)
|
||||
{
|
||||
if (client.major > server.major)
|
||||
throw ServerMustUpgradeError {};
|
||||
throw ServerMustUpgradeError{};
|
||||
if (client.major < server.major)
|
||||
throw ClientMustUpgradeError {};
|
||||
throw ClientMustUpgradeError{};
|
||||
if (client.minor > server.minor)
|
||||
throw ServerMustUpgradeError {};
|
||||
throw ServerMustUpgradeError{};
|
||||
else if (client.minor == server.minor)
|
||||
{
|
||||
if (client.patch > server.patch)
|
||||
throw ServerMustUpgradeError {};
|
||||
throw ServerMustUpgradeError{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClientInfo
|
||||
SubsonicResource::getClientInfo(const Wt::Http::ParameterMap& parameters)
|
||||
{
|
||||
ClientInfo SubsonicResource::getClientInfo(const Wt::Http::ParameterMap& parameters)
|
||||
{
|
||||
ClientInfo res;
|
||||
|
||||
if (hasParameter(parameters, "t"))
|
||||
throw TokenAuthenticationNotSupportedForLDAPUsersError {};
|
||||
throw TokenAuthenticationNotSupportedForLDAPUsersError{};
|
||||
|
||||
// Mandatory parameters
|
||||
res.name = getMandatoryParameterAs<std::string>(parameters, "c");
|
||||
@@ -384,33 +373,30 @@ SubsonicResource::getClientInfo(const Wt::Http::ParameterMap& parameters)
|
||||
res.password = decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(parameters, "p"));
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
RequestContext
|
||||
SubsonicResource::buildRequestContext(const Wt::Http::Request& request)
|
||||
{
|
||||
const Wt::Http::ParameterMap& parameters {request.getParameterMap()};
|
||||
const ClientInfo clientInfo {getClientInfo(parameters)};
|
||||
const Database::UserId userId {authenticateUser(request, clientInfo)};
|
||||
|
||||
return {parameters, _db.getTLSSession(), userId, clientInfo, getServerProtocolVersion(clientInfo.name)};
|
||||
}
|
||||
|
||||
Database::UserId
|
||||
SubsonicResource::authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo)
|
||||
{
|
||||
if (auto *authEnvService {Service<::Auth::IEnvService>::get()})
|
||||
RequestContext SubsonicResource::buildRequestContext(const Wt::Http::Request& request)
|
||||
{
|
||||
const auto checkResult {authEnvService->processRequest(request)};
|
||||
const Wt::Http::ParameterMap& parameters{ request.getParameterMap() };
|
||||
const ClientInfo clientInfo{ getClientInfo(parameters) };
|
||||
const Database::UserId userId{ authenticateUser(request, clientInfo) };
|
||||
|
||||
return { parameters, _db.getTLSSession(), userId, clientInfo, getServerProtocolVersion(clientInfo.name) };
|
||||
}
|
||||
|
||||
Database::UserId SubsonicResource::authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo)
|
||||
{
|
||||
if (auto * authEnvService{ Service<::Auth::IEnvService>::get() })
|
||||
{
|
||||
const auto checkResult{ authEnvService->processRequest(request) };
|
||||
if (checkResult.state != ::Auth::IEnvService::CheckResult::State::Granted)
|
||||
throw UserNotAuthorizedError {};
|
||||
throw UserNotAuthorizedError{};
|
||||
|
||||
return *checkResult.userId;
|
||||
}
|
||||
else if (auto *authPasswordService {Service<::Auth::IPasswordService>::get()})
|
||||
else if (auto * authPasswordService{ Service<::Auth::IPasswordService>::get() })
|
||||
{
|
||||
const auto checkResult {authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()),
|
||||
clientInfo.user, clientInfo.password)};
|
||||
const auto checkResult{ authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()), clientInfo.user, clientInfo.password) };
|
||||
|
||||
switch (checkResult.state)
|
||||
{
|
||||
@@ -418,14 +404,14 @@ SubsonicResource::authenticateUser(const Wt::Http::Request& request, const Clien
|
||||
return *checkResult.userId;
|
||||
break;
|
||||
case Auth::IPasswordService::CheckResult::State::Denied:
|
||||
throw WrongUsernameOrPasswordError {};
|
||||
throw WrongUsernameOrPasswordError{};
|
||||
case Auth::IPasswordService::CheckResult::State::Throttled:
|
||||
throw LoginThrottledGenericError {};
|
||||
throw LoginThrottledGenericError{};
|
||||
}
|
||||
}
|
||||
|
||||
throw InternalErrorGenericError {"No service avalaible to authenticate user"};
|
||||
}
|
||||
throw InternalErrorGenericError{ "No service avalaible to authenticate user" };
|
||||
}
|
||||
|
||||
} // namespace api::subsonic
|
||||
|
||||
|
||||
@@ -34,9 +34,8 @@
|
||||
namespace API::Subsonic
|
||||
{
|
||||
|
||||
std::string
|
||||
ResponseFormatToMimeType(ResponseFormat format)
|
||||
{
|
||||
std::string ResponseFormatToMimeType(ResponseFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ResponseFormat::xml: return "text/xml";
|
||||
@@ -44,122 +43,108 @@ ResponseFormatToMimeType(ResponseFormat format)
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Response::Node::setValue(std::string_view value)
|
||||
{
|
||||
void Response::Node::setValue(std::string_view value)
|
||||
{
|
||||
if (!_children.empty() || !_childrenArrays.empty())
|
||||
throw LmsException {"Node already has children"};
|
||||
throw LmsException{ "Node already has children" };
|
||||
|
||||
_value = std::string {value};
|
||||
}
|
||||
_value = std::string{ value };
|
||||
}
|
||||
|
||||
void
|
||||
Response::Node::setValue(long long value)
|
||||
{
|
||||
void Response::Node::setValue(long long value)
|
||||
{
|
||||
if (!_children.empty() || !_childrenArrays.empty())
|
||||
throw LmsException {"Node already has children"};
|
||||
throw LmsException{ "Node already has children" };
|
||||
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Response::Node::setAttribute(std::string_view key, std::string_view value)
|
||||
{
|
||||
_attributes[std::string {key}] = std::string {value};
|
||||
}
|
||||
void Response::Node::setAttribute(std::string_view key, std::string_view value)
|
||||
{
|
||||
_attributes[std::string{ key }] = std::string{ value };
|
||||
}
|
||||
|
||||
void
|
||||
Response::Node::addChild(const std::string& key, Node node)
|
||||
{
|
||||
void Response::Node::addChild(const std::string& key, Node node)
|
||||
{
|
||||
if (_value)
|
||||
throw LmsException {"Node already has a value"};
|
||||
throw LmsException{ "Node already has a value" };
|
||||
|
||||
_children[key].emplace_back(std::move(node));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Response::Node::addArrayChild(const std::string& key, Node node)
|
||||
{
|
||||
void Response::Node::addArrayChild(const std::string& key, Node node)
|
||||
{
|
||||
if (_value)
|
||||
throw LmsException {"Node already has a value"};
|
||||
throw LmsException{ "Node already has a value" };
|
||||
|
||||
_childrenArrays[key].emplace_back(std::move(node));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Response::Node&
|
||||
Response::Node::createChild(const std::string& key)
|
||||
{
|
||||
Response::Node& Response::Node::createChild(const std::string& key)
|
||||
{
|
||||
_children[key].emplace_back();
|
||||
return _children[key].back();
|
||||
}
|
||||
}
|
||||
|
||||
Response::Node&
|
||||
Response::Node::createArrayChild(const std::string& key)
|
||||
{
|
||||
Response::Node& Response::Node::createArrayChild(const std::string& key)
|
||||
{
|
||||
_childrenArrays[key].emplace_back();
|
||||
return _childrenArrays[key].back();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Response::Node::setVersionAttribute(ProtocolVersion protocolVersion)
|
||||
{
|
||||
void Response::Node::setVersionAttribute(ProtocolVersion protocolVersion)
|
||||
{
|
||||
setAttribute("version", std::to_string(protocolVersion.major) + "." + std::to_string(protocolVersion.minor) + "." + std::to_string(protocolVersion.patch));
|
||||
}
|
||||
}
|
||||
|
||||
Response
|
||||
Response::createOkResponse(ProtocolVersion protocolVersion)
|
||||
{
|
||||
Response Response::createOkResponse(ProtocolVersion protocolVersion)
|
||||
{
|
||||
Response response;
|
||||
Node& responseNode {response._root.createChild("subsonic-response")};
|
||||
Node& responseNode{ response._root.createChild("subsonic-response") };
|
||||
|
||||
responseNode.setAttribute("status", "ok");
|
||||
responseNode.setVersionAttribute(protocolVersion);
|
||||
responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
Response
|
||||
Response::createFailedResponse(ProtocolVersion protocolVersion, const Error& error)
|
||||
{
|
||||
Response Response::createFailedResponse(ProtocolVersion protocolVersion, const Error& error)
|
||||
{
|
||||
Response response;
|
||||
Node& responseNode {response._root.createChild("subsonic-response")};
|
||||
Node& responseNode{ response._root.createChild("subsonic-response") };
|
||||
|
||||
responseNode.setAttribute("status", "failed");
|
||||
responseNode.setVersionAttribute(protocolVersion);
|
||||
responseNode.setAttribute("type", "lms"); // non standard field to ease client hacks
|
||||
|
||||
Node& errorNode {responseNode.createChild("error")};
|
||||
Node& errorNode{ responseNode.createChild("error") };
|
||||
errorNode.setAttribute("code", static_cast<int>(error.getCode()));
|
||||
errorNode.setAttribute("message", error.getMessage());
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Response::addNode(const std::string& key, Node node)
|
||||
{
|
||||
void Response::addNode(const std::string& key, Node node)
|
||||
{
|
||||
return _root._children["subsonic-response"].front().addChild(key, std::move(node));
|
||||
}
|
||||
}
|
||||
|
||||
Response::Node&
|
||||
Response::createNode(const std::string& key)
|
||||
{
|
||||
Response::Node& Response::createNode(const std::string& key)
|
||||
{
|
||||
return _root._children["subsonic-response"].front().createChild(key);
|
||||
}
|
||||
}
|
||||
|
||||
Response::Node&
|
||||
Response::createArrayNode(const std::string& key)
|
||||
{
|
||||
Response::Node& Response::createArrayNode(const std::string& key)
|
||||
{
|
||||
return _root._children["subsonic-response"].front().createArrayChild(key);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Response::write(std::ostream& os, ResponseFormat format)
|
||||
{
|
||||
void Response::write(std::ostream& os, ResponseFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case ResponseFormat::xml:
|
||||
@@ -169,12 +154,11 @@ Response::write(std::ostream& os, ResponseFormat format)
|
||||
writeJSON(os);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Response::writeXML(std::ostream& os)
|
||||
{
|
||||
std::function<boost::property_tree::ptree(const Response::Node&)> nodeToPropertyTree = [&] (const Response::Node& node)
|
||||
void Response::writeXML(std::ostream& os)
|
||||
{
|
||||
std::function<boost::property_tree::ptree(const Response::Node&)> nodeToPropertyTree = [&](const Response::Node& node)
|
||||
{
|
||||
boost::property_tree::ptree res;
|
||||
|
||||
@@ -190,7 +174,7 @@ Response::writeXML(std::ostream& os)
|
||||
|
||||
if (node._value)
|
||||
{
|
||||
const auto& value {*node._value};
|
||||
const auto& value{ *node._value };
|
||||
|
||||
if (std::holds_alternative<std::string>(value))
|
||||
res.put_value(std::get<std::string>(value));
|
||||
@@ -209,9 +193,9 @@ Response::writeXML(std::ostream& os)
|
||||
|
||||
for (auto itChildArrayNode : node._childrenArrays)
|
||||
{
|
||||
const std::vector<Response::Node>& childArrayNodes {itChildArrayNode.second};
|
||||
const std::vector<Response::Node>& childArrayNodes{ itChildArrayNode.second };
|
||||
|
||||
for (const Response::Node& childNode : childArrayNodes )
|
||||
for (const Response::Node& childNode : childArrayNodes)
|
||||
res.add_child(itChildArrayNode.first, nodeToPropertyTree(childNode));
|
||||
}
|
||||
}
|
||||
@@ -219,20 +203,19 @@ Response::writeXML(std::ostream& os)
|
||||
return res;
|
||||
};
|
||||
|
||||
boost::property_tree::ptree root {nodeToPropertyTree(_root)};
|
||||
boost::property_tree::ptree root{ nodeToPropertyTree(_root) };
|
||||
boost::property_tree::write_xml(os, root);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Response::writeJSON(std::ostream& os)
|
||||
{
|
||||
void Response::writeJSON(std::ostream& os)
|
||||
{
|
||||
namespace Json = Wt::Json;
|
||||
|
||||
std::function<Json::Object(const Response::Node&)> nodeToJsonObject = [&] (const Response::Node& node)
|
||||
std::function<Json::Object(const Response::Node&)> nodeToJsonObject = [&](const Response::Node& node)
|
||||
{
|
||||
Json::Object res;
|
||||
|
||||
auto valueToJsonValue {[](const Node::ValueType& value) -> Json::Value
|
||||
auto valueToJsonValue{ [](const Node::ValueType& value) -> Json::Value
|
||||
{
|
||||
if (std::holds_alternative<std::string>(value))
|
||||
return Json::Value {std::get<std::string>(value)};
|
||||
@@ -242,7 +225,7 @@ Response::writeJSON(std::ostream& os)
|
||||
return Json::Value {std::get<long long>(value)};
|
||||
|
||||
throw LmsException("Unexpected value type");
|
||||
}};
|
||||
} };
|
||||
|
||||
for (auto itAttribute : node._attributes)
|
||||
res[itAttribute.first] = valueToJsonValue(itAttribute.second);
|
||||
@@ -261,10 +244,10 @@ Response::writeJSON(std::ostream& os)
|
||||
|
||||
for (auto itChildArrayNode : node._childrenArrays)
|
||||
{
|
||||
const std::vector<Response::Node>& childArrayNodes {itChildArrayNode .second};
|
||||
const std::vector<Response::Node>& childArrayNodes{ itChildArrayNode.second };
|
||||
|
||||
Json::Array array;
|
||||
for (const Response::Node& childNode : childArrayNodes )
|
||||
for (const Response::Node& childNode : childArrayNodes)
|
||||
array.emplace_back(nodeToJsonObject(childNode));
|
||||
|
||||
res[itChildArrayNode.first] = std::move(array);
|
||||
@@ -274,9 +257,9 @@ Response::writeJSON(std::ostream& os)
|
||||
return res;
|
||||
};
|
||||
|
||||
Json::Object root {nodeToJsonObject(_root)};
|
||||
Json::Object root{ nodeToJsonObject(_root) };
|
||||
os << Json::serialize(root);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -30,16 +30,16 @@
|
||||
namespace API::Subsonic
|
||||
{
|
||||
|
||||
enum class ResponseFormat
|
||||
{
|
||||
enum class ResponseFormat
|
||||
{
|
||||
xml,
|
||||
json,
|
||||
};
|
||||
};
|
||||
|
||||
std::string ResponseFormatToMimeType(ResponseFormat format);
|
||||
std::string ResponseFormatToMimeType(ResponseFormat format);
|
||||
|
||||
class Error
|
||||
{
|
||||
class Error
|
||||
{
|
||||
public:
|
||||
enum class Code
|
||||
{
|
||||
@@ -53,7 +53,7 @@ class Error
|
||||
RequestedDataNotFound = 70,
|
||||
};
|
||||
|
||||
Error(Code code) : _code {code} {}
|
||||
Error(Code code) : _code{ code } {}
|
||||
|
||||
virtual std::string getMessage() const = 0;
|
||||
|
||||
@@ -61,132 +61,132 @@ class Error
|
||||
|
||||
private:
|
||||
const Code _code;
|
||||
};
|
||||
};
|
||||
|
||||
class GenericError : public Error
|
||||
{
|
||||
class GenericError : public Error
|
||||
{
|
||||
public:
|
||||
GenericError() : Error {Code::Generic} {}
|
||||
};
|
||||
GenericError() : Error{ Code::Generic } {}
|
||||
};
|
||||
|
||||
class RequiredParameterMissingError : public Error
|
||||
{
|
||||
class RequiredParameterMissingError : public Error
|
||||
{
|
||||
public:
|
||||
RequiredParameterMissingError(std::string_view param)
|
||||
: Error {Code::RequiredParameterMissing}
|
||||
, _param {param}
|
||||
: Error{ Code::RequiredParameterMissing }
|
||||
, _param{ param }
|
||||
{}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Required parameter '" + _param + "' is missing."; }
|
||||
std::string _param;
|
||||
};
|
||||
};
|
||||
|
||||
class ClientMustUpgradeError : public Error
|
||||
{
|
||||
class ClientMustUpgradeError : public Error
|
||||
{
|
||||
public:
|
||||
ClientMustUpgradeError() : Error {Code::ClientMustUpgrade} {}
|
||||
ClientMustUpgradeError() : Error{ Code::ClientMustUpgrade } {}
|
||||
private:
|
||||
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; }
|
||||
};
|
||||
};
|
||||
|
||||
class ServerMustUpgradeError : public Error
|
||||
{
|
||||
class ServerMustUpgradeError : public Error
|
||||
{
|
||||
public:
|
||||
ServerMustUpgradeError() : Error {Code::ServerMustUpgrade} {}
|
||||
ServerMustUpgradeError() : Error{ Code::ServerMustUpgrade } {}
|
||||
private:
|
||||
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; }
|
||||
};
|
||||
};
|
||||
|
||||
class WrongUsernameOrPasswordError : public Error
|
||||
{
|
||||
class WrongUsernameOrPasswordError : public Error
|
||||
{
|
||||
public:
|
||||
WrongUsernameOrPasswordError() : Error {Code::WrongUsernameOrPassword} {}
|
||||
WrongUsernameOrPasswordError() : Error{ Code::WrongUsernameOrPassword } {}
|
||||
private:
|
||||
std::string getMessage() const override { return "Wrong username or password."; }
|
||||
};
|
||||
};
|
||||
|
||||
class TokenAuthenticationNotSupportedForLDAPUsersError : public Error
|
||||
{
|
||||
class TokenAuthenticationNotSupportedForLDAPUsersError : public Error
|
||||
{
|
||||
public:
|
||||
TokenAuthenticationNotSupportedForLDAPUsersError() : Error {Code::TokenAuthenticationNotSupportedForLDAPUsers} {}
|
||||
TokenAuthenticationNotSupportedForLDAPUsersError() : Error{ Code::TokenAuthenticationNotSupportedForLDAPUsers } {}
|
||||
private:
|
||||
std::string getMessage() const override { return "Token authentication not supported for LDAP users."; }
|
||||
};
|
||||
};
|
||||
|
||||
class UserNotAuthorizedError : public Error
|
||||
{
|
||||
class UserNotAuthorizedError : public Error
|
||||
{
|
||||
public:
|
||||
UserNotAuthorizedError () : Error {Code::UserNotAuthorized} {}
|
||||
UserNotAuthorizedError() : Error{ Code::UserNotAuthorized } {}
|
||||
private:
|
||||
std::string getMessage() const override { return "User is not authorized for the given operation."; }
|
||||
};
|
||||
};
|
||||
|
||||
class RequestedDataNotFoundError : public Error
|
||||
{
|
||||
class RequestedDataNotFoundError : public Error
|
||||
{
|
||||
public:
|
||||
RequestedDataNotFoundError() : Error {Code::RequestedDataNotFound} {}
|
||||
RequestedDataNotFoundError() : Error{ Code::RequestedDataNotFound } {}
|
||||
private:
|
||||
std::string getMessage() const override { return "The requested data was not found."; }
|
||||
};
|
||||
};
|
||||
|
||||
class InternalErrorGenericError : public GenericError
|
||||
{
|
||||
class InternalErrorGenericError : public GenericError
|
||||
{
|
||||
public:
|
||||
InternalErrorGenericError(const std::string& message) : _message {message} {}
|
||||
InternalErrorGenericError(const std::string& message) : _message{ message } {}
|
||||
private:
|
||||
std::string getMessage() const override { return "Internal error: " + _message; }
|
||||
const std::string _message;
|
||||
};
|
||||
};
|
||||
|
||||
class LoginThrottledGenericError : public GenericError
|
||||
{
|
||||
class LoginThrottledGenericError : public GenericError
|
||||
{
|
||||
std::string getMessage() const override { return "Login throttled, too many attempts"; }
|
||||
};
|
||||
};
|
||||
|
||||
class NotImplementedGenericError : public GenericError
|
||||
{
|
||||
class NotImplementedGenericError : public GenericError
|
||||
{
|
||||
std::string getMessage() const override { return "Not implemented"; }
|
||||
};
|
||||
};
|
||||
|
||||
class UnknownEntryPointGenericError : public GenericError
|
||||
{
|
||||
class UnknownEntryPointGenericError : public GenericError
|
||||
{
|
||||
std::string getMessage() const override { return "Unknown API method"; }
|
||||
};
|
||||
};
|
||||
|
||||
class PasswordTooWeakGenericError : public GenericError
|
||||
{
|
||||
class PasswordTooWeakGenericError : public GenericError
|
||||
{
|
||||
std::string getMessage() const override { return "Password too weak"; }
|
||||
};
|
||||
};
|
||||
|
||||
class PasswordMustMatchLoginNameGenericError : public GenericError
|
||||
{
|
||||
class PasswordMustMatchLoginNameGenericError : public GenericError
|
||||
{
|
||||
std::string getMessage() const override { return "Password must match login name"; }
|
||||
};
|
||||
};
|
||||
|
||||
class DemoUserCannotChangePasswordGenericError : public GenericError
|
||||
{
|
||||
class DemoUserCannotChangePasswordGenericError : public GenericError
|
||||
{
|
||||
std::string getMessage() const override { return "Demo user cannot change its password"; }
|
||||
};
|
||||
};
|
||||
|
||||
class UserAlreadyExistsGenericError : public GenericError
|
||||
{
|
||||
class UserAlreadyExistsGenericError : public GenericError
|
||||
{
|
||||
std::string getMessage() const override { return "User already exists"; }
|
||||
};
|
||||
};
|
||||
|
||||
class BadParameterGenericError : public GenericError
|
||||
{
|
||||
class BadParameterGenericError : public GenericError
|
||||
{
|
||||
public:
|
||||
BadParameterGenericError(const std::string& parameterName) : _parameterName {parameterName} {}
|
||||
BadParameterGenericError(const std::string& parameterName) : _parameterName{ parameterName } {}
|
||||
|
||||
private:
|
||||
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; }
|
||||
|
||||
const std::string _parameterName;
|
||||
};
|
||||
};
|
||||
|
||||
class Response
|
||||
{
|
||||
class Response
|
||||
{
|
||||
public:
|
||||
class Node
|
||||
{
|
||||
@@ -197,9 +197,9 @@ class Response
|
||||
void setAttribute(std::string_view key, T value)
|
||||
{
|
||||
if constexpr (std::is_same<bool, T>::value)
|
||||
_attributes[std::string {key}] = value;
|
||||
_attributes[std::string{ key }] = value;
|
||||
else
|
||||
_attributes[std::string {key}] = static_cast<long long>(value);
|
||||
_attributes[std::string{ key }] = static_cast<long long>(value);
|
||||
}
|
||||
|
||||
// A Node has either a value or some children
|
||||
@@ -243,7 +243,7 @@ class Response
|
||||
|
||||
Response() = default;
|
||||
Node _root;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user