Properly handled http headers for subsonic API. ref #119
This commit is contained in:
+2
-2
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {};
|
||||||
|
|
||||||
|
|||||||
@@ -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)};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user