From 22eae833f8fc932c423af28cddef3453cf6ce4cc Mon Sep 17 00:00:00 2001 From: Tazio Ceri Date: Wed, 3 Jun 2020 22:41:54 +0200 Subject: [PATCH 01/10] Improved build system to check compile time dependencies --- CMakeLists.txt | 5 +- cmake/modules/FindFFMPEGAV.cmake | 26 ++++++++++ cmake/modules/FindPStreams.cmake | 17 +++++++ cmake/modules/FindTaglib.cmake | 87 ++++++++++++++++++++++++++++++++ src/libs/av/CMakeLists.txt | 11 ++++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 cmake/modules/FindFFMPEGAV.cmake create mode 100644 cmake/modules/FindPStreams.cmake create mode 100644 cmake/modules/FindTaglib.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 1faefee5..d8601fa8 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,6 +10,10 @@ 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) pkg_check_modules(GRAPHICSMAGICKXX REQUIRED GraphicsMagick++) add_subdirectory(src) 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/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/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) From f8fb64b5e44e7d865cfac7e01f2bfc200c3d51ef Mon Sep 17 00:00:00 2001 From: Tazio Ceri Date: Fri, 19 Jun 2020 00:32:24 +0200 Subject: [PATCH 02/10] Implemented PAM authentication --- CMakeLists.txt | 10 ++ approot/admin-user.xml | 11 ++ approot/messages.xml | 3 + cmake/modules/FindPAM.cmake | 73 +++++++++++++ docroot/css/lms.css | 3 + src/libs/auth/CMakeLists.txt | 1 + src/libs/auth/impl/AuthTokenService.cpp | 2 - src/libs/auth/impl/AuthTokenService.hpp | 2 - src/libs/auth/impl/PasswordService.cpp | 107 +++++++++++++++++++- src/libs/database/include/database/User.hpp | 5 + src/lms/ui/admin/UserView.cpp | 54 +++++++++- src/lms/ui/common/Validators.cpp | 2 +- 12 files changed, 262 insertions(+), 11 deletions(-) create mode 100644 cmake/modules/FindPAM.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index d8601fa8..67572a74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,10 +14,20 @@ 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/admin-user.xml b/approot/admin-user.xml index 9ce8c4f4..14f69aa0 100644 --- a/approot/admin-user.xml +++ b/approot/admin-user.xml @@ -38,6 +38,17 @@ ${password-info class="help-block"} +
+ +
+ + ${external-auth} + ${external-auth-info class="help-block"} + +
+
${}
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/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/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/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 3bb52e6d..961bd906 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,6 +102,11 @@ 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); } } @@ -116,6 +125,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,7 +166,14 @@ class UserModel : public Wt::WFormModel } else if (field == PasswordField) { - if (!valueText(PasswordField).empty()) + 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))) { @@ -157,8 +186,13 @@ class UserModel : public Wt::WFormModel // 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"); + } } else if (field == DemoField) { @@ -167,6 +201,11 @@ class UserModel : public Wt::WFormModel if (Wt::asNumber(value(DemoField)) && Database::User::getDemo(LmsApp->getDbSession())) error = Wt::WString::tr("Lms.Admin.User.demo-account-already-exists"); } +// else if (field == ExternalAuthField) +// { +// if (!valueText(PasswordField).empty()) +// error = Wt::WString::tr("Lms.password_must_be_empty_for_ext"); +// } if (error.empty()) return Wt::WFormModel::validateField(field); @@ -215,6 +254,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 +267,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 @@ -233,7 +283,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; } From 1ce6aa781ec950ea00c082e99859a207a22369b0 Mon Sep 17 00:00:00 2001 From: Tazio Ceri Date: Sat, 27 Jun 2020 00:13:54 +0200 Subject: [PATCH 03/10] Review comments --- approot/admin-user.xml | 11 ------ conf/lms | 9 +++++ src/lms/ui/admin/UserView.cpp | 66 ++++++++++++++++++----------------- 3 files changed, 43 insertions(+), 43 deletions(-) create mode 100644 conf/lms diff --git a/approot/admin-user.xml b/approot/admin-user.xml index 14f69aa0..9ce8c4f4 100644 --- a/approot/admin-user.xml +++ b/approot/admin-user.xml @@ -38,17 +38,6 @@ ${password-info class="help-block"}
-
- -
- - ${external-auth} - ${external-auth-info class="help-block"} - -
-
${}
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/src/lms/ui/admin/UserView.cpp b/src/lms/ui/admin/UserView.cpp index 961bd906..ea94a769 100644 --- a/src/lms/ui/admin/UserView.cpp +++ b/src/lms/ui/admin/UserView.cpp @@ -111,6 +111,39 @@ class UserModel : public Wt::WFormModel } 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() { @@ -166,33 +199,7 @@ class UserModel : public Wt::WFormModel } else if (field == PasswordField) { - 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"); - } + error = validatePassword(); } else if (field == DemoField) { @@ -201,11 +208,6 @@ class UserModel : public Wt::WFormModel if (Wt::asNumber(value(DemoField)) && Database::User::getDemo(LmsApp->getDbSession())) error = Wt::WString::tr("Lms.Admin.User.demo-account-already-exists"); } -// else if (field == ExternalAuthField) -// { -// if (!valueText(PasswordField).empty()) -// error = Wt::WString::tr("Lms.password_must_be_empty_for_ext"); -// } if (error.empty()) return Wt::WFormModel::validateField(field); From 50b0fd1cb8bd5013fd24ab71ff71ebe89bd57780 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 5 Jul 2020 15:15:51 +0200 Subject: [PATCH 04/10] Various fixes + refacto --- CMakeLists.txt | 8 - README.md | 4 +- approot/admin-user.xml | 9 + approot/messages.xml | 7 +- approot/messages_fr.xml | 4 + cmake/modules/FindFFMPEGAV.cmake | 3 - src/libs/auth/CMakeLists.txt | 5 + src/libs/auth/impl/LoginThrottler.hpp | 2 - src/libs/auth/impl/PasswordService.cpp | 128 ++++--------- src/libs/auth/impl/PasswordService.hpp | 5 +- src/libs/auth/impl/pam/PAM.cpp | 177 ++++++++++++++++++ src/libs/auth/impl/pam/PAM.hpp | 32 ++++ .../auth/include/auth/IPasswordService.hpp | 3 + src/libs/database/impl/Session.cpp | 9 +- src/libs/database/impl/User.cpp | 8 +- src/libs/database/include/database/User.hpp | 20 +- src/libs/subsonic/impl/SubsonicResource.cpp | 4 +- src/libs/utils/impl/String.cpp | 1 + src/lms/ui/admin/InitWizardView.cpp | 4 +- src/lms/ui/admin/UserView.cpp | 131 +++++++------ src/test/database/DatabaseTest.cpp | 22 +-- 21 files changed, 390 insertions(+), 196 deletions(-) create mode 100644 src/libs/auth/impl/pam/PAM.cpp create mode 100644 src/libs/auth/impl/pam/PAM.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 67572a74..2d0c04fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,14 +17,6 @@ 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 diff --git a/README.md b/README.md index 2d1b24a0..d8e1773e 100644 --- a/README.md +++ b/README.md @@ -90,9 +90,11 @@ __Notes__: * a C++17 compiler is needed * ffmpeg version 4 minimum is required ```sh -apt-get install g++ cmake libboost-system-dev libavutil-dev libavformat-dev libgraphicsmagick++1-dev libconfig++-dev libpstreams-dev ffmpeg libtag1-dev +apt-get install g++ cmake libboost-system-dev libavutil-dev libavformat-dev libgraphicsmagick++1-dev libconfig++-dev libpstreams-dev ffmpeg libtag1-dev libpam0g-dev ``` +__Note__: package libpam0g-dev is optional (only for using PAM authentication) + You also need _Wt4_, which is not packaged yet on _Debian_. See [installation instructions](https://www.webtoolkit.eu/wt/doc/reference/html/InstallationUnix.html).
No optional requirement is needed, except openSSL if you plan not to deploy behind a reverse proxy (which is not recommended). diff --git a/approot/admin-user.xml b/approot/admin-user.xml index c3adc97a..80a97d25 100644 --- a/approot/admin-user.xml +++ b/approot/admin-user.xml @@ -30,6 +30,15 @@
${} +
+ +
+ ${auth-mode} + ${auth-mode-info class="help-block"} +
+
- +
+ +
+ ${auth-mode} + ${auth-mode-info class="help-block"} +
+
-