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
+9 -263
View File
@@ -47,17 +47,20 @@
#include "utils/String.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/Album.hpp"
#include "responses/Song.hpp"
#include "Bookmark.hpp"
#include "ParameterParsing.hpp"
#include "ProtocolVersion.hpp"
#include "RequestContext.hpp"
#include "Scan.hpp"
#include "Stream.hpp"
#include "SubsonicId.hpp"
#include "SubsonicResponse.hpp"
#include "Utils.hpp"
using namespace Database;
@@ -75,14 +78,6 @@ createSubsonicResource(Database::Db& db)
return std::make_unique<SubsonicResource>(db);
}
static
void
checkSetPasswordImplemented()
{
Auth::IPasswordService* passwordService {Service<Auth::IPasswordService>::get()};
if (!passwordService || !passwordService->canSetPasswords())
throw NotImplementedGenericError {};
}
static
std::string
@@ -91,22 +86,6 @@ makeNameFilesystemCompatible(const std::string& 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
std::unordered_map<std::string, ProtocolVersion>
readConfigProtocolVersions()
@@ -161,18 +140,6 @@ std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap
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
void
checkUserTypeIsAllowed(RequestContext& context, EnumSet<Database::UserType> allowedUserTypes)
@@ -200,33 +167,6 @@ clusterToResponseNode(const Cluster::pointer& cluster)
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
Response
handlePingRequest(RequestContext& context)
@@ -234,46 +174,6 @@ handlePingRequest(RequestContext& context)
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
Response
handleCreatePlaylistRequest(RequestContext& context)
@@ -324,57 +224,6 @@ handleCreatePlaylistRequest(RequestContext& context)
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
Response
handleDeletePlaylistRequest(RequestContext& context)
@@ -400,27 +249,6 @@ handleDeletePlaylistRequest(RequestContext& context)
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
Response
handleGetLicenseRequest(RequestContext& context)
@@ -1201,45 +1029,6 @@ handleGetSongsByGenreRequest(RequestContext& context)
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
Response
handleSearchRequestCommon(RequestContext& context, bool id3)
@@ -1433,49 +1222,6 @@ handleScrobble(RequestContext& context)
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
Response
handleUpdatePlaylistRequest(RequestContext& context)
@@ -1554,7 +1300,7 @@ handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/,
throw BadParameterGenericError {"id"};
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;
if (trackId)
@@ -1663,10 +1409,10 @@ static const std::unordered_map<std::string_view, RequestEntryPointInfo> request
// User management
{"/getUser", {handleGetUserRequest}},
{"/getUsers", {handleGetUsersRequest, {UserType::ADMIN}}},
{"/createUser", {handleCreateUserRequest, {UserType::ADMIN}, &checkSetPasswordImplemented}},
{"/createUser", {handleCreateUserRequest, {UserType::ADMIN}, &Utils::checkSetPasswordImplemented}},
{"/updateUser", {handleUpdateUserRequest, {UserType::ADMIN}}},
{"/deleteUser", {handleDeleteUserRequest, {UserType::ADMIN}}},
{"/changePassword", {handleChangePassword, {UserType::REGULAR, UserType::ADMIN}, &checkSetPasswordImplemented}},
{"/changePassword", {handleChangePassword, {UserType::REGULAR, UserType::ADMIN}, &Utils::checkSetPasswordImplemented}},
// Bookmarks
{"/getBookmarks", {handleGetBookmarks}},