Properly handled http headers for subsonic API. ref #119

This commit is contained in:
emeric
2021-03-05 13:23:47 +01:00
parent cc28d893f5
commit 1b49e8f6a6
13 changed files with 149 additions and 102 deletions
+2 -2
View File
@@ -132,8 +132,8 @@ If a setting is not present in the configuration file, a hardcoded default value
You can define which authentication backend to be used thanks to the `authentication-backend` option: You can define which authentication backend to be used thanks to the `authentication-backend` option:
* `internal` (default): _LMS_ uses an internal database to store users and their associated passwords (salted and hashed using [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt)). Only the admin user can create, edit or remove other users. * `internal` (default): _LMS_ uses an internal database to store users and their associated passwords (salted and hashed using [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt)). Only the admin user can create, edit or remove other users.
* `PAM`: the authentication request is forwarded to PAM (see the [default configuration file](conf/pam/lms)). * `PAM`: the user/password authentication request is forwarded to PAM (see the [default configuration file](conf/pam/lms)).
* `http-headers`: _LMS_ uses a configurable HTTP header field, typically set by a reverse proxy to handle [SSO](https://en.wikipedia.org/wiki/Single_sign-on), to extract the login name. You can customize the field to be used using the `http-headers-user-field` option. * `http-headers`: _LMS_ uses a configurable HTTP header field, typically set by a reverse proxy to handle [SSO](https://en.wikipedia.org/wiki/Single_sign-on), to extract the login name. You can customize the field to be used using the `http-headers-login-field` option.
__Note__: the first created user is the admin user __Note__: the first created user is the admin user
-1
View File
@@ -28,7 +28,6 @@
<!--Errors--> <!--Errors-->
<message id="Lms.Error.artist-not-found">Artist not found</message> <message id="Lms.Error.artist-not-found">Artist not found</message>
<message id="Lms.Error.error-occurred">Error occured!</message> <message id="Lms.Error.error-occurred">Error occured!</message>
<message id="Lms.Error.deployment-error">Deployment error</message>
<message id="Lms.Error.go-home">Go home</message> <message id="Lms.Error.go-home">Go home</message>
<message id="Lms.Error.release-not-found">Release not found</message> <message id="Lms.Error.release-not-found">Release not found</message>
<message id="Lms.Error.user-not-allowed">You are not allowed to perform this operation</message> <message id="Lms.Error.user-not-allowed">You are not allowed to perform this operation</message>
-1
View File
@@ -28,7 +28,6 @@
<!--Errors--> <!--Errors-->
<message id="Lms.Error.artist-not-found">Cet artiste n'existe pas</message> <message id="Lms.Error.artist-not-found">Cet artiste n'existe pas</message>
<message id="Lms.Error.error-occurred">Une erreur est survenue!</message> <message id="Lms.Error.error-occurred">Une erreur est survenue!</message>
<message id="Lms.Error.deployment-error">Erreur de déploiement</message>
<message id="Lms.Error.go-home">Retour à l'accueil</message> <message id="Lms.Error.go-home">Retour à l'accueil</message>
<message id="Lms.Error.release-not-found">Cet album n'existe pas</message> <message id="Lms.Error.release-not-found">Cet album n'existe pas</message>
<message id="Lms.Error.user-not-allowed">Vous n'avez pas les droits pour effectuer cette opération</message> <message id="Lms.Error.user-not-allowed">Vous n'avez pas les droits pour effectuer cette opération</message>
+1 -1
View File
@@ -40,7 +40,7 @@ acousticbrainz-api-url = "https://acousticbrainz.org/api/v1/";
# Authentication # Authentication
# Available backends: "internal", "PAM", "http-headers" # Available backends: "internal", "PAM", "http-headers"
authentication-backend = "internal"; authentication-backend = "internal";
http-headers-user-field = "X-Forwarded-User"; http-headers-login-field = "X-Forwarded-User";
# Max entries in the login throttler (1 entry per IP address. For IPv6, the whole /64 block is used) # Max entries in the login throttler (1 entry per IP address. For IPv6, the whole /64 block is used)
login-throttler-max-entries = 10000; login-throttler-max-entries = 10000;
@@ -29,7 +29,7 @@ namespace Auth
{ {
HttpHeadersEnvService::HttpHeadersEnvService() HttpHeadersEnvService::HttpHeadersEnvService()
: _fieldName {Service<IConfig>::get()->getString("http-headers-field-name", "X-Forwarded-User")} : _fieldName {Service<IConfig>::get()->getString("http-headers-login-field", "X-Forwarded-User")}
{ {
LMS_LOG(AUTH, INFO) << "Using http header field = '" << _fieldName << "'"; LMS_LOG(AUTH, INFO) << "Using http header field = '" << _fieldName << "'";
} }
@@ -37,7 +37,21 @@ namespace Auth
HttpHeadersEnvService::CheckResult HttpHeadersEnvService::CheckResult
HttpHeadersEnvService::processEnv(Database::Session& session, const Wt::WEnvironment& env) HttpHeadersEnvService::processEnv(Database::Session& session, const Wt::WEnvironment& env)
{ {
const std::string loginName { env.headerValue(_fieldName)}; const std::string loginName {env.headerValue(_fieldName)};
if (loginName.empty())
return {CheckResult::State::Denied};
LMS_LOG(AUTH, DEBUG) << "Extracted login name = '" << loginName << "' from HTTP header";
const Database::IdType userId {getOrCreateUser(session, loginName)};
onUserAuthenticated(session, userId);
return {CheckResult::State::Granted, userId};
}
HttpHeadersEnvService::CheckResult
HttpHeadersEnvService::processRequest(Database::Session& session, const Wt::Http::Request& request)
{
const std::string loginName {request.headerValue(_fieldName)};
if (loginName.empty()) if (loginName.empty())
return {CheckResult::State::Denied}; return {CheckResult::State::Denied};
@@ -31,6 +31,7 @@ namespace Auth
private: private:
CheckResult processEnv(Database::Session& session, const Wt::WEnvironment& env) override; CheckResult processEnv(Database::Session& session, const Wt::WEnvironment& env) override;
CheckResult processRequest(Database::Session& session, const Wt::Http::Request& request) override;
std::string _fieldName; std::string _fieldName;
}; };
@@ -34,6 +34,11 @@ namespace Wt
class WEnvironment; class WEnvironment;
} }
namespace Wt::Http
{
class Request;
}
namespace Auth namespace Auth
{ {
class IEnvService class IEnvService
@@ -56,6 +61,7 @@ namespace Auth
}; };
virtual CheckResult processEnv(Database::Session& session, const Wt::WEnvironment& env) = 0; virtual CheckResult processEnv(Database::Session& session, const Wt::WEnvironment& env) = 0;
virtual CheckResult processRequest(Database::Session& session, const Wt::Http::Request& request) = 0;
}; };
std::unique_ptr<IEnvService> createEnvService(std::string_view backendName); std::unique_ptr<IEnvService> createEnvService(std::string_view backendName);
+2 -4
View File
@@ -22,8 +22,8 @@
#include <string> #include <string>
#include <Wt/Http/Request.h> #include <Wt/Http/Request.h>
#include <Wt/Http/ResponseContinuation.h>
#include "database/Types.hpp"
namespace Database namespace Database
{ {
@@ -32,14 +32,12 @@ namespace Database
namespace API::Subsonic namespace API::Subsonic
{ {
struct RequestContext struct RequestContext
{ {
const Wt::Http::ParameterMap& parameters; const Wt::Http::ParameterMap& parameters;
Database::Session& dbSession; Database::Session& dbSession;
std::string userName; Database::IdType userId;
std::string clientName; std::string clientName;
}; };
} }
+1 -1
View File
@@ -81,7 +81,7 @@ getStreamParameters(RequestContext& context)
} }
{ {
const User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; const User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
+96 -63
View File
@@ -26,6 +26,7 @@
#include <Wt/WLocalDateTime.h> #include <Wt/WLocalDateTime.h>
#include "auth/IPasswordService.hpp" #include "auth/IPasswordService.hpp"
#include "auth/IEnvService.hpp"
#include "cover/ICoverArtGrabber.hpp" #include "cover/ICoverArtGrabber.hpp"
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
@@ -209,14 +210,33 @@ static
void void
checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username) checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username)
{ {
if (username != context.userName) User::pointer currentUser {User::getById(context.dbSession, context.userId)};
{ if (!currentUser)
User::pointer currentUser {User::getByLoginName(context.dbSession, context.userName)}; throw RequestedDataNotFoundError {};
if (!currentUser)
throw RequestedDataNotFoundError {};
if (!currentUser->isAdmin()) if (currentUser->getLoginName() != username && !currentUser->isAdmin())
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
}
static
void
checkUserIsAdmin(RequestContext& context)
{
LMS_LOG(API_SUBSONIC, DEBUG) << "Check user is admin";
auto transaction {context.dbSession.createSharedTransaction()};
User::pointer currentUser {User::getById(context.dbSession, context.userId)};
if (!currentUser)
{
LMS_LOG(API_SUBSONIC, DEBUG) << "NOT FOUND";
throw RequestedDataNotFoundError {};
}
if (!currentUser->isAdmin())
{
LMS_LOG(API_SUBSONIC, DEBUG) << "NOT ADMIN";
throw UserNotAuthorizedError {};
} }
} }
@@ -327,7 +347,6 @@ trackToResponseNode(const Track::pointer& track, Session& dbSession, const User:
trackResponse.setAttribute("coverArt", IdToString({Id::Type::Track, track.id()})); trackResponse.setAttribute("coverArt", IdToString({Id::Type::Track, track.id()}));
const std::vector<Artist::pointer>& artists {track->getArtists({TrackArtistLinkType::Artist})}; const std::vector<Artist::pointer>& artists {track->getArtists({TrackArtistLinkType::Artist})};
LMS_LOG(API_SUBSONIC, DEBUG) << "Artists count = " << artists.size();
if (!artists.empty()) if (!artists.empty())
{ {
trackResponse.setAttribute("artist", getArtistNames(artists)); trackResponse.setAttribute("artist", getArtistNames(artists));
@@ -573,7 +592,7 @@ handleCreatePlaylistRequest(RequestContext& context)
auto transaction {context.dbSession.createUniqueTransaction()}; auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -629,12 +648,12 @@ handleCreateUserRequest(RequestContext& context)
} }
auto removeCreatedUser {[&]() auto removeCreatedUser {[&]()
{ {
auto transaction {context.dbSession.createUniqueTransaction()}; auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::getById(context.dbSession, userId)}; User::pointer user {User::getById(context.dbSession, userId)};
if (user) if (user)
user.remove(); user.remove();
}}; }};
try try
{ {
@@ -664,7 +683,7 @@ handleDeletePlaylistRequest(RequestContext& context)
auto transaction {context.dbSession.createUniqueTransaction()}; auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -687,16 +706,16 @@ handleDeleteUserRequest(RequestContext& context)
{ {
std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")}; std::string username {getMandatoryParameterAs<std::string>(context.parameters, "username")};
// cannot delete ourself
if (username == context.userName)
throw UserNotAuthorizedError {};
auto transaction {context.dbSession.createUniqueTransaction()}; auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, username)}; User::pointer user {User::getByLoginName(context.dbSession, username)};
if (!user) if (!user)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError {};
// cannot delete ourself
if (user.id() == context.userId)
throw UserNotAuthorizedError {};
user.remove(); user.remove();
return Response::createOkResponse(context); return Response::createOkResponse(context);
@@ -726,7 +745,7 @@ handleGetRandomSongsRequest(RequestContext& context)
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -758,7 +777,7 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -860,7 +879,7 @@ handleGetAlbumRequest(RequestContext& context)
if (!release) if (!release)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError {};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -892,7 +911,7 @@ handleGetArtistRequest(RequestContext& context)
if (!artist) if (!artist)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError {};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -943,7 +962,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
{ {
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -987,7 +1006,7 @@ handleGetArtistsRequest(RequestContext& context)
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1031,7 +1050,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1137,7 +1156,7 @@ handleGetIndexesRequest(RequestContext& context)
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1190,7 +1209,7 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
if (!artist) if (!artist)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError {};
const User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; const User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1239,7 +1258,7 @@ handleGetStarredRequestCommon(RequestContext& context, bool id3)
{ {
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1313,7 +1332,7 @@ handleGetPlaylistRequest(RequestContext& context)
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1339,7 +1358,7 @@ handleGetPlaylistsRequest(RequestContext& context)
{ {
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1376,7 +1395,7 @@ handleGetSongsByGenreRequest(RequestContext& context)
if (!cluster) if (!cluster)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError {};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1446,7 +1465,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3)
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1528,7 +1547,7 @@ handleStarRequest(RequestContext& context)
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1584,7 +1603,7 @@ handleUnstarRequest(RequestContext& context)
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError {};
@@ -1631,7 +1650,7 @@ handleScrobble(RequestContext& context)
auto transaction {context.dbSession.createUniqueTransaction()}; auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError {};
@@ -1707,7 +1726,7 @@ handleUpdatePlaylistRequest(RequestContext& context)
auto transaction {context.dbSession.createUniqueTransaction()}; auto transaction {context.dbSession.createUniqueTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1756,7 +1775,7 @@ handleGetBookmarks(RequestContext& context)
{ {
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1790,7 +1809,7 @@ handleCreateBookmark(RequestContext& context)
auto transaction {context.dbSession.createUniqueTransaction()}; auto transaction {context.dbSession.createUniqueTransaction()};
const User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; const User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1821,7 +1840,7 @@ handleDeleteBookmark(RequestContext& context)
auto transaction {context.dbSession.createUniqueTransaction()}; auto transaction {context.dbSession.createUniqueTransaction()};
const User::pointer user {User::getByLoginName(context.dbSession, context.userName)}; const User::pointer user {User::getById(context.dbSession, context.userId)};
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
@@ -1995,6 +2014,39 @@ static std::unordered_map<std::string, MediaRetrievalHandlerFunc> mediaRetrieval
{"getCoverArt", handleGetCoverArt}, {"getCoverArt", handleGetCoverArt},
}; };
static
Database::IdType
authenticateUser(const Wt::Http::Request &request, const ClientInfo& clientInfo, Session& dbSession)
{
if (auto *authEnvService {Service<::Auth::IEnvService>::get()})
{
const auto checkResult {authEnvService->processRequest(dbSession, request)};
if (checkResult.state != ::Auth::IEnvService::CheckResult::State::Granted)
throw UserNotAuthorizedError {};
return *checkResult.userId;
}
else if (auto *authPasswordService {Service<::Auth::IPasswordService>::get()})
{
const auto checkResult {authPasswordService->checkUserPassword(dbSession,
boost::asio::ip::address::from_string(request.clientAddress()),
clientInfo.user, clientInfo.password)};
switch (checkResult.state)
{
case Auth::IPasswordService::CheckResult::State::Granted:
return *checkResult.userId;
break;
case Auth::IPasswordService::CheckResult::State::Denied:
throw WrongUsernameOrPasswordError {};
case Auth::IPasswordService::CheckResult::State::Throttled:
throw LoginThrottledGenericError {};
}
}
throw InternalErrorGenericError {"No service avalaible to authenticate user"};
}
void void
SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response)
{ {
@@ -2024,21 +2076,8 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
Session& dbSession {_db.getTLSSession()}; Session& dbSession {_db.getTLSSession()};
const Auth::IPasswordService::CheckResult checkResult {Service<Auth::IPasswordService>::get()->checkUserPassword(dbSession, const Database::IdType userId {authenticateUser(request, clientInfo, dbSession)};
boost::asio::ip::address::from_string(request.clientAddress()), RequestContext requestContext {parameters, dbSession, userId, clientInfo.name};
clientInfo.user, clientInfo.password)};
switch (checkResult.state)
{
case Auth::IPasswordService::CheckResult::State::Granted:
break;
case Auth::IPasswordService::CheckResult::State::Denied:
throw WrongUsernameOrPasswordError {};
case Auth::IPasswordService::CheckResult::State::Throttled:
throw LoginThrottledGenericError {};
}
RequestContext requestContext {parameters, dbSession, clientInfo.user, clientInfo.name};
auto itEntryPoint {requestEntryPoints.find(requestPath)}; auto itEntryPoint {requestEntryPoints.find(requestPath)};
if (itEntryPoint != requestEntryPoints.end()) if (itEntryPoint != requestEntryPoints.end())
@@ -2047,13 +2086,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
itEntryPoint->second.checkFunc(); itEntryPoint->second.checkFunc();
if (itEntryPoint->second.mustBeAdmin) if (itEntryPoint->second.mustBeAdmin)
{ checkUserIsAdmin(requestContext);
auto transaction {dbSession.createSharedTransaction()};
User::pointer user {User::getByLoginName(dbSession, clientInfo.user)};
if (!user || !user->isAdmin())
throw UserNotAuthorizedError {};
}
Response resp {(itEntryPoint->second.func)(requestContext)}; Response resp {(itEntryPoint->second.func)(requestContext)};
+23 -19
View File
@@ -65,6 +65,19 @@ static constexpr const char* defaultPath {"/releases"};
std::unique_ptr<Wt::WApplication> std::unique_ptr<Wt::WApplication>
LmsApplication::create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups) LmsApplication::create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups)
{ {
if (auto *authEnvService {Service<::Auth::IEnvService>::get()})
{
const auto checkResult {authEnvService->processEnv(db.getTLSSession(), env)};
if (checkResult.state != ::Auth::IEnvService::CheckResult::State::Granted)
{
LMS_LOG(UI, ERROR) << "Cannot authenticate user from environment!";
// return a blank page
return std::make_unique<Wt::WApplication>(env);
}
return std::make_unique<LmsApplication>(env, db, appGroups, checkResult.userId);
}
return std::make_unique<LmsApplication>(env, db, appGroups); return std::make_unique<LmsApplication>(env, db, appGroups);
} }
@@ -121,10 +134,12 @@ LmsApplication::getUserLoginName()
LmsApplication::LmsApplication(const Wt::WEnvironment& env, LmsApplication::LmsApplication(const Wt::WEnvironment& env,
Database::Db& db, Database::Db& db,
LmsApplicationGroupContainer& appGroups) LmsApplicationGroupContainer& appGroups,
: Wt::WApplication {env}, std::optional<Database::IdType> userId)
_db {db}, : Wt::WApplication {env}
_appGroups {appGroups} , _db {db}
, _appGroups {appGroups}
, _authenticatedUser {userId ? std::make_optional<UserAuthInfo>(UserAuthInfo {*userId, false}) : std::nullopt}
{ {
try try
{ {
@@ -181,23 +196,12 @@ LmsApplication::init()
// Handle Media Scanner events and other session events // Handle Media Scanner events and other session events
enableUpdates(true); enableUpdates(true);
if (Service<::Auth::IEnvService>::exists()) if (_authenticatedUser)
processEnvAuth(); {
onUserLoggedIn();
}
else if (Service<::Auth::IPasswordService>::exists()) else if (Service<::Auth::IPasswordService>::exists())
processPasswordAuth(); processPasswordAuth();
else
throw LmsException {"No auth service available!"};
}
void
LmsApplication::processEnvAuth()
{
const auto checkResult {Service<::Auth::IEnvService>::get()->processEnv(getDbSession(), wApp->environment())};
if (checkResult.state != ::Auth::IEnvService::CheckResult::State::Granted)
throw DeploymentException {};
_authenticatedUser = {*checkResult.userId, false};
onUserLoggedIn();
} }
void void
+1 -2
View File
@@ -60,7 +60,7 @@ class LmsApplication : public Wt::WApplication
{ {
public: public:
LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups); LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups, std::optional<Database::IdType> userId = std::nullopt);
~LmsApplication(); ~LmsApplication();
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups); static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups);
@@ -110,7 +110,6 @@ class LmsApplication : public Wt::WApplication
void init(); void init();
void setTheme(); void setTheme();
void processEnvAuth();
void processPasswordAuth(); void processPasswordAuth();
void handleException(LmsApplicationException& e); void handleException(LmsApplicationException& e);
void goHomeAndQuit(); void goHomeAndQuit();
-6
View File
@@ -31,12 +31,6 @@ class LmsApplicationException : public LmsException
LmsApplicationException(const Wt::WString& error) : LmsException {error.toUTF8()} {} LmsApplicationException(const Wt::WString& error) : LmsException {error.toUTF8()} {}
}; };
class DeploymentException : public LmsApplicationException
{
public:
DeploymentException() : LmsApplicationException {Wt::WString::tr("Lms.Error.deployment-error")} {}
};
class ArtistNotFoundException : public LmsApplicationException class ArtistNotFoundException : public LmsApplicationException
{ {
public: public: