diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1faefee5..67572a74 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.12)
project(lms)
-
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")
set(CMAKE_CXX_STANDARD 17)
@@ -11,10 +10,24 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
include(CTest)
find_package(PkgConfig REQUIRED)
find_package(Filesystem REQUIRED)
+find_package(FFMPEGAV REQUIRED)
+find_package(Taglib REQUIRED)
+find_package(Boost REQUIRED COMPONENTS system)
+find_package(PStreams REQUIRED)
+find_package(PAM)
pkg_check_modules(GRAPHICSMAGICKXX REQUIRED GraphicsMagick++)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror -Wno-error=parentheses -Wno-error=unused-function -O0 -g")
+set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -g")
+
+if(PAM_FOUND)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSEPAM")
+endif(PAM_FOUND)
+
add_subdirectory(src)
+# TODO pam configuration file
install(DIRECTORY approot DESTINATION share/lms)
install(DIRECTORY docroot DESTINATION share/lms)
install(FILES systemd/default.service DESTINATION share/lms)
diff --git a/approot/messages.xml b/approot/messages.xml
index 9c63aee0..babaaad2 100644
--- a/approot/messages.xml
+++ b/approot/messages.xml
@@ -17,6 +17,9 @@
Login throttled, please try again later
Confirm password
New password
+Is auth managed externally?
+Password must be empty for users with external authentication
+Password must not be empty for users without external authentication
Old password
Password too weak
Passwords don't match
diff --git a/cmake/modules/FindFFMPEGAV.cmake b/cmake/modules/FindFFMPEGAV.cmake
new file mode 100644
index 00000000..2a512757
--- /dev/null
+++ b/cmake/modules/FindFFMPEGAV.cmake
@@ -0,0 +1,26 @@
+# Quick and dirty av* includes discoverer
+
+find_path(AVCODEC_INCLUDE_DIR NAMES libavcodec/avcodec.h PATH_SUFFIXES ffmpeg)
+find_library(AVCODEC_LIBRARY avcodec)
+
+find_path(AVFORMAT_INCLUDE_DIR NAMES libavformat/avformat.h PATH_SUFFIXES ffmpeg)
+find_library(AVFORMAT_LIBRARY avformat)
+
+find_path(AVUTIL_INCLUDE_DIR NAMES libavutil/avutil.h PATH_SUFFIXES ffmpeg)
+find_library(AVUTIL_LIBRARY avutil)
+
+find_path(AVDEVICE_INCLUDE_DIR NAMES libavdevice/avdevice.h PATH_SUFFIXES ffmpeg)
+find_library(AVDEVICE_LIBRARY avdevice)
+
+include(FindPackageHandleStandardArgs)
+
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(
+ FFMPEGAV
+ FOUND_VAR FFMPEGAV_FOUND
+ REQUIRED_VARS AVUTIL_LIBRARY AVFORMAT_LIBRARY
+)
+
+mark_as_advanced(AVFORMAT_LIBRARY)
+mark_as_advanced(AVUTIL_LIBRARY)
+
+
diff --git a/cmake/modules/FindPAM.cmake b/cmake/modules/FindPAM.cmake
new file mode 100644
index 00000000..08c86def
--- /dev/null
+++ b/cmake/modules/FindPAM.cmake
@@ -0,0 +1,73 @@
+# From http://code.google.com/p/pam-face-authentication/source/browse/branches/pam_face_authentication/cmake/modules/FindPAM.cmake?r=336
+
+# - Try to find the PAM libraries
+# Once done this will define
+#
+# PAM_FOUND - system has pam
+# PAM_INCLUDE_DIR - the pam include directory
+# PAM_LIBRARIES - libpam library
+
+if (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+ # Already in cache, be silent
+ set(PAM_FIND_QUIETLY TRUE)
+endif (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+
+find_path(PAM_INCLUDE_DIR NAMES security/pam_appl.h pam/pam_appl.h)
+find_library(PAM_LIBRARY pam)
+find_library(DL_LIBRARY dl)
+
+if (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+ set(PAM_FOUND TRUE)
+ if (DL_LIBRARY)
+ set(PAM_LIBRARIES ${PAM_LIBRARY} ${DL_LIBRARY})
+ else (DL_LIBRARY)
+ set(PAM_LIBRARIES ${PAM_LIBRARY})
+ endif (DL_LIBRARY)
+
+ if (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h)
+ # darwin claims to be something special
+ set(HAVE_PAM_PAM_APPL_H 1)
+ endif (EXISTS ${PAM_INCLUDE_DIR}/pam/pam_appl.h)
+
+ if (NOT DEFINED PAM_MESSAGE_CONST)
+ include(CheckCXXSourceCompiles)
+ # XXX does this work with plain c?
+ check_cxx_source_compiles("
+#if ${HAVE_PAM_PAM_APPL_H}+0
+# include
+#else
+# include
+#endif
+static int PAM_conv(
+ int num_msg,
+ const struct pam_message **msg, /* this is the culprit */
+ struct pam_response **resp,
+ void *ctx)
+{
+ return 0;
+}
+int main(void)
+{
+ struct pam_conv PAM_conversation = {
+ &PAM_conv, /* this bombs out if the above does not match */
+ 0
+ };
+ return 0;
+}
+" PAM_MESSAGE_CONST)
+ endif (NOT DEFINED PAM_MESSAGE_CONST)
+ set(PAM_MESSAGE_CONST ${PAM_MESSAGE_CONST} CACHE BOOL "PAM expects a conversation function with const pam_message")
+
+endif (PAM_INCLUDE_DIR AND PAM_LIBRARY)
+
+if (PAM_FOUND)
+ if (NOT PAM_FIND_QUIETLY)
+ message(STATUS "Found PAM: ${PAM_LIBRARIES}")
+ endif (NOT PAM_FIND_QUIETLY)
+else (PAM_FOUND)
+ if (PAM_FIND_REQUIRED)
+ message(FATAL_ERROR "PAM was not found")
+ endif(PAM_FIND_REQUIRED)
+endif (PAM_FOUND)
+
+mark_as_advanced(PAM_INCLUDE_DIR PAM_LIBRARY DL_LIBRARY PAM_MESSAGE_CONST)
diff --git a/cmake/modules/FindPStreams.cmake b/cmake/modules/FindPStreams.cmake
new file mode 100644
index 00000000..bff205cd
--- /dev/null
+++ b/cmake/modules/FindPStreams.cmake
@@ -0,0 +1,17 @@
+# If already in cache, be silent
+if(PSTREAMS_INCLUDE_DIRS)
+ set (PSTREAMS_FIND_QUIETLY TRUE)
+endif()
+
+FIND_PATH(PSTREAMS_INCLUDE_DIR NAMES pstream.h
+ PATH_SUFFIXES pstreams
+ HINTS ${PSTREAMS_ROOT}/include $ENV{PSTREAMS_ROOT})
+
+set(PSTREAMS_INCLUDE_DIRS ${PSTREAMS_INCLUDE_DIR})
+
+# Handle the QUIETLY and REQUIRED arguments and set PSTREAMS_FOUND to TRUE if
+# all listed variables are TRUE.
+INCLUDE(FindPackageHandleStandardArgs)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(Pstreams DEFAULT_MSG PSTREAMS_INCLUDE_DIRS)
+
+MARK_AS_ADVANCED(PSTREAMS_INCLUDE_DIRS)
diff --git a/cmake/modules/FindTaglib.cmake b/cmake/modules/FindTaglib.cmake
new file mode 100644
index 00000000..7c617918
--- /dev/null
+++ b/cmake/modules/FindTaglib.cmake
@@ -0,0 +1,87 @@
+# - Try to find the Taglib library
+# Once done this will define
+#
+# TAGLIB_FOUND - system has the taglib library
+# TAGLIB_CFLAGS - the taglib cflags
+# TAGLIB_LIBRARIES - The libraries needed to use taglib
+
+# Copyright (c) 2006, Laurent Montel,
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+IF(TAGLIB_FOUND)
+ MESSAGE(STATUS "Using manually specified taglib locations")
+ELSE()
+
+ if(NOT TAGLIB_MIN_VERSION)
+ set(TAGLIB_MIN_VERSION "1.6")
+ endif(NOT TAGLIB_MIN_VERSION)
+
+ if(NOT WIN32)
+ find_program(TAGLIBCONFIG_EXECUTABLE NAMES taglib-config PATHS
+ ${BIN_INSTALL_DIR}
+ )
+ endif(NOT WIN32)
+
+ #reset vars
+ set(TAGLIB_LIBRARIES)
+ set(TAGLIB_CFLAGS)
+
+# MESSAGE( STATUS "PATHS: ${PATHS}")
+ # if taglib-config has been found
+ if(TAGLIBCONFIG_EXECUTABLE)
+
+ exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --version RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_VERSION)
+
+ if(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}")
+ message(STATUS "TagLib version not found: version searched :${TAGLIB_MIN_VERSION}, found ${TAGLIB_VERSION}")
+ set(TAGLIB_FOUND FALSE)
+ else(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}")
+
+ exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --libs RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_LIBRARIES)
+
+ exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --cflags RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_CFLAGS)
+
+ if(TAGLIB_LIBRARIES AND TAGLIB_CFLAGS)
+ set(TAGLIB_FOUND TRUE)
+# message(STATUS "Found taglib: ${TAGLIB_LIBRARIES}")
+ endif(TAGLIB_LIBRARIES AND TAGLIB_CFLAGS)
+ string(REGEX REPLACE " *-I" ";" TAGLIB_INCLUDES "${TAGLIB_CFLAGS}")
+ endif(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}")
+ mark_as_advanced(TAGLIB_CFLAGS TAGLIB_LIBRARIES TAGLIB_INCLUDES)
+
+ else(TAGLIBCONFIG_EXECUTABLE)
+
+ include(FindLibraryWithDebug)
+ include(FindPackageHandleStandardArgs)
+
+ find_path(TAGLIB_INCLUDES
+ NAMES
+ tag.h
+ PATH_SUFFIXES taglib
+ PATHS
+ ${INCLUDE_INSTALL_DIR}
+ )
+
+ find_library_with_debug(TAGLIB_LIBRARIES
+ WIN32_DEBUG_POSTFIX d
+ NAMES tag
+ PATHS
+ ${LIB_INSTALL_DIR}
+ )
+
+ find_package_handle_standard_args(Taglib DEFAULT_MSG
+ TAGLIB_INCLUDES TAGLIB_LIBRARIES)
+ endif(TAGLIBCONFIG_EXECUTABLE)
+ENDIF()
+
+if(TAGLIB_FOUND)
+ if(NOT Taglib_FIND_QUIETLY AND TAGLIBCONFIG_EXECUTABLE)
+ message(STATUS "Found TagLib: ${TAGLIB_LIBRARIES}")
+ endif(NOT Taglib_FIND_QUIETLY AND TAGLIBCONFIG_EXECUTABLE)
+else(TAGLIB_FOUND)
+ if(Taglib_FIND_REQUIRED)
+ message(FATAL_ERROR "Could not find Taglib")
+ endif(Taglib_FIND_REQUIRED)
+endif(TAGLIB_FOUND)
diff --git a/conf/lms b/conf/lms
new file mode 100644
index 00000000..c53af2a1
--- /dev/null
+++ b/conf/lms
@@ -0,0 +1,9 @@
+account required pam_unix.so try_first_pass
+account sufficient pam_localuser.so
+
+auth required pam_unix.so try_first_pass
+
+password required pam_deny.so
+
+session required pam_unix.so try_first_pass
+
diff --git a/docroot/css/lms.css b/docroot/css/lms.css
index 3fecc982..6f1ca489 100644
--- a/docroot/css/lms.css
+++ b/docroot/css/lms.css
@@ -483,4 +483,7 @@ a.Lms-releasename:hover, a.Lms-releasename:focus {
margin-bottom: 8px;
}
+.hack {
+ margin-left: 20px;
+}
diff --git a/src/libs/auth/CMakeLists.txt b/src/libs/auth/CMakeLists.txt
index 9be20d0c..20e1cf37 100644
--- a/src/libs/auth/CMakeLists.txt
+++ b/src/libs/auth/CMakeLists.txt
@@ -22,6 +22,7 @@ target_link_libraries(lmsauth PUBLIC
pthread
boost_system
wt
+ ${PAM_LIBRARIES}
)
install(TARGETS lmsauth DESTINATION lib)
diff --git a/src/libs/auth/impl/AuthTokenService.cpp b/src/libs/auth/impl/AuthTokenService.cpp
index ba37b35c..ff21dbeb 100644
--- a/src/libs/auth/impl/AuthTokenService.cpp
+++ b/src/libs/auth/impl/AuthTokenService.cpp
@@ -17,8 +17,6 @@
* along with LMS. If not, see .
*/
-/* This file contains some classes in order to get info from file using the libavconv */
-
#include "AuthTokenService.hpp"
#include
diff --git a/src/libs/auth/impl/AuthTokenService.hpp b/src/libs/auth/impl/AuthTokenService.hpp
index 36a9721a..00b0b5a8 100644
--- a/src/libs/auth/impl/AuthTokenService.hpp
+++ b/src/libs/auth/impl/AuthTokenService.hpp
@@ -17,8 +17,6 @@
* along with LMS. If not, see .
*/
-/* This file contains some classes in order to get info from file using the libavconv */
-
#pragma once
#include "auth/IAuthTokenService.hpp"
diff --git a/src/libs/auth/impl/PasswordService.cpp b/src/libs/auth/impl/PasswordService.cpp
index 9c17de2b..3a0fac63 100644
--- a/src/libs/auth/impl/PasswordService.cpp
+++ b/src/libs/auth/impl/PasswordService.cpp
@@ -17,8 +17,6 @@
* along with LMS. If not, see .
*/
-/* This file contains some classes in order to get info from file using the libavconv */
-
#include "PasswordService.hpp"
#include
@@ -29,6 +27,8 @@
#include "utils/Exception.hpp"
#include "utils/Logger.hpp"
+#include
+
namespace Auth {
std::unique_ptr createPasswordService(std::size_t maxThrottlerEntries)
@@ -41,10 +41,101 @@ PasswordService::PasswordService(std::size_t maxThrottlerEntries)
{
}
+#ifdef USEPAM
+static void
+delete_resp(int num_msg, pam_response *response)
+{
+ if (response == nullptr)
+ return;
+ for (int i = 0; i < num_msg; i++) {
+ if (response[i].resp) {
+ /* clear before freeing -- might be a password */
+ bzero(response[i].resp, strlen(response[i].resp));
+ free(response[i].resp);
+ response[i].resp = nullptr;
+ }
+ }
+}
+
+struct pam_conv_data
+{
+ const char *username;
+ const char *password;
+};
+
+static
+int lms_conv(int num_msg, const pam_message** msgs, pam_response** resps, void* appdata_ptr)
+{
+ if(num_msg < 1)
+ return PAM_CONV_ERR;
+ if (!resps || !msgs || !appdata_ptr)
+ return PAM_CONV_ERR;
+
+ pam_conv_data *data = static_cast( appdata_ptr );
+ pam_response *response = new (std::nothrow) pam_response[num_msg];
+ if(!response)
+ return PAM_CONV_ERR;
+
+ for(int i = 0; i < num_msg; ++i)
+ {
+ response[i].resp_retcode = 0;
+ response[i].resp = 0;
+ switch (msgs[i]->msg_style) {
+ case PAM_PROMPT_ECHO_ON:
+ /* on memory allocation failure, auth fails */
+ response[i].resp = strdup(data->username);
+ break;
+ case PAM_PROMPT_ECHO_OFF:
+ response[i].resp = strdup(data->password);
+ break;
+ case PAM_ERROR_MSG:
+ case PAM_TEXT_INFO:
+ default:
+ delete_resp(i, response);
+ return PAM_CONV_ERR;
+ }
+ }
+
+ *resps = response;
+ return PAM_SUCCESS;
+}
+#endif
+
+static bool
+pamCheckUserPassword(const std::string& loginName, const std::string& password)
+{
+#ifdef USEPAM
+ pam_conv_data authdata{loginName.c_str(), password.c_str()};
+ pam_conv conv = { lms_conv, &authdata };
+ pam_handle_t *pamh;
+ bool authenticated{false};
+
+ /* Initialize PAM framework */
+ int err = pam_start("lms", loginName.c_str(), &conv, &pamh);
+ if (err != PAM_SUCCESS) {
+ return false;
+ }
+
+ err = pam_authenticate(pamh, 0);
+ if(err == PAM_SUCCESS)
+ {
+ /* Make sure account and password are still valid */
+ err = pam_acct_mgmt(pamh, PAM_SILENT);
+ authenticated = err == PAM_SUCCESS;
+ }
+
+ (void) pam_end(pamh, 0);
+ return authenticated;
+#else
+ return false;
+#endif
+}
+
static
bool
checkUserPassword(Database::Session& session, const std::string& loginName, const std::string& password)
{
+ bool hasExternalAuth;
Database::User::PasswordHash passwordHash;
{
auto transaction {session.createSharedTransaction()};
@@ -53,11 +144,19 @@ checkUserPassword(Database::Session& session, const std::string& loginName, cons
if (!user)
return false;
+ hasExternalAuth = user->hasExternalAuth();
passwordHash = user->getPasswordHash();
}
- const Wt::Auth::BCryptHashFunction hashFunc {6};
- return hashFunc.verify(password, passwordHash.salt, passwordHash.hash);
+ if (hasExternalAuth)
+ {
+ return pamCheckUserPassword(loginName, password);
+ }
+ else
+ {
+ const Wt::Auth::BCryptHashFunction hashFunc {6};
+ return hashFunc.verify(password, passwordHash.salt, passwordHash.hash);
+ }
}
diff --git a/src/libs/av/CMakeLists.txt b/src/libs/av/CMakeLists.txt
index d79974ac..b519c980 100644
--- a/src/libs/av/CMakeLists.txt
+++ b/src/libs/av/CMakeLists.txt
@@ -8,11 +8,20 @@ add_library(lmsav SHARED
target_include_directories(lmsav INTERFACE
include
+ ${AVFORMAT_INCLUDE_DIR}
+ ${AVUTIL_INCLUDE_DIR}
)
+# ${AVCODEC_INCLUDE_DIR}
+# ${AVDEVICE_INCLUDE_DIR}
target_include_directories(lmsav PRIVATE
include/
+
+ ${AVFORMAT_INCLUDE_DIR}
+ ${AVUTIL_INCLUDE_DIR}
)
+# ${AVCODEC_INCLUDE_DIR}
+# ${AVDEVICE_INCLUDE_DIR}
# TODO make these private
target_link_libraries(lmsav PUBLIC
@@ -21,6 +30,8 @@ target_link_libraries(lmsav PUBLIC
avutil
std::filesystem
wt
+ ${AVFORMAT_LIBRARY}
+ ${AVUTIL_LIBRARY}
)
install(TARGETS lmsav DESTINATION lib)
diff --git a/src/libs/database/include/database/User.hpp b/src/libs/database/include/database/User.hpp
index 24410c47..99b4eeb5 100644
--- a/src/libs/database/include/database/User.hpp
+++ b/src/libs/database/include/database/User.hpp
@@ -165,6 +165,7 @@ class User : public Wt::Dbo::Dbo
void setSubsonicTranscodeBitrate(Bitrate bitrate);
void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; }
void setRadio(bool val) { _radio = val; }
+ void setExternalAuth(bool val) { _externalAuth = val; }
void setRepeatAll(bool val) { _repeatAll = val; }
void setUITheme(UITheme uiTheme) { _uiTheme = uiTheme; }
void clearAuthTokens();
@@ -179,6 +180,7 @@ class User : public Wt::Dbo::Dbo
std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; }
bool isRepeatAllSet() const { return _repeatAll; }
bool isRadioSet() const { return _radio; }
+ bool hasExternalAuth() const { return _externalAuth; }
UITheme getUITheme() const { return _uiTheme; }
SubsonicArtistListMode getSubsonicArtistListMode() const { return _subsonicArtistListMode; }
@@ -218,6 +220,7 @@ class User : public Wt::Dbo::Dbo
Wt::Dbo::field(a, _curPlayingTrackPos, "cur_playing_track_pos");
Wt::Dbo::field(a, _repeatAll, "repeat_all");
Wt::Dbo::field(a, _radio, "radio");
+ Wt::Dbo::field(a, _externalAuth, "external_auth");
Wt::Dbo::hasMany(a, _tracklists, Wt::Dbo::ManyToOne, "user");
Wt::Dbo::hasMany(a, _starredArtists, Wt::Dbo::ManyToMany, "user_artist_starred", "", Wt::Dbo::OnDeleteCascade);
Wt::Dbo::hasMany(a, _starredReleases, Wt::Dbo::ManyToMany, "user_release_starred", "", Wt::Dbo::OnDeleteCascade);
@@ -246,6 +249,8 @@ class User : public Wt::Dbo::Dbo
int _curPlayingTrackPos {}; // Current track position in queue
bool _repeatAll {};
bool _radio {};
+
+ bool _externalAuth {false};
Wt::Dbo::collection> _tracklists;
Wt::Dbo::collection> _starredArtists;
diff --git a/src/lms/ui/admin/UserView.cpp b/src/lms/ui/admin/UserView.cpp
index 53c27e82..fec69cb7 100644
--- a/src/lms/ui/admin/UserView.cpp
+++ b/src/lms/ui/admin/UserView.cpp
@@ -51,6 +51,7 @@ class UserModel : public Wt::WFormModel
static inline const Field LoginField {"login"};
static inline const Field PasswordField {"password"};
static inline const Field DemoField {"demo"};
+ static inline const Field ExternalAuthField{"external-auth"};
UserModel(std::optional userId)
: _userId {userId}
@@ -63,6 +64,7 @@ class UserModel : public Wt::WFormModel
addField(PasswordField);
addField(DemoField);
+ addField(ExternalAuthField);
if (!_userId)
setValidator(PasswordField, createMandatoryValidator());
@@ -90,6 +92,8 @@ class UserModel : public Wt::WFormModel
user.modify()->setPasswordHash(*passwordHash);
user.modify()->clearAuthTokens();
}
+
+ user.modify()->setExternalAuth(static_cast(Wt::asNumber(value(ExternalAuthField))));
}
else
{
@@ -98,10 +102,48 @@ class UserModel : public Wt::WFormModel
if (Wt::asNumber(value(DemoField)))
user.modify()->setType(Database::User::Type::DEMO);
+
+ if (Wt::asNumber(value(ExternalAuthField)))
+ user.modify()->setExternalAuth(true);
+ else
+ user.modify()->setExternalAuth(false);
}
}
private:
+
+ Wt::WString validatePassword() const
+ {
+ Wt::WString error;
+
+ if (Wt::asNumber(value(ExternalAuthField)))
+ {
+ if (!valueText(PasswordField).empty())
+ {
+ error = Wt::WString::tr("Lms.password_must_be_empty_for_ext");
+ }
+ }
+ else if (!valueText(PasswordField).empty())
+ {
+ if (Wt::asNumber(value(DemoField)))
+ {
+ // Demo account: password must be the same as the login name
+ if (valueText(PasswordField) != getLoginName())
+ error = Wt::WString::tr("Lms.Admin.User.demo-password-invalid");
+ }
+ else
+ {
+ // Evaluate the strength of the password for non demo accounts
+ if (!ServiceProvider<::Auth::IPasswordService>::get()->evaluatePasswordStrength(getLoginName(), valueText(PasswordField).toUTF8()))
+ error = Wt::WString::tr("Lms.password-too-weak");
+ }
+ }
+ else
+ {
+ error = Wt::WString::tr("Lms.password-must-not-be-empty");
+ }
+ return error;
+ }
void loadData()
{
@@ -116,6 +158,19 @@ class UserModel : public Wt::WFormModel
else if (user == LmsApp->getUser())
throw UserNotAllowedException {};
}
+
+ bool getExternalAuth() const
+ {
+ if (_userId)
+ {
+ auto transaction {LmsApp->getDbSession().createSharedTransaction()};
+
+ const Database::User::pointer user {Database::User::getById(LmsApp->getDbSession(), *_userId)};
+ return user->hasExternalAuth();
+ }
+ else
+ return Wt::asNumber(value(ExternalAuthField));
+ }
std::string getLoginName() const
{
@@ -144,21 +199,7 @@ class UserModel : public Wt::WFormModel
}
else if (field == PasswordField)
{
- if (!valueText(PasswordField).empty())
- {
- if (Wt::asNumber(value(DemoField)))
- {
- // Demo account: password must be the same as the login name
- if (valueText(PasswordField) != getLoginName())
- error = Wt::WString::tr("Lms.Admin.User.demo-password-invalid");
- }
- else
- {
- // Evaluate the strength of the password for non demo accounts
- if (!ServiceProvider<::Auth::IPasswordService>::get()->evaluatePasswordStrength(getLoginName(), valueText(PasswordField).toUTF8()))
- error = Wt::WString::tr("Lms.password-too-weak");
- }
- }
+ error = validatePassword();
}
else if (field == DemoField)
{
@@ -215,6 +256,12 @@ UserView::refreshView()
t->setCondition("if-has-last-login", true);
t->bindString("last-login", user->getLastLogin().toString(), Wt::TextFormat::Plain);
+
+
+ auto extCheckBox = std::make_unique();
+ extCheckBox->setChecked(user->hasExternalAuth());
+ t->setFormWidget(UserModel::ExternalAuthField, std::move(extCheckBox));
+ t->setCondition("if-external-auth", true);
}
else
{
@@ -222,6 +269,11 @@ UserView::refreshView()
t->setCondition("if-has-login", true);
t->setFormWidget(UserModel::LoginField, std::make_unique());
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-create"));
+
+ auto extCheckBox = std::make_unique();
+ extCheckBox->setChecked(true);
+ t->setCondition(UserModel::ExternalAuthField, false);
+ t->setFormWidget("external-auth", std::move(extCheckBox));
}
// Password
@@ -234,7 +286,7 @@ UserView::refreshView()
t->setFormWidget(UserModel::DemoField, std::make_unique());
if (!userId && ServiceProvider::get()->getBool("demo", false))
t->setCondition("if-demo", true);
-
+
Wt::WPushButton* saveBtn = t->bindNew("save-btn", Wt::WString::tr(userId ? "Lms.save" : "Lms.create"));
saveBtn->clicked().connect([=]()
{
diff --git a/src/lms/ui/common/Validators.cpp b/src/lms/ui/common/Validators.cpp
index 40bf8b91..a1050b8c 100644
--- a/src/lms/ui/common/Validators.cpp
+++ b/src/lms/ui/common/Validators.cpp
@@ -39,7 +39,7 @@ std::shared_ptr
createMandatoryValidator()
{
auto v = std::make_shared();
- v->setMandatory(true);
+ //sv->setMandatory(true);
return v;
}