Added a way to create the admin account linked to the PAM backend password authentication. ref #65
This commit is contained in:
@@ -23,6 +23,7 @@ A [demo](http://lms.demo.poupon.io) instance is available, with the following li
|
|||||||
* MusicBrainzID support to handle duplicated artist and release names
|
* MusicBrainzID support to handle duplicated artist and release names
|
||||||
* Disc subtitles support
|
* Disc subtitles support
|
||||||
* _Systemd_ integration
|
* _Systemd_ integration
|
||||||
|
* _PAM_ authentication backend
|
||||||
* Subsonic API, with the following additional features:
|
* Subsonic API, with the following additional features:
|
||||||
* Playlists
|
* Playlists
|
||||||
* Starred Album/Artist/Tracks
|
* Starred Album/Artist/Tracks
|
||||||
|
|||||||
@@ -20,7 +20,15 @@
|
|||||||
${admin-login-info class="help-block"}
|
${admin-login-info class="help-block"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-lg-3" for="${id:auth-mode}">
|
||||||
|
${tr:Lms.Admin.User.auth-mode}
|
||||||
|
</label>
|
||||||
|
<div class="col-lg-9">
|
||||||
|
${auth-mode}
|
||||||
|
${auth-mode-info class="help-block"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-lg-3 control-label" for="${id:password}">
|
<label class="col-lg-3 control-label" for="${id:password}">
|
||||||
${tr:Lms.password}
|
${tr:Lms.password}
|
||||||
@@ -30,7 +38,6 @@
|
|||||||
${password-info class="help-block"}
|
${password-info class="help-block"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-lg-3 control-label" for="${id:password-confirm}">
|
<label class="col-lg-3 control-label" for="${id:password-confirm}">
|
||||||
${tr:Lms.password-confirm}
|
${tr:Lms.password-confirm}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ checkUserPassword(Database::Session& session, const std::string& loginName, cons
|
|||||||
{
|
{
|
||||||
case Database::User::AuthMode::Internal:
|
case Database::User::AuthMode::Internal:
|
||||||
{
|
{
|
||||||
|
LMS_LOG(AUTH, DEBUG) << "Checking internal password for user '" << loginName << "'";
|
||||||
const Wt::Auth::BCryptHashFunction hashFunc {6}; // TODO parametrize this
|
const Wt::Auth::BCryptHashFunction hashFunc {6}; // TODO parametrize this
|
||||||
return hashFunc.verify(password, passwordHash.salt, passwordHash.hash);
|
return hashFunc.verify(password, passwordHash.salt, passwordHash.hash);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ checkUserPassword(const std::string& loginName, const std::string& password)
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
LMS_LOG(AUTH, DEBUG) << "Checking PAM password for user '" << loginName << "'";
|
||||||
PAMContext pamContext {loginName};
|
PAMContext pamContext {loginName};
|
||||||
|
|
||||||
pamContext.authenticate(password);
|
pamContext.authenticate(password);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ add_executable(lms
|
|||||||
ui/admin/InitWizardView.cpp
|
ui/admin/InitWizardView.cpp
|
||||||
ui/admin/UserView.cpp
|
ui/admin/UserView.cpp
|
||||||
ui/admin/UsersView.cpp
|
ui/admin/UsersView.cpp
|
||||||
|
ui/common/AuthModeModel.cpp
|
||||||
ui/common/Validators.cpp
|
ui/common/Validators.cpp
|
||||||
ui/explore/ArtistListHelpers.cpp
|
ui/explore/ArtistListHelpers.cpp
|
||||||
ui/explore/ArtistView.cpp
|
ui/explore/ArtistView.cpp
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "InitWizardView.hpp"
|
#include "InitWizardView.hpp"
|
||||||
|
|
||||||
|
#include <Wt/WComboBox.h>
|
||||||
#include <Wt/WFormModel.h>
|
#include <Wt/WFormModel.h>
|
||||||
#include <Wt/WLineEdit.h>
|
#include <Wt/WLineEdit.h>
|
||||||
#include <Wt/WPushButton.h>
|
#include <Wt/WPushButton.h>
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
#include "utils/Service.hpp"
|
#include "utils/Service.hpp"
|
||||||
|
|
||||||
#include "common/Validators.hpp"
|
#include "common/Validators.hpp"
|
||||||
|
#include "common/AuthModeModel.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
@@ -41,18 +43,23 @@ class InitWizardModel : public Wt::WFormModel
|
|||||||
static const Field AdminLoginField;
|
static const Field AdminLoginField;
|
||||||
static const Field PasswordField;
|
static const Field PasswordField;
|
||||||
static const Field PasswordConfirmField;
|
static const Field PasswordConfirmField;
|
||||||
|
static inline const Field AuthModeField{"auth-mode"};
|
||||||
|
|
||||||
InitWizardModel() : Wt::WFormModel()
|
InitWizardModel() : Wt::WFormModel()
|
||||||
{
|
{
|
||||||
addField(AdminLoginField);
|
addField(AdminLoginField);
|
||||||
|
addField(AuthModeField);
|
||||||
addField(PasswordField);
|
addField(PasswordField);
|
||||||
addField(PasswordConfirmField);
|
addField(PasswordConfirmField);
|
||||||
|
|
||||||
|
setValidator(AuthModeField, createMandatoryValidator());
|
||||||
setValidator(AdminLoginField, createNameValidator());
|
setValidator(AdminLoginField, createNameValidator());
|
||||||
setValidator(PasswordField, createMandatoryValidator());
|
setValidator(PasswordField, createMandatoryValidator());
|
||||||
setValidator(PasswordConfirmField, createMandatoryValidator());
|
setValidator(PasswordConfirmField, createMandatoryValidator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AuthModeModel> getAuthModeModel() const { return _authModeModel; }
|
||||||
|
|
||||||
void saveData()
|
void saveData()
|
||||||
{
|
{
|
||||||
const Database::User::PasswordHash passwordHash {ServiceProvider<::Auth::IPasswordService>::get()->hashPassword(valueText(PasswordField).toUTF8())};
|
const Database::User::PasswordHash passwordHash {ServiceProvider<::Auth::IPasswordService>::get()->hashPassword(valueText(PasswordField).toUTF8())};
|
||||||
@@ -64,34 +71,67 @@ class InitWizardModel : public Wt::WFormModel
|
|||||||
if (!Database::User::getAll(LmsApp->getDbSession()).empty())
|
if (!Database::User::getAll(LmsApp->getDbSession()).empty())
|
||||||
throw LmsException("Admin user already created");
|
throw LmsException("Admin user already created");
|
||||||
|
|
||||||
|
auto authModeRow {_authModeModel->getRowFromString(valueText(AuthModeField))};
|
||||||
|
if (!authModeRow)
|
||||||
|
throw LmsException {"Bad authentication mode"};
|
||||||
|
|
||||||
|
const Database::User::AuthMode authMode {_authModeModel->getValue(*authModeRow)};
|
||||||
|
|
||||||
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::User::Type::ADMIN);
|
||||||
user.modify()->setAuthMode(Database::User::AuthMode::Internal);
|
user.modify()->setAuthMode(authMode);
|
||||||
|
if (authMode == Database::User::AuthMode::Internal)
|
||||||
user.modify()->setPasswordHash(passwordHash);
|
user.modify()->setPasswordHash(passwordHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void validatePassword(Wt::WString& error) const
|
||||||
|
{
|
||||||
|
auto authModeRow {_authModeModel->getRowFromString(valueText(AuthModeField))};
|
||||||
|
if (!authModeRow)
|
||||||
|
throw LmsException {"Bad authentication mode"};
|
||||||
|
|
||||||
|
const Database::User::AuthMode authMode {_authModeModel->getValue(*authModeRow)};
|
||||||
|
if (authMode != Database::User::AuthMode::Internal)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!valueText(PasswordField).empty())
|
||||||
|
{
|
||||||
|
// Evaluate the strength of the password
|
||||||
|
if (!ServiceProvider<::Auth::IPasswordService>::get()->evaluatePasswordStrength(valueText(AdminLoginField).toUTF8(), valueText(PasswordField).toUTF8()))
|
||||||
|
error = Wt::WString::tr("Lms.password-too-weak");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
error = Wt::WString::tr("Lms.password-must-not-be-empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
void validatePasswordConfirm(Wt::WString& error) const
|
||||||
|
{
|
||||||
|
auto authModeRow {_authModeModel->getRowFromString(valueText(AuthModeField))};
|
||||||
|
if (!authModeRow)
|
||||||
|
throw LmsException {"Bad authentication mode"};
|
||||||
|
|
||||||
|
const Database::User::AuthMode authMode {_authModeModel->getValue(*authModeRow)};
|
||||||
|
if (authMode != Database::User::AuthMode::Internal)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (validation(PasswordField).state() == Wt::ValidationState::Valid)
|
||||||
|
{
|
||||||
|
if (valueText(PasswordField) != valueText(PasswordConfirmField))
|
||||||
|
error = Wt::WString::tr("Lms.passwords-dont-match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool validateField(Field field)
|
bool validateField(Field field)
|
||||||
{
|
{
|
||||||
Wt::WString error;
|
Wt::WString error;
|
||||||
|
|
||||||
if (field == PasswordField)
|
if (field == PasswordField)
|
||||||
{
|
{
|
||||||
if (!valueText(PasswordField).empty())
|
validatePassword(error);
|
||||||
{
|
|
||||||
// Evaluate the strength of the password
|
|
||||||
if (!ServiceProvider<::Auth::IPasswordService>::get()->evaluatePasswordStrength(valueText(AdminLoginField).toUTF8(), valueText(PasswordField).toUTF8()))
|
|
||||||
error = Wt::WString::tr("Lms.password-too-weak");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return Wt::WFormModel::validateField(field);
|
|
||||||
}
|
}
|
||||||
else if (field == PasswordConfirmField)
|
else if (field == PasswordConfirmField)
|
||||||
{
|
{
|
||||||
if (validation(PasswordField).state() == Wt::ValidationState::Valid)
|
validatePasswordConfirm(error);
|
||||||
{
|
|
||||||
if (valueText(PasswordField) != valueText(PasswordConfirmField))
|
|
||||||
error = Wt::WString::tr("Lms.passwords-dont-match");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -103,6 +143,7 @@ class InitWizardModel : public Wt::WFormModel
|
|||||||
return (validation(field).state() == Wt::ValidationState::Valid);
|
return (validation(field).state() == Wt::ValidationState::Valid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<AuthModeModel> _authModeModel {createAuthModeModel()};
|
||||||
};
|
};
|
||||||
|
|
||||||
const Wt::WFormModel::Field InitWizardModel::AdminLoginField = "admin-login";
|
const Wt::WFormModel::Field InitWizardModel::AdminLoginField = "admin-login";
|
||||||
@@ -121,6 +162,20 @@ InitWizardView::InitWizardView()
|
|||||||
setFormWidget(InitWizardModel::AdminLoginField, std::move(adminLogin));
|
setFormWidget(InitWizardModel::AdminLoginField, std::move(adminLogin));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auth mode
|
||||||
|
auto authMode = std::make_unique<Wt::WComboBox>();
|
||||||
|
authMode->setModel(model->getAuthModeModel());
|
||||||
|
authMode->activated().connect([=](int row)
|
||||||
|
{
|
||||||
|
const Database::User::AuthMode authMode {model->getAuthModeModel()->getValue(row)};
|
||||||
|
|
||||||
|
model->setReadOnly(InitWizardModel::PasswordField, authMode != Database::User::AuthMode::Internal);
|
||||||
|
model->setReadOnly(InitWizardModel::PasswordConfirmField, authMode != Database::User::AuthMode::Internal);
|
||||||
|
updateModel(model.get());
|
||||||
|
updateView(model.get());
|
||||||
|
});
|
||||||
|
setFormWidget(InitWizardModel::AuthModeField, std::move(authMode));
|
||||||
|
|
||||||
// Password
|
// Password
|
||||||
{
|
{
|
||||||
auto passwordEdit = std::make_unique<Wt::WLineEdit>();
|
auto passwordEdit = std::make_unique<Wt::WLineEdit>();
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include "utils/Service.hpp"
|
#include "utils/Service.hpp"
|
||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
|
#include "common/AuthModeModel.hpp"
|
||||||
#include "common/Validators.hpp"
|
#include "common/Validators.hpp"
|
||||||
#include "common/ValueStringModel.hpp"
|
#include "common/ValueStringModel.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
@@ -58,8 +59,6 @@ class UserModel : public Wt::WFormModel
|
|||||||
UserModel(std::optional<Database::IdType> userId)
|
UserModel(std::optional<Database::IdType> userId)
|
||||||
: _userId {userId}
|
: _userId {userId}
|
||||||
{
|
{
|
||||||
initializeModels();
|
|
||||||
|
|
||||||
if (!_userId)
|
if (!_userId)
|
||||||
{
|
{
|
||||||
addField(LoginField);
|
addField(LoginField);
|
||||||
@@ -70,6 +69,8 @@ class UserModel : public Wt::WFormModel
|
|||||||
addField(PasswordField);
|
addField(PasswordField);
|
||||||
addField(DemoField);
|
addField(DemoField);
|
||||||
|
|
||||||
|
setValidator(AuthModeField, createMandatoryValidator());
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,18 +235,8 @@ class UserModel : public Wt::WFormModel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void initializeModels()
|
|
||||||
{
|
|
||||||
_authModeModel = std::make_shared<AuthModeModel>();
|
|
||||||
|
|
||||||
if (ServiceProvider<::Auth::IPasswordService>::get()->isAuthModeSupported(User::AuthMode::Internal))
|
|
||||||
_authModeModel->add(Wt::WString::tr("Lms.Admin.User.auth-mode.internal"), User::AuthMode::Internal);
|
|
||||||
if (ServiceProvider<::Auth::IPasswordService>::get()->isAuthModeSupported(User::AuthMode::PAM))
|
|
||||||
_authModeModel->add(Wt::WString::tr("Lms.Admin.User.auth-mode.pam"), User::AuthMode::PAM);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<Database::IdType> _userId;
|
std::optional<Database::IdType> _userId;
|
||||||
std::shared_ptr<AuthModeModel> _authModeModel;
|
std::shared_ptr<AuthModeModel> _authModeModel {createAuthModeModel()};
|
||||||
};
|
};
|
||||||
|
|
||||||
UserView::UserView()
|
UserView::UserView()
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AuthModeModel.hpp"
|
||||||
|
|
||||||
|
#include "auth/IPasswordService.hpp"
|
||||||
|
#include "utils/Service.hpp"
|
||||||
|
|
||||||
|
namespace UserInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
std::unique_ptr<AuthModeModel>
|
||||||
|
createAuthModeModel()
|
||||||
|
{
|
||||||
|
auto model {std::make_unique<AuthModeModel>()};
|
||||||
|
|
||||||
|
if (ServiceProvider<::Auth::IPasswordService>::get()->isAuthModeSupported(Database::User::AuthMode::Internal))
|
||||||
|
model->add(Wt::WString::tr("Lms.Admin.User.auth-mode.internal"), Database::User::AuthMode::Internal);
|
||||||
|
if (ServiceProvider<::Auth::IPasswordService>::get()->isAuthModeSupported(Database::User::AuthMode::PAM))
|
||||||
|
model->add(Wt::WString::tr("Lms.Admin.User.auth-mode.pam"), Database::User::AuthMode::PAM);
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "database/User.hpp"
|
||||||
|
#include "common/ValueStringModel.hpp"
|
||||||
|
|
||||||
|
namespace UserInterface
|
||||||
|
{
|
||||||
|
using AuthModeModel = ValueStringModel<Database::User::AuthMode>;
|
||||||
|
|
||||||
|
std::unique_ptr<AuthModeModel> createAuthModeModel();
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2019 Emeric Poupon
|
* Copyright (C) 2019 Emeric Poupon
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user