Subsonic API: better error reporting

This commit is contained in:
emeric
2019-08-13 13:58:59 +02:00
parent add80a9f6c
commit 178c400067
4 changed files with 202 additions and 177 deletions
+7 -14
View File
@@ -19,6 +19,8 @@
#include "SubsonicId.hpp"
#include "SubsonicResponse.hpp"
#include "utils/Logger.hpp"
#include "utils/Utils.hpp"
@@ -29,18 +31,15 @@ boost::optional<Id>
IdFromString(const std::string& id)
{
if (id == "root")
return Id{Id::Type::Root};
return Id {Id::Type::Root};
std::vector<std::string> values {splitString(id, "-")};
if (values.size() != 2)
{
LMS_LOG(API_SUBSONIC, ERROR) << "Bad id format";
return {};
}
return boost::none;
Id res;
std::string type {std::move(values[0])};
const std::string type {std::move(values[0])};
if (type == "ar")
res.type = Id::Type::Artist;
else if (type == "al")
@@ -50,17 +49,11 @@ IdFromString(const std::string& id)
else if (type == "pl")
res.type = Id::Type::Playlist;
else
{
LMS_LOG(API_SUBSONIC, ERROR) << "Bad id format";
return {};
}
return boost::none;
auto optId {readAs<Database::IdType>(values[1])};
if (!optId)
{
LMS_LOG(API_SUBSONIC, ERROR) << "Bad id format";
return {};
}
return boost::none;
res.value = *optId;
+87 -86
View File
@@ -187,14 +187,11 @@ getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::stri
for (const std::string& param : it->second)
{
auto val {readAs<T>(param)};
if (!val)
{
res.clear();
return res;
}
auto value {readAs<T>(param)};
if (!value)
throw BadParameterFormatGenericError {paramName};
res.emplace_back(std::move(*val));
res.emplace_back(std::move(*value));
}
return res;
@@ -206,7 +203,7 @@ getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const
{
std::vector<T> res {getMultiParametersAs<T>(parameterMap, param)};
if (res.empty())
throw Error {Error::Code::RequiredParameterMissing};
throw RequiredParameterMissingError {};
return res;
}
@@ -229,7 +226,7 @@ getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::s
{
auto res {getParameterAs<T>(parameterMap, param)};
if (!res)
throw Error {Error::Code::RequiredParameterMissing};
throw RequiredParameterMissingError {};
return *res;
}
@@ -242,7 +239,7 @@ decodePasswordIfNeeded(const std::string& password)
{
auto decodedPassword {stringFromHex(password.substr(4))};
if (!decodedPassword)
throw Error {Error::CustomType::BadPasswordFormat};
return password; // fallback on plain password
return *decodedPassword;
}
@@ -259,11 +256,11 @@ getClientInfo(const Wt::Http::ParameterMap& parameters)
// Mandatory parameters
res.version = getMandatoryParameterAs<ClientVersion>(parameters, "v");
if (res.version.major > API_VERSION_MAJOR)
throw Error {Error::Code::ServerMustUpgrade};
throw ServerMustUpgradeError {};
if (res.version.major < API_VERSION_MAJOR)
throw Error {Error::Code::ClientMustUpgrade};
throw ClientMustUpgradeError {};
if (res.version.minor > API_VERSION_MINOR)
throw Error {Error::Code::ServerMustUpgrade};
throw ServerMustUpgradeError {};
res.name = getMandatoryParameterAs<std::string>(parameters, "c");
res.user = getMandatoryParameterAs<std::string>(parameters, "u");
@@ -332,10 +329,10 @@ checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username)
{
User::pointer currentUser {User::getByLoginName(context.dbSession, context.userName)};
if (!currentUser)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
if (!currentUser->isAdmin())
throw Error {Error::Code::UserNotAuthorized};
throw UserNotAuthorizedError {};
}
}
@@ -587,7 +584,7 @@ handleChangePassword(RequestContext& context)
std::string password {decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(context.parameters, "password"))};
if (!getService<Auth::PasswordService>()->evaluatePasswordStrength(username, password))
throw Error {Error::CustomType::PasswordTooWeak};
throw PasswordTooWeakGenericError {};
const User::PasswordHash hash {getService<Auth::PasswordService>()->hashPassword(password)};
@@ -597,7 +594,7 @@ handleChangePassword(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, username)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
user.modify()->setPasswordHash(hash);
user.modify()->clearAuthTokens();
@@ -612,22 +609,22 @@ handleCreatePlaylistRequest(RequestContext& context)
// Optional params
auto id {getParameterAs<Id>(context.parameters, "playlistId")};
if (id && id->type != Id::Type::Playlist)
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"playlistId"};
auto name {getParameterAs<std::string>(context.parameters, "name")};
std::vector<Id> trackIds {getMultiParametersAs<Id>(context.parameters, "songId")};
if (!std::all_of(std::cbegin(trackIds), std::cend(trackIds ), [](const Id& id) { return id.type == Id::Type::Track; }))
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"songId"};
if (!name && !id)
throw Error {Error::Code::RequiredParameterMissing};
throw RequiredParameterMissingError {};
auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
TrackList::pointer tracklist;
if (id)
@@ -637,7 +634,7 @@ handleCreatePlaylistRequest(RequestContext& context)
|| tracklist->getUser() != user
|| tracklist->getType() != TrackList::Type::Playlist)
{
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
}
if (name)
@@ -669,14 +666,14 @@ handleCreateUserRequest(RequestContext& context)
// Just ignore all the other fields as we don't handle them
if (!getService<Auth::PasswordService>()->evaluatePasswordStrength(username, password))
throw Error {Error::CustomType::PasswordTooWeak};
throw PasswordTooWeakGenericError {};
const User::PasswordHash hash {getService<Auth::PasswordService>()->hashPassword(password)};
auto transaction {context.dbSession.createUniqueTransaction()};
if (User::getByLoginName(context.dbSession, username) != User::pointer{})
throw Error {Error::CustomType::UserAlreadyExists};
throw UserAlreadyExistsGenericError {};
User::pointer user {User::create(context.dbSession, username, hash)};
user.modify()->setMaxAudioTranscodeBitrate(128000);
@@ -690,20 +687,20 @@ handleDeletePlaylistRequest(RequestContext& context)
{
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Playlist)
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"id"};
auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
TrackList::pointer tracklist {TrackList::getById(context.dbSession, id.value)};
if (!tracklist
|| tracklist->getUser() != user
|| tracklist->getType() != TrackList::Type::Playlist)
{
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
}
tracklist.remove();
@@ -717,14 +714,15 @@ handleDeleteUserRequest(RequestContext& context)
{
std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")};
// cannot delete ourself
if (username == context.userName)
throw Error {Error::Code::UserNotAuthorized};
throw UserNotAuthorizedError {};
auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, username)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
user.remove();
@@ -757,7 +755,7 @@ handleGetRandomSongsRequest(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
auto tracks {Track::getAllRandom(context.dbSession, size)};
@@ -787,7 +785,7 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
if (type == "random")
{
@@ -835,7 +833,7 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
}
}
else
throw Error {Error::CustomType::NotImplemented};
throw NotImplementedGenericError {};
Response response {Response::createOkResponse()};
Response::Node& albumListNode {response.createNode(id3 ? "albumList2" : "albumList")};
@@ -868,17 +866,17 @@ handleGetAlbumRequest(RequestContext& context)
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Release)
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"id"};
auto transaction {context.dbSession.createSharedTransaction()};
Release::pointer release {Release::getById(context.dbSession, id.value)};
if (!release)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()};
Response::Node releaseNode {releaseToResponseNode(release, context.dbSession, user, true /* id3 */)};
@@ -900,15 +898,17 @@ handleGetArtistRequest(RequestContext& context)
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Artist)
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"id"};
auto transaction {context.dbSession.createSharedTransaction()};
Artist::pointer artist {Artist::getById(context.dbSession, id.value)};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!artist)
throw RequestedDataNotFoundError {};
if (!artist || !user)
throw Error {Error::Code::RequestedDataNotFound};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()};
Response::Node artistNode {artistToResponseNode(user, artist, true /* id3 */)};
@@ -929,7 +929,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Artist)
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"id"};
// Optional params
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").get_value_or(20)};
@@ -942,7 +942,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
Artist::pointer artist {Artist::getById(context.dbSession, id.value)};
if (!artist)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
if (!artist->getMBID().empty())
artistInfoNode.createChild("musicBrainzId").setValue(artist->getMBID());
@@ -955,7 +955,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
for ( const auto& similarArtistId : similarArtistsId )
{
@@ -996,7 +996,7 @@ handleGetArtistsRequest(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
auto artists {Artist::getAll(context.dbSession)};
for (const Artist::pointer& artist : artists)
@@ -1021,7 +1021,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
switch (id.type)
{
@@ -1040,7 +1040,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
{
auto artist {Artist::getById(context.dbSession, id.value)};
if (!artist)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
directoryNode.setAttribute("name", makeNameFilesystemCompatible(artist->getName()));
@@ -1055,7 +1055,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
{
auto release {Release::getById(context.dbSession, id.value)};
if (!release)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
directoryNode.setAttribute("name", makeNameFilesystemCompatible(release->getName()));
@@ -1067,7 +1067,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
}
default:
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"id"};
}
return response;
@@ -1123,7 +1123,7 @@ handleGetIndexesRequest(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
auto artists {Artist::getAll(context.dbSession)};
for (const Artist::pointer& artist : artists)
@@ -1139,7 +1139,7 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Artist)
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"id"};
// Optional params
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").get_value_or(50)};
@@ -1149,10 +1149,12 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
auto transaction {context.dbSession.createSharedTransaction()};
Artist::pointer artist {Artist::getById(context.dbSession, id.value)};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!artist)
throw RequestedDataNotFoundError {};
if (!user || !artist)
throw Error {Error::Code::RequestedDataNotFound};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw UserNotAuthorizedError {};
// "Returns a random collection of songs from the given artist and similar artists"
auto tracks {artist->getRandomTracks(count / 2)};
@@ -1203,7 +1205,7 @@ handleGetStarredRequestCommon(RequestContext& context, bool id3)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()};
Response::Node& starredNode {response.createNode(id3 ? "starred2" : "starred")};
@@ -1268,14 +1270,17 @@ handleGetPlaylistRequest(RequestContext& context)
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
if (id.type != Id::Type::Playlist)
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"id"};
auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw UserNotAuthorizedError {};
TrackList::pointer tracklist {TrackList::getById(context.dbSession, id.value)};
if (!user || !tracklist)
throw Error {Error::Code::RequestedDataNotFound};
if (!tracklist)
throw RequestedDataNotFoundError {};
Response response {Response::createOkResponse()};
Response::Node playlistNode {tracklistToResponseNode(tracklist, context.dbSession)};
@@ -1297,7 +1302,7 @@ handleGetPlaylistsRequest(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()};
Response::Node& playlistsNode {response.createNode("playlists")};
@@ -1326,15 +1331,15 @@ handleGetSongsByGenreRequest(RequestContext& context)
auto clusterType {ClusterType::getByName(context.dbSession, genreClusterName)};
if (!clusterType)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
auto cluster {clusterType->getCluster(genre)};
if (!cluster)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()};
Response::Node& songsByGenreNode {response.createNode("songsByGenre")};
@@ -1359,7 +1364,7 @@ handleGetUserRequest(RequestContext& context)
const User::pointer user {User::getByLoginName(context.dbSession, username)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
Response response {Response::createOkResponse()};
response.addNode("user", userToResponseNode(user));
@@ -1404,7 +1409,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()};
Response::Node& searchResult2Node {response.createNode(id3 ? "searchResult3" : "searchResult2")};
@@ -1449,10 +1454,10 @@ getStarParameters(const Wt::Http::ParameterMap& parameters)
res.releaseIds = getMultiParametersAs<Id>(parameters, "albumId");
if (!std::all_of(std::cbegin(res.releaseIds ), std::cend(res.releaseIds ), [](const Id& id) { return id.type == Id::Type::Release; }))
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"albumId"};
if (!std::all_of(std::cbegin(res.artistIds ), std::cend(res.artistIds ), [](const Id& id) { return id.type == Id::Type::Artist; }))
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"artistId"};
// Redispatch the old "id" parameter in new lists
for (const Id& id : ids)
@@ -1469,7 +1474,7 @@ getStarParameters(const Wt::Http::ParameterMap& parameters)
res.trackIds.emplace_back(id);
break;
default:
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"id"};
}
}
@@ -1486,7 +1491,7 @@ handleStarRequest(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
for (const Id& id : params.artistIds)
{
@@ -1542,7 +1547,7 @@ handleUnstarRequest(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
for (const Id& id : params.artistIds)
{
@@ -1588,7 +1593,7 @@ handleUpdateUserRequest(RequestContext& context)
{
*password = decodePasswordIfNeeded(*password);
if (!getService<Auth::PasswordService>()->evaluatePasswordStrength(username, *password))
throw Error {Error::CustomType::PasswordTooWeak};
throw PasswordTooWeakGenericError {};
hash = getService<Auth::PasswordService>()->hashPassword(*password);
}
@@ -1597,7 +1602,7 @@ handleUpdateUserRequest(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, username)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
if (maxBitRate)
{
@@ -1623,7 +1628,7 @@ handleUpdatePlaylistRequest(RequestContext& context)
// Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "playlistId")};
if (id.type != Id::Type::Playlist)
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"playlistId"};
// Optional parameters
auto name {getParameterAs<std::string>(context.parameters, "name")};
@@ -1631,7 +1636,7 @@ handleUpdatePlaylistRequest(RequestContext& context)
std::vector<Id> trackIdsToAdd {getMultiParametersAs<Id>(context.parameters, "songIdToAdd")};
if (!std::all_of(std::cbegin(trackIdsToAdd), std::cend(trackIdsToAdd), [](const Id& id) { return id.type == Id::Type::Track; }))
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"songIdToAdd"};
std::vector<std::size_t> trackPositionsToRemove {getMultiParametersAs<std::size_t>(context.parameters, "songIndexToRemove")};
@@ -1639,14 +1644,14 @@ handleUpdatePlaylistRequest(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
TrackList::pointer tracklist {TrackList::getById(context.dbSession, id.value)};
if (!tracklist
|| tracklist->getUser() != user
|| tracklist->getType() != TrackList::Type::Playlist)
{
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
}
if (name)
@@ -1696,7 +1701,7 @@ createTranscoder(RequestContext& context)
User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
throw UserNotAuthorizedError {};
// "If set to zero, no limit is imposed"
if (!maxBitRate || *maxBitRate == 0)
@@ -1706,7 +1711,7 @@ createTranscoder(RequestContext& context)
auto track {Track::getById(context.dbSession, id.value)};
if (!track)
throw Error {Error::Code::RequestedDataNotFound};
throw RequestedDataNotFoundError {};
trackPath = track->getPath();
}
@@ -1743,7 +1748,7 @@ handleStream(RequestContext& context, Wt::Http::ResponseContinuation* continuati
}
if (!transcoder)
throw Error {Error::CustomType::InternalError};
throw InternalErrorGenericError {"Cannot create transcoder"};
if (!transcoder->isComplete())
{
@@ -1751,7 +1756,6 @@ handleStream(RequestContext& context, Wt::Http::ResponseContinuation* continuati
res.data.reserve(chunkSize);
transcoder->process(res.data, chunkSize);
}
if (!transcoder->isComplete())
@@ -1781,7 +1785,7 @@ handleGetCoverArt(RequestContext& context, Wt::Http::ResponseContinuation*)
res.data = getService<CoverArt::Grabber>()->getFromRelease(context.dbSession, id.value, Image::Format::JPEG, size);
break;
default:
throw Error {Error::CustomType::BadIdFormat};
throw BadParameterGenericError {"id"};
}
res.mimeType = Image::format_to_mimeType(Image::Format::JPEG);
@@ -1874,9 +1878,9 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
case Auth::PasswordService::PasswordCheckResult::Match:
break;
case Auth::PasswordService::PasswordCheckResult::Mismatch:
throw Error {Error::Code::WrongUsernameOrPassword};
throw WrongUsernameOrPasswordError {};
case Auth::PasswordService::PasswordCheckResult::Throttled:
throw Error {Error::CustomType::LoginThrottled};
throw LoginThrottledGenericError {};
}
RequestContext requestContext {.parameters = parameters, .dbSession = dbSession, .userName = clientInfo.user};
@@ -1889,11 +1893,8 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
auto transaction {dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(dbSession, clientInfo.user)};
if (!user)
throw Error {Error::Code::RequestedDataNotFound};
if (!user->isAdmin())
throw Error {Error::Code::UserNotAuthorized};
if (!user || !user->isAdmin())
throw UserNotAuthorizedError {};
}
Response resp {(itEntryPoint->second.func)(requestContext)};
@@ -1933,7 +1934,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
}
LMS_LOG(API_SUBSONIC, ERROR) << "Unhandled command '" << requestPath << "'";
throw Error {Error::CustomType::NotImplemented};
throw NotImplementedGenericError {};
}
catch (const Error& e)
{
-62
View File
@@ -44,68 +44,6 @@ ResponseFormatToMimeType(ResponseFormat format)
return "";
}
static
const char*
ErrorCodeToString(Error::Code error)
{
switch (error)
{
case Error::Code::Generic:
return "Generic Error.";
case Error::Code::RequiredParameterMissing:
return "Required parameter is missing.";
case Error::Code::ClientMustUpgrade:
return "Incompatible Subsonic REST protocol version. Client must upgrade.";
case Error::Code::ServerMustUpgrade:
return "Incompatible Subsonic REST protocol version. Server must upgrade.";
case Error::Code::WrongUsernameOrPassword:
return "Wrong username or password.";
case Error::Code::UserNotAuthorized:
return "User is not authorized for the given operation.";
case Error::Code::RequestedDataNotFound:
return "The requested data was not found.";
}
return "Unknown error";
}
static
const char*
CustomTypeToString(Error::CustomType type)
{
switch (type)
{
case Error::CustomType::BadIdFormat:
return "Bad id format";
case Error::CustomType::BadPasswordFormat:
return "Bad password format";
case Error::CustomType::InternalError:
return "Internal error";
case Error::CustomType::LoginThrottled:
return "Login throttled, too many attempts";
case Error::CustomType::NotImplemented:
return "Not implemented";
case Error::CustomType::PasswordTooWeak:
return "Password too weak";
case Error::CustomType::UserAlreadyExists:
return "User already exists";
}
return "Unknown custom error";
}
Error::Error(Code code)
: _code {code},
_message {ErrorCodeToString(code)}
{
}
Error::Error(Error::CustomType type)
: _code {Code::Generic},
_message {CustomTypeToString(type)}
{
}
void
Response::Node::setValue(const std::string& value)
{
+108 -15
View File
@@ -52,26 +52,119 @@ class Error
RequestedDataNotFound = 70,
};
enum class CustomType
{
BadIdFormat,
BadPasswordFormat,
InternalError,
LoginThrottled,
NotImplemented,
PasswordTooWeak,
UserAlreadyExists,
};
Error(Code code) : _code {code} {}
Error(Code code);
Error(CustomType customType);
virtual std::string getMessage() const = 0;
Code getCode() const { return _code; }
const std::string& getMessage() const { return _message; }
private:
Code _code;
std::string _message;
const Code _code;
};
class GenericError : public Error
{
public:
GenericError() : Error {Code::Generic} {}
};
class RequiredParameterMissingError : public Error
{
public:
RequiredParameterMissingError() : Error {Code::RequiredParameterMissing} {}
private:
std::string getMessage() const override { return "Required parameter is missing."; }
};
class ClientMustUpgradeError : public Error
{
public:
ClientMustUpgradeError() : Error {Code::ClientMustUpgrade} {}
private:
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; }
};
class ServerMustUpgradeError : public Error
{
public:
ServerMustUpgradeError() : Error {Code::ServerMustUpgrade} {}
private:
std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; }
};
class WrongUsernameOrPasswordError : public Error
{
public:
WrongUsernameOrPasswordError() : Error {Code::WrongUsernameOrPassword} {}
private:
std::string getMessage() const override { return "Wrong username or password."; }
};
class UserNotAuthorizedError : public Error
{
public:
UserNotAuthorizedError () : Error {Code::UserNotAuthorized} {}
private:
std::string getMessage() const override { return "User is not authorized for the given operation."; }
};
class RequestedDataNotFoundError : public Error
{
public:
RequestedDataNotFoundError() : Error {Code::RequestedDataNotFound} {}
private:
std::string getMessage() const override { return "The requested data was not found."; }
};
class InternalErrorGenericError : public GenericError
{
public:
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
{
std::string getMessage() const override { return "Login throttled, too many attempts"; }
};
class NotImplementedGenericError : public GenericError
{
std::string getMessage() const override { return "Not implemented"; }
};
class PasswordTooWeakGenericError : public GenericError
{
std::string getMessage() const override { return "Password too weak"; }
};
class UserAlreadyExistsGenericError : public GenericError
{
std::string getMessage() const override { return "User already exists"; }
};
class BadParameterGenericError : public GenericError
{
public:
BadParameterGenericError(const std::string& parameterName) : _parameterName {parameterName} {}
private:
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; }
const std::string _parameterName;
};
class BadParameterFormatGenericError : public GenericError
{
public:
BadParameterFormatGenericError(const std::string& parameterName) : _parameterName {parameterName} {}
private:
std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad format"; }
const std::string _parameterName;
};
class Response