Fixed demo account creation. fixes #167
This commit is contained in:
@@ -33,9 +33,9 @@ namespace Auth
|
|||||||
Database::User::pointer user {Database::User::getByLoginName(session, loginName)};
|
Database::User::pointer user {Database::User::getByLoginName(session, loginName)};
|
||||||
if (!user)
|
if (!user)
|
||||||
{
|
{
|
||||||
const Database::User::Type type {Database::User::getCount(session) == 0 ? Database::User::Type::ADMIN : Database::User::Type::REGULAR};
|
const Database::UserType type {Database::User::getCount(session) == 0 ? Database::UserType::ADMIN : Database::UserType::REGULAR};
|
||||||
|
|
||||||
LMS_LOG(AUTH, DEBUG) << "Creating user '" << loginName << "', admin = " << (type == Database::User::Type::ADMIN);
|
LMS_LOG(AUTH, DEBUG) << "Creating user '" << loginName << "', admin = " << (type == Database::UserType::ADMIN);
|
||||||
|
|
||||||
user = Database::User::create(session, loginName);
|
user = Database::User::create(session, loginName);
|
||||||
user.modify()->setType(type);
|
user.modify()->setType(type);
|
||||||
|
|||||||
@@ -81,9 +81,18 @@ namespace Auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
InternalPasswordService::isPasswordSecureEnough(std::string_view loginName, std::string_view password) const
|
InternalPasswordService::isPasswordSecureEnough(std::string_view password, const PasswordValidationContext& context) const
|
||||||
{
|
{
|
||||||
return _validator.evaluateStrength(std::string {password}, std::string {loginName}, "").isValid();
|
switch (context.userType)
|
||||||
|
{
|
||||||
|
case Database::UserType::ADMIN:
|
||||||
|
case Database::UserType::REGULAR:
|
||||||
|
return _validator.evaluateStrength(std::string {password}, context.loginName, "").isValid();
|
||||||
|
case Database::UserType::DEMO:
|
||||||
|
return true; // no constraint
|
||||||
|
}
|
||||||
|
|
||||||
|
throw NotImplementedException {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -97,7 +106,7 @@ namespace Auth
|
|||||||
if (!user)
|
if (!user)
|
||||||
throw Exception {"User not found!"};
|
throw Exception {"User not found!"};
|
||||||
|
|
||||||
if (!isPasswordSecureEnough(user->getLoginName(), newPassword))
|
if (!isPasswordSecureEnough(newPassword, PasswordValidationContext {user->getLoginName(), user->getType()} ))
|
||||||
throw PasswordTooWeakException {};
|
throw PasswordTooWeakException {};
|
||||||
|
|
||||||
user.modify()->setPasswordHash(passwordHash);
|
user.modify()->setPasswordHash(passwordHash);
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace Auth
|
|||||||
std::string_view password) override;
|
std::string_view password) override;
|
||||||
|
|
||||||
bool canSetPasswords() const override;
|
bool canSetPasswords() const override;
|
||||||
bool isPasswordSecureEnough(std::string_view loginName, std::string_view password) const override;
|
bool isPasswordSecureEnough(std::string_view loginName, const PasswordValidationContext& context) const override;
|
||||||
void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) override;
|
void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) override;
|
||||||
|
|
||||||
Database::User::PasswordHash hashPassword(std::string_view password) const;
|
Database::User::PasswordHash hashPassword(std::string_view password) const;
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ namespace Auth
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
PAMPasswordService::isPasswordSecureEnough(std::string_view, std::string_view) const
|
PAMPasswordService::isPasswordSecureEnough(std::string_view, const PasswordValidationContext&) const
|
||||||
{
|
{
|
||||||
throw NotImplementedException {};
|
throw NotImplementedException {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ namespace Auth
|
|||||||
std::string_view password) override;
|
std::string_view password) override;
|
||||||
|
|
||||||
bool canSetPasswords() const override;
|
bool canSetPasswords() const override;
|
||||||
bool isPasswordSecureEnough(std::string_view loginName,
|
bool isPasswordSecureEnough(std::string_view loginName, const PasswordValidationContext& context) const override;
|
||||||
std::string_view password) const override;
|
|
||||||
void setPassword(Database::Session& session,
|
void setPassword(Database::Session& session,
|
||||||
Database::IdType userId,
|
Database::IdType userId,
|
||||||
std::string_view newPassword) override;
|
std::string_view newPassword) override;
|
||||||
|
|||||||
@@ -61,14 +61,9 @@ namespace Auth
|
|||||||
std::string_view loginName,
|
std::string_view loginName,
|
||||||
std::string_view password) = 0;
|
std::string_view password) = 0;
|
||||||
|
|
||||||
class PasswordTooWeakException : public Auth::Exception
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PasswordTooWeakException() : Auth::Exception {"Password too weak"} {}
|
|
||||||
};
|
|
||||||
|
|
||||||
virtual bool canSetPasswords() const = 0;
|
virtual bool canSetPasswords() const = 0;
|
||||||
virtual bool isPasswordSecureEnough(std::string_view username, std::string_view password) const = 0;
|
|
||||||
|
virtual bool isPasswordSecureEnough(std::string_view password, const PasswordValidationContext& context) const = 0;
|
||||||
virtual void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) = 0;
|
virtual void setPassword(Database::Session& session, Database::IdType userId, std::string_view newPassword) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "database/Types.hpp"
|
||||||
#include "utils/Exception.hpp"
|
#include "utils/Exception.hpp"
|
||||||
|
|
||||||
namespace Auth
|
namespace Auth
|
||||||
@@ -33,5 +35,17 @@ namespace Auth
|
|||||||
public:
|
public:
|
||||||
NotImplementedException() : Auth::Exception {"Not implemented"} {}
|
NotImplementedException() : Auth::Exception {"Not implemented"} {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PasswordValidationContext
|
||||||
|
{
|
||||||
|
std::string loginName;
|
||||||
|
Database::UserType userType;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PasswordTooWeakException : public Exception
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PasswordTooWeakException() : Auth::Exception {"Password too weak"} {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ User::getDemo(Session& session)
|
|||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
pointer res = session.getDboSession().find<User>().where("type = ?").bind(Type::DEMO);
|
pointer res = session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,5 +70,13 @@ namespace Database
|
|||||||
Internal = 0,
|
Internal = 0,
|
||||||
ListenBrainz = 1,
|
ListenBrainz = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Do not change enum values!
|
||||||
|
enum class UserType
|
||||||
|
{
|
||||||
|
REGULAR = 0,
|
||||||
|
ADMIN = 1,
|
||||||
|
DEMO = 2,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,15 +80,6 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
public:
|
public:
|
||||||
using pointer = Wt::Dbo::ptr<User>;
|
using pointer = Wt::Dbo::ptr<User>;
|
||||||
|
|
||||||
|
|
||||||
// Do not change enum values!
|
|
||||||
enum class Type
|
|
||||||
{
|
|
||||||
REGULAR = 0,
|
|
||||||
ADMIN = 1,
|
|
||||||
DEMO = 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PasswordHash
|
struct PasswordHash
|
||||||
{
|
{
|
||||||
std::string salt;
|
std::string salt;
|
||||||
@@ -152,7 +143,7 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
// write
|
// write
|
||||||
void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; }
|
void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; }
|
||||||
void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; }
|
void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; }
|
||||||
void setType(Type type) { _type = type; }
|
void setType(UserType type) { _type = type; }
|
||||||
void setSubsonicTranscodeEnable(bool value) { _subsonicTranscodeEnable = value; }
|
void setSubsonicTranscodeEnable(bool value) { _subsonicTranscodeEnable = value; }
|
||||||
void setSubsonicTranscodeFormat(AudioFormat encoding) { _subsonicTranscodeFormat = encoding; }
|
void setSubsonicTranscodeFormat(AudioFormat encoding) { _subsonicTranscodeFormat = encoding; }
|
||||||
void setSubsonicTranscodeBitrate(Bitrate bitrate);
|
void setSubsonicTranscodeBitrate(Bitrate bitrate);
|
||||||
@@ -166,8 +157,9 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
void setListenBrainzToken(const std::optional<UUID>& MBID) { _listenbrainzToken = MBID ? MBID->getAsString() : ""; }
|
void setListenBrainzToken(const std::optional<UUID>& MBID) { _listenbrainzToken = MBID ? MBID->getAsString() : ""; }
|
||||||
|
|
||||||
// read
|
// read
|
||||||
bool isAdmin() const { return _type == Type::ADMIN; }
|
bool isAdmin() const { return _type == UserType::ADMIN; }
|
||||||
bool isDemo() const { return _type == Type::DEMO; }
|
bool isDemo() const { return _type == UserType::DEMO; }
|
||||||
|
UserType getType() const { return _type; }
|
||||||
bool getSubsonicTranscodeEnable() const { return _subsonicTranscodeEnable; }
|
bool getSubsonicTranscodeEnable() const { return _subsonicTranscodeEnable; }
|
||||||
AudioFormat getSubsonicTranscodeFormat() const { return _subsonicTranscodeFormat; }
|
AudioFormat getSubsonicTranscodeFormat() const { return _subsonicTranscodeFormat; }
|
||||||
Bitrate getSubsonicTranscodeBitrate() const { return _subsonicTranscodeBitrate; }
|
Bitrate getSubsonicTranscodeBitrate() const { return _subsonicTranscodeBitrate; }
|
||||||
@@ -233,7 +225,7 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
std::string _listenbrainzToken; // Musicbrainz Identifier
|
std::string _listenbrainzToken; // Musicbrainz Identifier
|
||||||
|
|
||||||
// Admin defined settings
|
// Admin defined settings
|
||||||
Type _type {Type::REGULAR};
|
UserType _type {UserType::REGULAR};
|
||||||
|
|
||||||
// User defined settings
|
// User defined settings
|
||||||
SubsonicArtistListMode _subsonicArtistListMode {defaultSubsonicArtistListMode};
|
SubsonicArtistListMode _subsonicArtistListMode {defaultSubsonicArtistListMode};
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap
|
|||||||
auto censorValue = [](const std::string& type, const std::string& value) -> std::string
|
auto censorValue = [](const std::string& type, const std::string& value) -> std::string
|
||||||
{
|
{
|
||||||
if (type == "p" || type == "password")
|
if (type == "p" || type == "password")
|
||||||
return "*SENSIBLE DATA*";
|
return "*REDACTED*";
|
||||||
else
|
else
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
@@ -221,24 +221,16 @@ checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username)
|
|||||||
|
|
||||||
static
|
static
|
||||||
void
|
void
|
||||||
checkUserIsAdmin(RequestContext& context)
|
checkUserTypeIsAllowed(RequestContext& context, EnumSet<Database::UserType> allowedUserTypes)
|
||||||
{
|
{
|
||||||
LMS_LOG(API_SUBSONIC, DEBUG) << "Check user is admin";
|
|
||||||
|
|
||||||
auto transaction {context.dbSession.createSharedTransaction()};
|
auto transaction {context.dbSession.createSharedTransaction()};
|
||||||
|
|
||||||
User::pointer currentUser {User::getById(context.dbSession, context.userId)};
|
User::pointer currentUser {User::getById(context.dbSession, context.userId)};
|
||||||
if (!currentUser)
|
if (!currentUser)
|
||||||
{
|
|
||||||
LMS_LOG(API_SUBSONIC, DEBUG) << "NOT FOUND";
|
|
||||||
throw RequestedDataNotFoundError {};
|
throw RequestedDataNotFoundError {};
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentUser->isAdmin())
|
if (!allowedUserTypes.contains(currentUser->getType()))
|
||||||
{
|
|
||||||
LMS_LOG(API_SUBSONIC, DEBUG) << "NOT ADMIN";
|
|
||||||
throw UserNotAuthorizedError {};
|
throw UserNotAuthorizedError {};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
@@ -567,7 +559,7 @@ handleChangePassword(RequestContext& context)
|
|||||||
|
|
||||||
Service<Auth::IPasswordService>::get()->setPassword(context.dbSession, userId, password);
|
Service<Auth::IPasswordService>::get()->setPassword(context.dbSession, userId, password);
|
||||||
}
|
}
|
||||||
catch (Auth::IPasswordService::PasswordTooWeakException&)
|
catch (Auth::PasswordTooWeakException&)
|
||||||
{
|
{
|
||||||
throw PasswordTooWeakGenericError {};
|
throw PasswordTooWeakGenericError {};
|
||||||
}
|
}
|
||||||
@@ -666,7 +658,7 @@ handleCreateUserRequest(RequestContext& context)
|
|||||||
{
|
{
|
||||||
Service<Auth::IPasswordService>::get()->setPassword(context.dbSession, userId, password);
|
Service<Auth::IPasswordService>::get()->setPassword(context.dbSession, userId, password);
|
||||||
}
|
}
|
||||||
catch (const Auth::IPasswordService::PasswordTooWeakException&)
|
catch (const Auth::PasswordTooWeakException&)
|
||||||
{
|
{
|
||||||
removeCreatedUser();
|
removeCreatedUser();
|
||||||
throw PasswordTooWeakGenericError {};
|
throw PasswordTooWeakGenericError {};
|
||||||
@@ -1719,7 +1711,7 @@ handleUpdateUserRequest(RequestContext& context)
|
|||||||
{
|
{
|
||||||
Service<::Auth::IPasswordService>()->setPassword(context.dbSession, userId, decodePasswordIfNeeded(*password));
|
Service<::Auth::IPasswordService>()->setPassword(context.dbSession, userId, decodePasswordIfNeeded(*password));
|
||||||
}
|
}
|
||||||
catch (const Auth::IPasswordService::PasswordTooWeakException&)
|
catch (const Auth::PasswordTooWeakException&)
|
||||||
{
|
{
|
||||||
throw PasswordTooWeakGenericError {};
|
throw PasswordTooWeakGenericError {};
|
||||||
}
|
}
|
||||||
@@ -1922,114 +1914,114 @@ using RequestHandlerFunc = std::function<Response(RequestContext& context)>;
|
|||||||
using CheckImplementedFunc = std::function<void()>;
|
using CheckImplementedFunc = std::function<void()>;
|
||||||
struct RequestEntryPointInfo
|
struct RequestEntryPointInfo
|
||||||
{
|
{
|
||||||
RequestHandlerFunc func;
|
RequestHandlerFunc func;
|
||||||
bool mustBeAdmin;
|
EnumSet<Database::UserType> allowedUserTypes {Database::UserType::DEMO, Database::UserType::REGULAR, Database::UserType::ADMIN};
|
||||||
CheckImplementedFunc checkFunc {};
|
CheckImplementedFunc checkFunc {};
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_map<std::string, RequestEntryPointInfo> requestEntryPoints
|
static std::unordered_map<std::string, RequestEntryPointInfo> requestEntryPoints
|
||||||
{
|
{
|
||||||
// System
|
// System
|
||||||
{"ping", {handlePingRequest, false}},
|
{"ping", {handlePingRequest}},
|
||||||
{"getLicense", {handleGetLicenseRequest, false}},
|
{"getLicense", {handleGetLicenseRequest}},
|
||||||
|
|
||||||
// Browsing
|
// Browsing
|
||||||
{"getMusicFolders", {handleGetMusicFoldersRequest, false}},
|
{"getMusicFolders", {handleGetMusicFoldersRequest}},
|
||||||
{"getIndexes", {handleGetIndexesRequest, false}},
|
{"getIndexes", {handleGetIndexesRequest}},
|
||||||
{"getMusicDirectory", {handleGetMusicDirectoryRequest, false}},
|
{"getMusicDirectory", {handleGetMusicDirectoryRequest}},
|
||||||
{"getGenres", {handleGetGenresRequest, false}},
|
{"getGenres", {handleGetGenresRequest}},
|
||||||
{"getArtists", {handleGetArtistsRequest, false}},
|
{"getArtists", {handleGetArtistsRequest}},
|
||||||
{"getArtist", {handleGetArtistRequest, false}},
|
{"getArtist", {handleGetArtistRequest}},
|
||||||
{"getAlbum", {handleGetAlbumRequest, false}},
|
{"getAlbum", {handleGetAlbumRequest}},
|
||||||
{"getSong", {handleNotImplemented, false}},
|
{"getSong", {handleNotImplemented}},
|
||||||
{"getVideos", {handleNotImplemented, false}},
|
{"getVideos", {handleNotImplemented}},
|
||||||
{"getArtistInfo", {handleGetArtistInfoRequest, false}},
|
{"getArtistInfo", {handleGetArtistInfoRequest}},
|
||||||
{"getArtistInfo2", {handleGetArtistInfo2Request, false}},
|
{"getArtistInfo2", {handleGetArtistInfo2Request}},
|
||||||
{"getAlbumInfo", {handleNotImplemented, false}},
|
{"getAlbumInfo", {handleNotImplemented}},
|
||||||
{"getAlbumInfo2", {handleNotImplemented, false}},
|
{"getAlbumInfo2", {handleNotImplemented}},
|
||||||
{"getSimilarSongs", {handleGetSimilarSongsRequest, false}},
|
{"getSimilarSongs", {handleGetSimilarSongsRequest}},
|
||||||
{"getSimilarSongs2", {handleGetSimilarSongs2Request, false}},
|
{"getSimilarSongs2", {handleGetSimilarSongs2Request}},
|
||||||
{"getTopSongs", {handleNotImplemented, false}},
|
{"getTopSongs", {handleNotImplemented}},
|
||||||
|
|
||||||
// Album/song lists
|
// Album/song lists
|
||||||
{"getAlbumList", {handleGetAlbumListRequest, false}},
|
{"getAlbumList", {handleGetAlbumListRequest}},
|
||||||
{"getAlbumList2", {handleGetAlbumList2Request, false}},
|
{"getAlbumList2", {handleGetAlbumList2Request}},
|
||||||
{"getRandomSongs", {handleGetRandomSongsRequest, false}},
|
{"getRandomSongs", {handleGetRandomSongsRequest}},
|
||||||
{"getSongsByGenre", {handleGetSongsByGenreRequest, false}},
|
{"getSongsByGenre", {handleGetSongsByGenreRequest}},
|
||||||
{"getNowPlaying", {handleNotImplemented, false}},
|
{"getNowPlaying", {handleNotImplemented}},
|
||||||
{"getStarred", {handleGetStarredRequest, false}},
|
{"getStarred", {handleGetStarredRequest}},
|
||||||
{"getStarred2", {handleGetStarred2Request, false}},
|
{"getStarred2", {handleGetStarred2Request}},
|
||||||
|
|
||||||
// Searching
|
// Searching
|
||||||
{"search", {handleNotImplemented, false}},
|
{"search", {handleNotImplemented}},
|
||||||
{"search2", {handleSearch2Request, false}},
|
{"search2", {handleSearch2Request}},
|
||||||
{"search3", {handleSearch3Request, false}},
|
{"search3", {handleSearch3Request}},
|
||||||
|
|
||||||
// Playlists
|
// Playlists
|
||||||
{"getPlaylists", {handleGetPlaylistsRequest, false}},
|
{"getPlaylists", {handleGetPlaylistsRequest}},
|
||||||
{"getPlaylist", {handleGetPlaylistRequest, false}},
|
{"getPlaylist", {handleGetPlaylistRequest}},
|
||||||
{"createPlaylist", {handleCreatePlaylistRequest, false}},
|
{"createPlaylist", {handleCreatePlaylistRequest}},
|
||||||
{"updatePlaylist", {handleUpdatePlaylistRequest, false}},
|
{"updatePlaylist", {handleUpdatePlaylistRequest}},
|
||||||
{"deletePlaylist", {handleDeletePlaylistRequest, false}},
|
{"deletePlaylist", {handleDeletePlaylistRequest}},
|
||||||
|
|
||||||
// Media retrieval
|
// Media retrieval
|
||||||
{"hls", {handleNotImplemented, false}},
|
{"hls", {handleNotImplemented}},
|
||||||
{"getCaptions", {handleNotImplemented, false}},
|
{"getCaptions", {handleNotImplemented}},
|
||||||
{"getLyrics", {handleNotImplemented, false}},
|
{"getLyrics", {handleNotImplemented}},
|
||||||
{"getAvatar", {handleNotImplemented, false}},
|
{"getAvatar", {handleNotImplemented}},
|
||||||
|
|
||||||
// Media annotation
|
// Media annotation
|
||||||
{"star", {handleStarRequest, false}},
|
{"star", {handleStarRequest}},
|
||||||
{"unstar", {handleUnstarRequest, false}},
|
{"unstar", {handleUnstarRequest}},
|
||||||
{"setRating", {handleNotImplemented, false}},
|
{"setRating", {handleNotImplemented}},
|
||||||
{"scrobble", {handleScrobble, false}},
|
{"scrobble", {handleScrobble}},
|
||||||
|
|
||||||
// Sharing
|
// Sharing
|
||||||
{"getShares", {handleNotImplemented, false}},
|
{"getShares", {handleNotImplemented}},
|
||||||
{"createShares", {handleNotImplemented, false}},
|
{"createShares", {handleNotImplemented}},
|
||||||
{"updateShare", {handleNotImplemented, false}},
|
{"updateShare", {handleNotImplemented}},
|
||||||
{"deleteShare", {handleNotImplemented, false}},
|
{"deleteShare", {handleNotImplemented}},
|
||||||
|
|
||||||
// Podcast
|
// Podcast
|
||||||
{"getPodcasts", {handleNotImplemented, false}},
|
{"getPodcasts", {handleNotImplemented}},
|
||||||
{"getNewestPodcasts", {handleNotImplemented, false}},
|
{"getNewestPodcasts", {handleNotImplemented}},
|
||||||
{"refreshPodcasts", {handleNotImplemented, false}},
|
{"refreshPodcasts", {handleNotImplemented}},
|
||||||
{"createPodcastChannel", {handleNotImplemented, false}},
|
{"createPodcastChannel", {handleNotImplemented}},
|
||||||
{"deletePodcastChannel", {handleNotImplemented, false}},
|
{"deletePodcastChannel", {handleNotImplemented}},
|
||||||
{"deletePodcastEpisode", {handleNotImplemented, false}},
|
{"deletePodcastEpisode", {handleNotImplemented}},
|
||||||
{"downloadPodcastEpisode", {handleNotImplemented, false}},
|
{"downloadPodcastEpisode", {handleNotImplemented}},
|
||||||
|
|
||||||
// Jukebox
|
// Jukebox
|
||||||
{"jukeboxControl", {handleNotImplemented, false}},
|
{"jukeboxControl", {handleNotImplemented}},
|
||||||
|
|
||||||
// Internet radio
|
// Internet radio
|
||||||
{"getInternetRadioStations", {handleNotImplemented, false}},
|
{"getInternetRadioStations", {handleNotImplemented}},
|
||||||
{"createInternetRadioStation", {handleNotImplemented, false}},
|
{"createInternetRadioStation", {handleNotImplemented}},
|
||||||
{"updateInternetRadioStation", {handleNotImplemented, false}},
|
{"updateInternetRadioStation", {handleNotImplemented}},
|
||||||
{"deleteInternetRadioStation", {handleNotImplemented, false}},
|
{"deleteInternetRadioStation", {handleNotImplemented}},
|
||||||
|
|
||||||
// Chat
|
// Chat
|
||||||
{"getChatMessages", {handleNotImplemented, false}},
|
{"getChatMessages", {handleNotImplemented}},
|
||||||
{"addChatMessages", {handleNotImplemented, false}},
|
{"addChatMessages", {handleNotImplemented}},
|
||||||
|
|
||||||
// User management
|
// User management
|
||||||
{"getUser", {handleGetUserRequest, false}},
|
{"getUser", {handleGetUserRequest}},
|
||||||
{"getUsers", {handleGetUsersRequest, true}},
|
{"getUsers", {handleGetUsersRequest, {Database::UserType::ADMIN}}},
|
||||||
{"createUser", {handleCreateUserRequest, true, &checkSetPasswordImplemented}},
|
{"createUser", {handleCreateUserRequest, {Database::UserType::ADMIN}, &checkSetPasswordImplemented}},
|
||||||
{"updateUser", {handleUpdateUserRequest, true}},
|
{"updateUser", {handleUpdateUserRequest, {Database::UserType::ADMIN}}},
|
||||||
{"deleteUser", {handleDeleteUserRequest, true}},
|
{"deleteUser", {handleDeleteUserRequest, {Database::UserType::ADMIN}}},
|
||||||
{"changePassword", {handleChangePassword, false, &checkSetPasswordImplemented}},
|
{"changePassword", {handleChangePassword, {Database::UserType::REGULAR, Database::UserType::ADMIN}, &checkSetPasswordImplemented}},
|
||||||
|
|
||||||
// Bookmarks
|
// Bookmarks
|
||||||
{"getBookmarks", {handleGetBookmarks, false}},
|
{"getBookmarks", {handleGetBookmarks}},
|
||||||
{"createBookmark", {handleCreateBookmark, false}},
|
{"createBookmark", {handleCreateBookmark}},
|
||||||
{"deleteBookmark", {handleDeleteBookmark, false}},
|
{"deleteBookmark", {handleDeleteBookmark}},
|
||||||
{"getPlayQueue", {handleNotImplemented, false}},
|
{"getPlayQueue", {handleNotImplemented}},
|
||||||
{"savePlayQueue", {handleNotImplemented, false}},
|
{"savePlayQueue", {handleNotImplemented}},
|
||||||
|
|
||||||
// Media library scanning
|
// Media library scanning
|
||||||
{"getScanStatus", {Scan::handleGetScanStatus, true}},
|
{"getScanStatus", {Scan::handleGetScanStatus, {Database::UserType::ADMIN}}},
|
||||||
{"startScan", {Scan::handleStartScan, true}},
|
{"startScan", {Scan::handleStartScan, {Database::UserType::ADMIN}}},
|
||||||
};
|
};
|
||||||
|
|
||||||
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
|
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
|
||||||
@@ -2112,8 +2104,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
|
|||||||
if (itEntryPoint->second.checkFunc)
|
if (itEntryPoint->second.checkFunc)
|
||||||
itEntryPoint->second.checkFunc();
|
itEntryPoint->second.checkFunc();
|
||||||
|
|
||||||
if (itEntryPoint->second.mustBeAdmin)
|
checkUserTypeIsAllowed(requestContext, itEntryPoint->second.allowedUserTypes);
|
||||||
checkUserIsAdmin(requestContext);
|
|
||||||
|
|
||||||
Response resp {(itEntryPoint->second.func)(requestContext)};
|
Response resp {(itEntryPoint->second.func)(requestContext)};
|
||||||
|
|
||||||
|
|||||||
@@ -152,6 +152,11 @@ class PasswordTooWeakGenericError : public GenericError
|
|||||||
std::string getMessage() const override { return "Password too weak"; }
|
std::string getMessage() const override { return "Password too weak"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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"; }
|
std::string getMessage() const override { return "User already exists"; }
|
||||||
|
|||||||
@@ -116,20 +116,12 @@ LmsApplication::isUserAuthStrong() const
|
|||||||
return _authenticatedUser->strongAuth;
|
return _authenticatedUser->strongAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
Database::UserType
|
||||||
LmsApplication::isUserAdmin()
|
LmsApplication::getUserType()
|
||||||
{
|
{
|
||||||
auto transaction {getDbSession().createSharedTransaction()};
|
auto transaction {getDbSession().createSharedTransaction()};
|
||||||
|
|
||||||
return getUser()->isAdmin();
|
return getUser()->getType();
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
LmsApplication::isUserDemo()
|
|
||||||
{
|
|
||||||
auto transaction {getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
return getUser()->isDemo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
@@ -458,7 +450,7 @@ LmsApplication::onUserLoggedIn()
|
|||||||
// Only one active session by user
|
// Only one active session by user
|
||||||
if (otherApplication.getUserId() == getUserId())
|
if (otherApplication.getUserId() == getUserId())
|
||||||
{
|
{
|
||||||
if (!LmsApp->isUserDemo())
|
if (LmsApp->getUserType() != Database::UserType::DEMO)
|
||||||
{
|
{
|
||||||
quit(Wt::WString::tr("Lms.quit-other-session"));
|
quit(Wt::WString::tr("Lms.quit-other-session"));
|
||||||
}
|
}
|
||||||
@@ -502,7 +494,7 @@ LmsApplication::createHome()
|
|||||||
Wt::WLineEdit* searchEdit {main->bindNew<Wt::WLineEdit>("search")};
|
Wt::WLineEdit* searchEdit {main->bindNew<Wt::WLineEdit>("search")};
|
||||||
searchEdit->setPlaceholderText(Wt::WString::tr("Lms.Explore.Search.search-placeholder"));
|
searchEdit->setPlaceholderText(Wt::WString::tr("Lms.Explore.Search.search-placeholder"));
|
||||||
|
|
||||||
if (isUserAdmin())
|
if (LmsApp->getUserType() == Database::UserType::ADMIN)
|
||||||
{
|
{
|
||||||
main->setCondition("if-is-admin", true);
|
main->setCondition("if-is-admin", true);
|
||||||
main->bindNew<Wt::WAnchor>("database", Wt::WLink {Wt::LinkType::InternalPath, "/admin/database"}, Wt::WString::tr("Lms.Admin.Database.menu-database"));
|
main->bindNew<Wt::WAnchor>("database", Wt::WLink {Wt::LinkType::InternalPath, "/admin/database"}, Wt::WString::tr("Lms.Admin.Database.menu-database"));
|
||||||
@@ -530,7 +522,7 @@ LmsApplication::createHome()
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Admin stuff
|
// Admin stuff
|
||||||
if (isUserAdmin())
|
if (getUserType() == Database::UserType::ADMIN)
|
||||||
{
|
{
|
||||||
mainStack->addNew<DatabaseSettingsView>();
|
mainStack->addNew<DatabaseSettingsView>();
|
||||||
mainStack->addNew<UsersView>();
|
mainStack->addNew<UsersView>();
|
||||||
@@ -581,7 +573,8 @@ LmsApplication::createHome()
|
|||||||
_mediaPlayer->stop();
|
_mediaPlayer->stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isUserAdmin())
|
const bool isAdmin {getUserType() == Database::UserType::ADMIN};
|
||||||
|
if (isAdmin)
|
||||||
{
|
{
|
||||||
_scannerEvents.scanComplete.connect([=] (const Scanner::ScanStats& stats)
|
_scannerEvents.scanComplete.connect([=] (const Scanner::ScanStats& stats)
|
||||||
{
|
{
|
||||||
@@ -597,10 +590,10 @@ LmsApplication::createHome()
|
|||||||
|
|
||||||
internalPathChanged().connect([=]
|
internalPathChanged().connect([=]
|
||||||
{
|
{
|
||||||
handlePathChange(*mainStack, isUserAdmin());
|
handlePathChange(*mainStack, isAdmin);
|
||||||
});
|
});
|
||||||
|
|
||||||
handlePathChange(*mainStack, isUserAdmin());
|
handlePathChange(*mainStack, isAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -65,9 +65,8 @@ class LmsApplication : public Wt::WApplication
|
|||||||
Wt::Dbo::ptr<Database::User> getUser();
|
Wt::Dbo::ptr<Database::User> getUser();
|
||||||
Database::IdType getUserId();
|
Database::IdType getUserId();
|
||||||
bool isUserAuthStrong() const; // user must be logged in prior this call
|
bool isUserAuthStrong() const; // user must be logged in prior this call
|
||||||
bool isUserAdmin(); // user must be logged in prior this call
|
Database::UserType getUserType(); // user must be logged in prior this call
|
||||||
bool isUserDemo(); // user must be logged in prior this call
|
std::string getUserLoginName(); // user must be logged in prior this call
|
||||||
std::string getUserLoginName(); // user must be logged in prior this call
|
|
||||||
|
|
||||||
// Proxified scanner events
|
// Proxified scanner events
|
||||||
Scanner::Events& getScannerEvents() { return _scannerEvents; }
|
Scanner::Events& getScannerEvents() { return _scannerEvents; }
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
addField(PasswordField);
|
addField(PasswordField);
|
||||||
setValidator(PasswordField, createPasswordStrengthValidator(LmsApp->getUserLoginName()));
|
setValidator(PasswordField, createPasswordStrengthValidator([] { return ::Auth::PasswordValidationContext {LmsApp->getUserLoginName(), LmsApp->getUserType()}; }));
|
||||||
addField(PasswordConfirmField);
|
addField(PasswordConfirmField);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class InitWizardModel : public Wt::WFormModel
|
|||||||
addField(PasswordConfirmField);
|
addField(PasswordConfirmField);
|
||||||
|
|
||||||
setValidator(AdminLoginField, createLoginNameValidator());
|
setValidator(AdminLoginField, createLoginNameValidator());
|
||||||
setValidator(PasswordField, createPasswordStrengthValidator([this] { return valueText(AdminLoginField).toUTF8(); }));
|
setValidator(PasswordField, createPasswordStrengthValidator([this] { return ::Auth::PasswordValidationContext {valueText(AdminLoginField).toUTF8(), Database::UserType::ADMIN}; }));
|
||||||
validator(PasswordField)->setMandatory(true);
|
validator(PasswordField)->setMandatory(true);
|
||||||
setValidator(PasswordConfirmField, createMandatoryValidator());
|
setValidator(PasswordConfirmField, createMandatoryValidator());
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ class InitWizardModel : public Wt::WFormModel
|
|||||||
throw LmsException {"Admin user already created"};
|
throw LmsException {"Admin user already created"};
|
||||||
|
|
||||||
Database::User::pointer user {Database::User::create(LmsApp->getDbSession(), valueText(AdminLoginField).toUTF8())};
|
Database::User::pointer user {Database::User::create(LmsApp->getDbSession(), valueText(AdminLoginField).toUTF8())};
|
||||||
user.modify()->setType(Database::User::Type::ADMIN);
|
user.modify()->setType(Database::UserType::ADMIN);
|
||||||
Service<::Auth::IPasswordService>::get()->setPassword(LmsApp->getDbSession(), user.id(), valueText(PasswordField).toUTF8());
|
Service<::Auth::IPasswordService>::get()->setPassword(LmsApp->getDbSession(), user.id(), valueText(PasswordField).toUTF8());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class UserModel : public Wt::WFormModel
|
|||||||
if (authPasswordService)
|
if (authPasswordService)
|
||||||
{
|
{
|
||||||
addField(PasswordField);
|
addField(PasswordField);
|
||||||
setValidator(PasswordField, createPasswordStrengthValidator([this] { return getLoginName(); }));
|
setValidator(PasswordField, createPasswordStrengthValidator([this] { return ::Auth::PasswordValidationContext {getLoginName(), Wt::asNumber(value(DemoField)) ? UserType::DEMO : UserType::REGULAR}; }));
|
||||||
if (!userId)
|
if (!userId)
|
||||||
validator(PasswordField)->setMandatory(true);
|
validator(PasswordField)->setMandatory(true);
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ class UserModel : public Wt::WFormModel
|
|||||||
user = Database::User::create(LmsApp->getDbSession(), valueText(LoginField).toUTF8());
|
user = Database::User::create(LmsApp->getDbSession(), valueText(LoginField).toUTF8());
|
||||||
|
|
||||||
if (Wt::asNumber(value(DemoField)))
|
if (Wt::asNumber(value(DemoField)))
|
||||||
user.modify()->setType(Database::User::Type::DEMO);
|
user.modify()->setType(Database::UserType::DEMO);
|
||||||
|
|
||||||
if (_authPasswordService)
|
if (_authPasswordService)
|
||||||
_authPasswordService->setPassword(LmsApp->getDbSession(), user.id(), valueText(PasswordField).toUTF8());
|
_authPasswordService->setPassword(LmsApp->getDbSession(), user.id(), valueText(PasswordField).toUTF8());
|
||||||
@@ -159,6 +159,9 @@ class UserModel : public Wt::WFormModel
|
|||||||
}
|
}
|
||||||
else if (field == PasswordField)
|
else if (field == PasswordField)
|
||||||
{
|
{
|
||||||
|
if (Wt::asNumber(value(DemoField)))
|
||||||
|
setValidator(PasswordField, {});
|
||||||
|
|
||||||
validatePassword(error);
|
validatePassword(error);
|
||||||
}
|
}
|
||||||
else if (field == DemoField)
|
else if (field == DemoField)
|
||||||
@@ -172,7 +175,7 @@ class UserModel : public Wt::WFormModel
|
|||||||
if (error.empty())
|
if (error.empty())
|
||||||
return Wt::WFormModel::validateField(field);
|
return Wt::WFormModel::validateField(field);
|
||||||
|
|
||||||
setValidation(field, Wt::WValidator::Result( Wt::ValidationState::Invalid, error));
|
setValidation(field, Wt::WValidator::Result {Wt::ValidationState::Invalid, error});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,14 @@ namespace UserInterface
|
|||||||
class PasswordStrengthValidator : public Wt::WValidator
|
class PasswordStrengthValidator : public Wt::WValidator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PasswordStrengthValidator(LoginNameGetFunc loginNameGetFunc) : _loginNameGetFunc {std::move(loginNameGetFunc)} {}
|
PasswordStrengthValidator(PasswordValidationContextGetFunc passwordValidationContextGetFunc)
|
||||||
|
: _passwordValidationContextGetFunc {std::move(passwordValidationContextGetFunc)}
|
||||||
Wt::WValidator::Result validate(const Wt::WString& input) const override;
|
{}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LoginNameGetFunc _loginNameGetFunc;
|
Wt::WValidator::Result validate(const Wt::WString& input) const override;
|
||||||
|
|
||||||
|
PasswordValidationContextGetFunc _passwordValidationContextGetFunc;
|
||||||
};
|
};
|
||||||
|
|
||||||
Wt::WValidator::Result
|
Wt::WValidator::Result
|
||||||
@@ -44,21 +46,18 @@ namespace UserInterface
|
|||||||
if (input.empty())
|
if (input.empty())
|
||||||
return Wt::WValidator::validate(input);
|
return Wt::WValidator::validate(input);
|
||||||
|
|
||||||
if (Service<::Auth::IPasswordService>::get()->isPasswordSecureEnough(_loginNameGetFunc(), input.toUTF8()))
|
const ::Auth::PasswordValidationContext context {_passwordValidationContextGetFunc()};
|
||||||
|
|
||||||
|
if (Service<::Auth::IPasswordService>::get()->isPasswordSecureEnough(input.toUTF8(), context))
|
||||||
return Wt::WValidator::Result {Wt::ValidationState::Valid};
|
return Wt::WValidator::Result {Wt::ValidationState::Valid};
|
||||||
|
|
||||||
return Wt::WValidator::Result {Wt::ValidationState::Invalid, Wt::WString::tr("Lms.password-too-weak")};
|
return Wt::WValidator::Result {Wt::ValidationState::Invalid, Wt::WString::tr("Lms.password-too-weak")};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Wt::WValidator>
|
std::shared_ptr<Wt::WValidator>
|
||||||
createPasswordStrengthValidator(std::string_view loginName)
|
createPasswordStrengthValidator(PasswordValidationContextGetFunc passwordValidationContextGetFunc)
|
||||||
{
|
{
|
||||||
return std::make_shared<PasswordStrengthValidator>([loginName = std::string {loginName}] { return loginName; });
|
return std::make_shared<PasswordStrengthValidator>(std::move(passwordValidationContextGetFunc));
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Wt::WValidator> createPasswordStrengthValidator(LoginNameGetFunc loginNameGetFunc)
|
|
||||||
{
|
|
||||||
return std::make_shared<PasswordStrengthValidator>(std::move(loginNameGetFunc));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PasswordCheckValidator : public Wt::WValidator
|
class PasswordCheckValidator : public Wt::WValidator
|
||||||
|
|||||||
@@ -19,13 +19,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <Wt/WValidator.h>
|
#include <Wt/WValidator.h>
|
||||||
|
|
||||||
|
#include "database/Types.hpp"
|
||||||
|
#include "auth/Types.hpp"
|
||||||
|
|
||||||
namespace UserInterface
|
namespace UserInterface
|
||||||
{
|
{
|
||||||
std::shared_ptr<Wt::WValidator> createPasswordStrengthValidator(std::string_view loginName);
|
using PasswordValidationContextGetFunc = std::function<::Auth::PasswordValidationContext()>;
|
||||||
using LoginNameGetFunc = std::function<std::string()>;
|
std::shared_ptr<Wt::WValidator> createPasswordStrengthValidator(PasswordValidationContextGetFunc passwordValidationContextGetFunc);
|
||||||
std::shared_ptr<Wt::WValidator> createPasswordStrengthValidator(LoginNameGetFunc loginNameGetFunc);
|
|
||||||
|
|
||||||
// Check current user password
|
// Check current user password
|
||||||
std::shared_ptr<Wt::WValidator> createPasswordCheckValidator();
|
std::shared_ptr<Wt::WValidator> createPasswordCheckValidator();
|
||||||
|
|||||||
Reference in New Issue
Block a user