Extracted user management

This commit is contained in:
emeric
2023-10-01 20:42:04 +02:00
parent abcf9e5b49
commit 9d879e66b7
20 changed files with 465 additions and 293 deletions
+7 -3
View File
@@ -1,16 +1,20 @@
add_library(lmssubsonic SHARED add_library(lmssubsonic SHARED
impl/entrypoints/Bookmark.cpp
impl/entrypoints/Scan.cpp
impl/entrypoints/Stream.cpp
impl/entrypoints/UserManagement.cpp
impl/responses/Album.cpp impl/responses/Album.cpp
impl/responses/Artist.cpp impl/responses/Artist.cpp
impl/responses/Bookmark.cpp impl/responses/Bookmark.cpp
impl/responses/Song.cpp impl/responses/Song.cpp
impl/responses/User.cpp
impl/ProtocolVersion.cpp impl/ProtocolVersion.cpp
impl/Bookmark.cpp impl/ParameterParsing.cpp
impl/Scan.cpp
impl/Stream.cpp
impl/SubsonicId.cpp impl/SubsonicId.cpp
impl/SubsonicResource.cpp impl/SubsonicResource.cpp
impl/SubsonicResponse.cpp impl/SubsonicResponse.cpp
impl/Utils.cpp
) )
target_include_directories(lmssubsonic INTERFACE target_include_directories(lmssubsonic INTERFACE
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2023 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ParameterParsing.hpp"
namespace API::Subsonic
{
bool hasParameter(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
{
return parameterMap.find(param) != std::cend(parameterMap);
}
std::string decodePasswordIfNeeded(const std::string& password)
{
if (password.find("enc:") == 0)
{
auto decodedPassword{ StringUtils::stringFromHex(password.substr(4)) };
if (!decodedPassword)
return password; // fallback on plain password
return *decodedPassword;
}
return password;
}
}
+18 -21
View File
@@ -16,10 +16,15 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#pragma once #pragma once
#include <Wt/Http/Request.h> #include <Wt/Http/Request.h>
#include <optional>
#include <vector>
#include <string>
#include "services/database/Types.hpp" #include "services/database/Types.hpp"
#include "utils/String.hpp" #include "utils/String.hpp"
#include "SubsonicResponse.hpp" #include "SubsonicResponse.hpp"
@@ -28,8 +33,7 @@ namespace API::Subsonic
{ {
template<typename T> template<typename T>
std::vector<T> std::vector<T> getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& paramName)
getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& paramName)
{ {
std::vector<T> res; std::vector<T> res;
@@ -39,7 +43,7 @@ namespace API::Subsonic
for (const std::string& param : it->second) for (const std::string& param : it->second)
{ {
auto value {StringUtils::readAs<T>(param)}; auto value{ StringUtils::readAs<T>(param) };
if (value) if (value)
res.emplace_back(std::move(*value)); res.emplace_back(std::move(*value));
} }
@@ -48,44 +52,37 @@ namespace API::Subsonic
} }
template<typename T> template<typename T>
std::vector<T> std::vector<T> getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
{ {
std::vector<T> res {getMultiParametersAs<T>(parameterMap, param)}; std::vector<T> res{ getMultiParametersAs<T>(parameterMap, param) };
if (res.empty()) if (res.empty())
throw RequiredParameterMissingError {param}; throw RequiredParameterMissingError{ param };
return res; return res;
} }
template<typename T> template<typename T>
std::optional<T> std::optional<T> getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
{ {
std::vector<T> params {getMultiParametersAs<T>(parameterMap, param)}; std::vector<T> params{ getMultiParametersAs<T>(parameterMap, param) };
if (params.size() != 1) if (params.size() != 1)
return std::nullopt; return std::nullopt;
return T { std::move(params.front()) }; return T{ std::move(params.front()) };
} }
template<typename T> template<typename T>
T T getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
{ {
auto res {getParameterAs<T>(parameterMap, param)}; auto res{ getParameterAs<T>(parameterMap, param) };
if (!res) if (!res)
throw RequiredParameterMissingError {param}; throw RequiredParameterMissingError{ param };
return *res; return *res;
} }
inline bool hasParameter(const Wt::Http::ParameterMap& parameterMap, const std::string& param);
bool std::string decodePasswordIfNeeded(const std::string& password);
hasParameter(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
{
return parameterMap.find(param) != std::cend(parameterMap);
}
} }
+9 -263
View File
@@ -47,17 +47,20 @@
#include "utils/String.hpp" #include "utils/String.hpp"
#include "utils/Utils.hpp" #include "utils/Utils.hpp"
#include "entrypoints/Bookmark.hpp"
#include "entrypoints/Scan.hpp"
#include "entrypoints/Stream.hpp"
#include "entrypoints/UserManagement.hpp"
#include "responses/Artist.hpp" #include "responses/Artist.hpp"
#include "responses/Album.hpp" #include "responses/Album.hpp"
#include "responses/Song.hpp" #include "responses/Song.hpp"
#include "Bookmark.hpp"
#include "ParameterParsing.hpp" #include "ParameterParsing.hpp"
#include "ProtocolVersion.hpp" #include "ProtocolVersion.hpp"
#include "RequestContext.hpp" #include "RequestContext.hpp"
#include "Scan.hpp"
#include "Stream.hpp"
#include "SubsonicId.hpp" #include "SubsonicId.hpp"
#include "SubsonicResponse.hpp" #include "SubsonicResponse.hpp"
#include "Utils.hpp"
using namespace Database; using namespace Database;
@@ -75,14 +78,6 @@ createSubsonicResource(Database::Db& db)
return std::make_unique<SubsonicResource>(db); return std::make_unique<SubsonicResource>(db);
} }
static
void
checkSetPasswordImplemented()
{
Auth::IPasswordService* passwordService {Service<Auth::IPasswordService>::get()};
if (!passwordService || !passwordService->canSetPasswords())
throw NotImplementedGenericError {};
}
static static
std::string std::string
@@ -91,22 +86,6 @@ makeNameFilesystemCompatible(const std::string& name)
return StringUtils::replaceInString(name, "/", "_"); return StringUtils::replaceInString(name, "/", "_");
} }
static
std::string
decodePasswordIfNeeded(const std::string& password)
{
if (password.find("enc:") == 0)
{
auto decodedPassword {StringUtils::stringFromHex(password.substr(4))};
if (!decodedPassword)
return password; // fallback on plain password
return *decodedPassword;
}
return password;
}
static static
std::unordered_map<std::string, ProtocolVersion> std::unordered_map<std::string, ProtocolVersion>
readConfigProtocolVersions() readConfigProtocolVersions()
@@ -161,18 +140,6 @@ std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap
return res; return res;
} }
static
void
checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username)
{
User::pointer currentUser {User::find(context.dbSession, context.userId)};
if (!currentUser)
throw RequestedDataNotFoundError {};
if (currentUser->getLoginName() != username && !currentUser->isAdmin())
throw UserNotAuthorizedError {};
}
static static
void void
checkUserTypeIsAllowed(RequestContext& context, EnumSet<Database::UserType> allowedUserTypes) checkUserTypeIsAllowed(RequestContext& context, EnumSet<Database::UserType> allowedUserTypes)
@@ -200,33 +167,6 @@ clusterToResponseNode(const Cluster::pointer& cluster)
return clusterNode; return clusterNode;
} }
static
Response::Node
userToResponseNode(const User::pointer& user)
{
Response::Node userNode;
userNode.setAttribute("username", user->getLoginName());
userNode.setAttribute("scrobblingEnabled", true);
userNode.setAttribute("adminRole", user->isAdmin());
userNode.setAttribute("settingsRole", true);
userNode.setAttribute("downloadRole", true);
userNode.setAttribute("uploadRole", false);
userNode.setAttribute("playlistRole", true);
userNode.setAttribute("coverArtRole", false);
userNode.setAttribute("commentRole", false);
userNode.setAttribute("podcastRole", false);
userNode.setAttribute("streamRole", true);
userNode.setAttribute("jukeboxRole", false);
userNode.setAttribute("shareRole", false);
Response::Node folder;
folder.setValue("0");
userNode.addArrayChild("folder", std::move(folder));
return userNode;
}
static static
Response Response
handlePingRequest(RequestContext& context) handlePingRequest(RequestContext& context)
@@ -234,46 +174,6 @@ handlePingRequest(RequestContext& context)
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.serverProtocolVersion);
} }
static
Response
handleChangePassword(RequestContext& context)
{
std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")};
std::string password {decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(context.parameters, "password"))};
try
{
Database::UserId userId;
{
auto transaction {context.dbSession.createSharedTransaction()};
checkUserIsMySelfOrAdmin(context, username);
User::pointer user {User::find(context.dbSession, username)};
if (!user)
throw UserNotAuthorizedError {};
userId = user->getId();
}
Service<Auth::IPasswordService>::get()->setPassword(userId, password);
}
catch (const Auth::PasswordMustMatchLoginNameException&)
{
throw PasswordMustMatchLoginNameGenericError {};
}
catch (const Auth::PasswordTooWeakException&)
{
throw PasswordTooWeakGenericError {};
}
catch (const Auth::Exception& authException)
{
throw UserNotAuthorizedError {};
}
return Response::createOkResponse(context.serverProtocolVersion);
}
static static
Response Response
handleCreatePlaylistRequest(RequestContext& context) handleCreatePlaylistRequest(RequestContext& context)
@@ -324,57 +224,6 @@ handleCreatePlaylistRequest(RequestContext& context)
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.serverProtocolVersion);
} }
static
Response
handleCreateUserRequest(RequestContext& context)
{
std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")};
std::string password {decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(context.parameters, "password"))};
// Just ignore all the other fields as we don't handle them
Database::UserId userId;
{
auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::find(context.dbSession, username)};
if (user)
throw UserAlreadyExistsGenericError {};
user = context.dbSession.create<User>(username);
userId = user->getId();
}
auto removeCreatedUser {[&]()
{
auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::find(context.dbSession, userId)};
if (user)
user.remove();
}};
try
{
Service<Auth::IPasswordService>::get()->setPassword(userId, password);
}
catch (const Auth::PasswordMustMatchLoginNameException&)
{
removeCreatedUser();
throw PasswordMustMatchLoginNameGenericError {};
}
catch (const Auth::PasswordTooWeakException&)
{
removeCreatedUser();
throw PasswordTooWeakGenericError {};
}
catch (const Auth::Exception& exception)
{
removeCreatedUser();
throw UserNotAuthorizedError {};
}
return Response::createOkResponse(context.serverProtocolVersion);
}
static static
Response Response
handleDeletePlaylistRequest(RequestContext& context) handleDeletePlaylistRequest(RequestContext& context)
@@ -400,27 +249,6 @@ handleDeletePlaylistRequest(RequestContext& context)
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.serverProtocolVersion);
} }
static
Response
handleDeleteUserRequest(RequestContext& context)
{
std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")};
auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::find(context.dbSession, username)};
if (!user)
throw RequestedDataNotFoundError {};
// cannot delete ourself
if (user->getId() == context.userId)
throw UserNotAuthorizedError {};
user.remove();
return Response::createOkResponse(context.serverProtocolVersion);
}
static static
Response Response
handleGetLicenseRequest(RequestContext& context) handleGetLicenseRequest(RequestContext& context)
@@ -1201,45 +1029,6 @@ handleGetSongsByGenreRequest(RequestContext& context)
return response; return response;
} }
static
Response
handleGetUserRequest(RequestContext& context)
{
std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")};
auto transaction {context.dbSession.createSharedTransaction()};
checkUserIsMySelfOrAdmin(context, username);
const User::pointer user {User::find(context.dbSession, username)};
if (!user)
throw RequestedDataNotFoundError {};
Response response {Response::createOkResponse(context.serverProtocolVersion)};
response.addNode("user", userToResponseNode(user));
return response;
}
static
Response
handleGetUsersRequest(RequestContext& context)
{
auto transaction {context.dbSession.createSharedTransaction()};
Response response {Response::createOkResponse(context.serverProtocolVersion)};
Response::Node& usersNode {response.createNode("users")};
const auto userIds {User::find(context.dbSession, User::FindParameters {})};
for (const UserId userId : userIds.results)
{
const User::pointer user {User::find(context.dbSession, userId)};
usersNode.addArrayChild("user", userToResponseNode(user));
}
return response;
}
static static
Response Response
handleSearchRequestCommon(RequestContext& context, bool id3) handleSearchRequestCommon(RequestContext& context, bool id3)
@@ -1433,49 +1222,6 @@ handleScrobble(RequestContext& context)
return Response::createOkResponse(context.serverProtocolVersion); return Response::createOkResponse(context.serverProtocolVersion);
} }
static
Response
handleUpdateUserRequest(RequestContext& context)
{
std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")};
std::optional<std::string> password {getParameterAs<std::string>(context.parameters, "password")};
UserId userId;
{
auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::find(context.dbSession, username)};
if (!user)
throw RequestedDataNotFoundError {};
userId = user->getId();
}
if (password)
{
checkSetPasswordImplemented();
try
{
Service<::Auth::IPasswordService>()->setPassword(userId, decodePasswordIfNeeded(*password));
}
catch (const Auth::PasswordMustMatchLoginNameException&)
{
throw PasswordMustMatchLoginNameGenericError {};
}
catch (const Auth::PasswordTooWeakException&)
{
throw PasswordTooWeakGenericError {};
}
catch (const Auth::Exception&)
{
throw UserNotAuthorizedError {};
}
}
return Response::createOkResponse(context.serverProtocolVersion);
}
static static
Response Response
handleUpdatePlaylistRequest(RequestContext& context) handleUpdatePlaylistRequest(RequestContext& context)
@@ -1554,7 +1300,7 @@ handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/,
throw BadParameterGenericError {"id"}; throw BadParameterGenericError {"id"};
std::size_t size {getParameterAs<std::size_t>(context.parameters, "size").value_or(1024)}; std::size_t size {getParameterAs<std::size_t>(context.parameters, "size").value_or(1024)};
size = Utils::clamp(size, std::size_t {32}, std::size_t {2048}); size = ::Utils::clamp(size, std::size_t {32}, std::size_t {2048});
std::shared_ptr<Image::IEncodedImage> cover; std::shared_ptr<Image::IEncodedImage> cover;
if (trackId) if (trackId)
@@ -1663,10 +1409,10 @@ static const std::unordered_map<std::string_view, RequestEntryPointInfo> request
// User management // User management
{"/getUser", {handleGetUserRequest}}, {"/getUser", {handleGetUserRequest}},
{"/getUsers", {handleGetUsersRequest, {UserType::ADMIN}}}, {"/getUsers", {handleGetUsersRequest, {UserType::ADMIN}}},
{"/createUser", {handleCreateUserRequest, {UserType::ADMIN}, &checkSetPasswordImplemented}}, {"/createUser", {handleCreateUserRequest, {UserType::ADMIN}, &Utils::checkSetPasswordImplemented}},
{"/updateUser", {handleUpdateUserRequest, {UserType::ADMIN}}}, {"/updateUser", {handleUpdateUserRequest, {UserType::ADMIN}}},
{"/deleteUser", {handleDeleteUserRequest, {UserType::ADMIN}}}, {"/deleteUser", {handleDeleteUserRequest, {UserType::ADMIN}}},
{"/changePassword", {handleChangePassword, {UserType::REGULAR, UserType::ADMIN}, &checkSetPasswordImplemented}}, {"/changePassword", {handleChangePassword, {UserType::REGULAR, UserType::ADMIN}, &Utils::checkSetPasswordImplemented}},
// Bookmarks // Bookmarks
{"/getBookmarks", {handleGetBookmarks}}, {"/getBookmarks", {handleGetBookmarks}},
+34
View File
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2023 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Utils.hpp"
#include "utils/Service.hpp"
#include "services/auth/IPasswordService.hpp"
#include "SubsonicResponse.hpp"
namespace API::Subsonic::Utils
{
void checkSetPasswordImplemented()
{
Auth::IPasswordService* passwordService{ Service<Auth::IPasswordService>::get() };
if (!passwordService || !passwordService->canSetPasswords())
throw NotImplementedGenericError{};
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
namespace API::Subsonic::Utils
{
void checkSetPasswordImplemented();
}
@@ -19,8 +19,6 @@
#pragma once #pragma once
#include <string>
#include "RequestContext.hpp" #include "RequestContext.hpp"
#include "SubsonicResponse.hpp" #include "SubsonicResponse.hpp"
@@ -0,0 +1,208 @@
#include "UserManagement.hpp"
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "services/auth/IPasswordService.hpp"
#include "utils/Service.hpp"
#include "responses/User.hpp"
#include "ParameterParsing.hpp"
#include "Utils.hpp"
namespace API::Subsonic
{
using namespace Database;
namespace {
void checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username)
{
User::pointer currentUser{ User::find(context.dbSession, context.userId) };
if (!currentUser)
throw RequestedDataNotFoundError{};
if (currentUser->getLoginName() != username && !currentUser->isAdmin())
throw UserNotAuthorizedError{};
}
}
Response handleGetUserRequest(RequestContext& context)
{
std::string username{ getMandatoryParameterAs<std::string>(context.parameters, "username") };
auto transaction{ context.dbSession.createSharedTransaction() };
checkUserIsMySelfOrAdmin(context, username);
const User::pointer user{ User::find(context.dbSession, username) };
if (!user)
throw RequestedDataNotFoundError{};
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
response.addNode("user", createUserNode(user));
return response;
}
Response handleGetUsersRequest(RequestContext& context)
{
auto transaction{ context.dbSession.createSharedTransaction() };
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& usersNode{ response.createNode("users") };
const auto userIds{ User::find(context.dbSession, User::FindParameters {}) };
for (const UserId userId : userIds.results)
{
const User::pointer user{ User::find(context.dbSession, userId) };
usersNode.addArrayChild("user", createUserNode(user));
}
return response;
}
Response handleCreateUserRequest(RequestContext& context)
{
std::string username{ getMandatoryParameterAs<std::string>(context.parameters, "username") };
std::string password{ decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(context.parameters, "password")) };
// Just ignore all the other fields as we don't handle them
Database::UserId userId;
{
auto transaction{ context.dbSession.createUniqueTransaction() };
User::pointer user{ User::find(context.dbSession, username) };
if (user)
throw UserAlreadyExistsGenericError{};
user = context.dbSession.create<User>(username);
userId = user->getId();
}
auto removeCreatedUser{ [&]()
{
auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::find(context.dbSession, userId)};
if (user)
user.remove();
} };
try
{
Service<Auth::IPasswordService>::get()->setPassword(userId, password);
}
catch (const Auth::PasswordMustMatchLoginNameException&)
{
removeCreatedUser();
throw PasswordMustMatchLoginNameGenericError{};
}
catch (const Auth::PasswordTooWeakException&)
{
removeCreatedUser();
throw PasswordTooWeakGenericError{};
}
catch (const Auth::Exception& exception)
{
removeCreatedUser();
throw UserNotAuthorizedError{};
}
return Response::createOkResponse(context.serverProtocolVersion);
}
Response handleDeleteUserRequest(RequestContext& context)
{
std::string username{ getMandatoryParameterAs<std::string>(context.parameters, "username") };
auto transaction{ context.dbSession.createUniqueTransaction() };
User::pointer user{ User::find(context.dbSession, username) };
if (!user)
throw RequestedDataNotFoundError{};
// cannot delete ourself
if (user->getId() == context.userId)
throw UserNotAuthorizedError{};
user.remove();
return Response::createOkResponse(context.serverProtocolVersion);
}
Response handleUpdateUserRequest(RequestContext& context)
{
std::string username{ getMandatoryParameterAs<std::string>(context.parameters, "username") };
std::optional<std::string> password{ getParameterAs<std::string>(context.parameters, "password") };
UserId userId;
{
auto transaction{ context.dbSession.createSharedTransaction() };
User::pointer user{ User::find(context.dbSession, username) };
if (!user)
throw RequestedDataNotFoundError{};
userId = user->getId();
}
if (password)
{
Utils::checkSetPasswordImplemented();
try
{
Service<::Auth::IPasswordService>()->setPassword(userId, decodePasswordIfNeeded(*password));
}
catch (const Auth::PasswordMustMatchLoginNameException&)
{
throw PasswordMustMatchLoginNameGenericError{};
}
catch (const Auth::PasswordTooWeakException&)
{
throw PasswordTooWeakGenericError{};
}
catch (const Auth::Exception&)
{
throw UserNotAuthorizedError{};
}
}
return Response::createOkResponse(context.serverProtocolVersion);
}
Response handleChangePassword(RequestContext& context)
{
std::string username{ getMandatoryParameterAs<std::string>(context.parameters, "username") };
std::string password{ decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(context.parameters, "password")) };
try
{
Database::UserId userId;
{
auto transaction{ context.dbSession.createSharedTransaction() };
checkUserIsMySelfOrAdmin(context, username);
User::pointer user{ User::find(context.dbSession, username) };
if (!user)
throw UserNotAuthorizedError{};
userId = user->getId();
}
Service<Auth::IPasswordService>::get()->setPassword(userId, password);
}
catch (const Auth::PasswordMustMatchLoginNameException&)
{
throw PasswordMustMatchLoginNameGenericError{};
}
catch (const Auth::PasswordTooWeakException&)
{
throw PasswordTooWeakGenericError{};
}
catch (const Auth::Exception& authException)
{
throw UserNotAuthorizedError{};
}
return Response::createOkResponse(context.serverProtocolVersion);
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "RequestContext.hpp"
#include "SubsonicResponse.hpp"
namespace API::Subsonic
{
Response handleGetUserRequest(RequestContext& context);
Response handleGetUsersRequest(RequestContext& context);
Response handleCreateUserRequest(RequestContext& context);
Response handleUpdateUserRequest(RequestContext& context);
Response handleDeleteUserRequest(RequestContext& context);
Response handleChangePassword(RequestContext& context);
}
+1 -1
View File
@@ -70,7 +70,7 @@ namespace API::Subsonic
} }
else if (!artists.empty()) else if (!artists.empty())
{ {
albumNode.setAttribute("artist", utils::joinArtistNames(artists)); albumNode.setAttribute("artist", Utils::joinArtistNames(artists));
if (artists.size() == 1) if (artists.size() == 1)
{ {
+1 -1
View File
@@ -33,7 +33,7 @@ namespace API::Subsonic
using namespace Database; using namespace Database;
namespace utils namespace Utils
{ {
std::string joinArtistNames(const std::vector<Artist::pointer>& artists) std::string joinArtistNames(const std::vector<Artist::pointer>& artists)
{ {
+1 -1
View File
@@ -33,7 +33,7 @@ namespace Database
namespace API::Subsonic namespace API::Subsonic
{ {
namespace utils namespace Utils
{ {
std::string joinArtistNames(const std::vector<Database::ObjectPtr<Database::Artist>>& artists); std::string joinArtistNames(const std::vector<Database::ObjectPtr<Database::Artist>>& artists);
} }
+1 -1
View File
@@ -130,7 +130,7 @@ namespace API::Subsonic
const std::vector<Artist::pointer>& artists{ track->getArtists({TrackArtistLinkType::Artist}) }; const std::vector<Artist::pointer>& artists{ track->getArtists({TrackArtistLinkType::Artist}) };
if (!artists.empty()) if (!artists.empty())
{ {
trackResponse.setAttribute("artist", utils::joinArtistNames(artists)); trackResponse.setAttribute("artist", Utils::joinArtistNames(artists));
if (artists.size() == 1) if (artists.size() == 1)
trackResponse.setAttribute("artistId", idToString(artists.front()->getId())); trackResponse.setAttribute("artistId", idToString(artists.front()->getId()));
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2023 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "responses/User.hpp"
#include "services/database/User.hpp"
namespace API::Subsonic
{
using namespace Database;
Response::Node createUserNode(const User::pointer& user)
{
Response::Node userNode;
userNode.setAttribute("username", user->getLoginName());
userNode.setAttribute("scrobblingEnabled", true);
userNode.setAttribute("adminRole", user->isAdmin());
userNode.setAttribute("settingsRole", true);
userNode.setAttribute("downloadRole", true);
userNode.setAttribute("uploadRole", false);
userNode.setAttribute("playlistRole", true);
userNode.setAttribute("coverArtRole", false);
userNode.setAttribute("commentRole", false);
userNode.setAttribute("podcastRole", false);
userNode.setAttribute("streamRole", true);
userNode.setAttribute("jukeboxRole", false);
userNode.setAttribute("shareRole", false);
Response::Node folder;
folder.setValue("0");
userNode.addArrayChild("folder", std::move(folder));
return userNode;
}
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2023 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "services/database/Object.hpp"
#include "SubsonicResponse.hpp"
namespace Database
{
class User;
}
namespace API::Subsonic
{
Response::Node createUserNode(const Database::ObjectPtr<Database::User>& user);
}