diff --git a/librepomgr/CMakeLists.txt b/librepomgr/CMakeLists.txt index 52f084d..c3aef37 100644 --- a/librepomgr/CMakeLists.txt +++ b/librepomgr/CMakeLists.txt @@ -83,6 +83,10 @@ set(CONFIGURATION_PACKAGE_SUFFIX find_package(c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.11.0 REQUIRED) use_cpp_utilities(VISIBILITY PUBLIC) +# find passwordfile +find_package(passwordfile${CONFIGURATION_PACKAGE_SUFFIX} 5.0.0 REQUIRED) +use_password_file(VISIBILITY PUBLIC) + # find boost libraries option(BOOST_STATIC_LINKAGE "${STATIC_LINKAGE}" "link statically against Boost (instead of dynamically)") set(Boost_USE_MULTITHREADED ON) diff --git a/librepomgr/authentication.cpp b/librepomgr/authentication.cpp index b409b29..b4183af 100644 --- a/librepomgr/authentication.cpp +++ b/librepomgr/authentication.cpp @@ -51,39 +51,41 @@ static constexpr char toLower(const char c) return (c >= 'A' && c <= 'Z') ? (c + ('a' - 'A')) : c; } -UserPermissions ServiceSetup::Authentication::authenticate(std::string_view authorizationHeader) const +UserAuth ServiceSetup::Authentication::authenticate(std::string_view authorizationHeader) const { // extract user name and password from base64 encoded header value + auto auth = UserAuth(); if (!CppUtilities::startsWith(authorizationHeader, "Basic ") && authorizationHeader.size() < 100) { - return UserPermissions::DefaultPermissions; + return auth; } std::pair, std::uint32_t> data; try { data = CppUtilities::decodeBase64(authorizationHeader.data() + 6, static_cast(authorizationHeader.size() - 6)); } catch (const CppUtilities::ConversionException &) { - return UserPermissions::DefaultPermissions; + return auth; } const auto parts = CppUtilities::splitStringSimple>( std::string_view(reinterpret_cast(data.first.get()), data.second), ":", 2); if (parts.size() != 2) { - return UserPermissions::DefaultPermissions; + return auth; } // find relevant user const std::string_view userName = parts[0], password = parts[1]; if (userName.empty() || password.empty()) { - return UserPermissions::DefaultPermissions; + return auth; } if (userName == "try" && password == "again") { - return UserPermissions::TryAgain; + auth.permissions = UserPermissions::TryAgain; + return auth; } const auto user = users.find(std::string(userName)); if (user == users.cend()) { - return UserPermissions::DefaultPermissions; + return auth; } constexpr auto sha512HexSize = 128; if (user->second.passwordSha512.size() != sha512HexSize) { - return UserPermissions::DefaultPermissions; + return auth; } // hash password @@ -98,12 +100,15 @@ UserPermissions ServiceSetup::Authentication::authenticate(std::string_view auth for (unsigned char hashNumber : hash) { const auto digits = CppUtilities::numberToString(hashNumber, 16); if ((toLower(*(i++)) != toLower(digits.size() < 2 ? '0' : digits.front())) || (toLower(*(i++)) != toLower(digits.back()))) { - return UserPermissions::DefaultPermissions; + return auth; } } // return the user's permissions - return user->second.permissions; + auth.permissions = user->second.permissions; + auth.name = userName; + auth.password = password; + return auth; } } // namespace LibRepoMgr diff --git a/librepomgr/authentication.h b/librepomgr/authentication.h index 2a27171..18f6e3e 100644 --- a/librepomgr/authentication.h +++ b/librepomgr/authentication.h @@ -16,6 +16,12 @@ enum class UserPermissions : std::uint64_t { DefaultPermissions = ReadBuildActionsDetails, }; +struct UserAuth { + std::string_view name; + std::string_view password; + UserPermissions permissions = UserPermissions::DefaultPermissions; +}; + constexpr UserPermissions operator|(UserPermissions lhs, UserPermissions rhs) { return static_cast( diff --git a/librepomgr/serversetup.h b/librepomgr/serversetup.h index 1cba778..1e03a50 100644 --- a/librepomgr/serversetup.h +++ b/librepomgr/serversetup.h @@ -162,7 +162,7 @@ struct LIBREPOMGR_EXPORT ServiceSetup : public LibPkg::Lockable { std::unordered_map users; void applyConfig(const std::string &userName, const std::multimap &multimap); - UserPermissions authenticate(std::string_view authorizationHeader) const; + UserAuth authenticate(std::string_view authorizationHeader) const; } auth; struct LIBREPOMGR_EXPORT Locks { diff --git a/librepomgr/webapi/session.cpp b/librepomgr/webapi/session.cpp index b191ded..9fd1751 100644 --- a/librepomgr/webapi/session.cpp +++ b/librepomgr/webapi/session.cpp @@ -74,21 +74,34 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred) respond(Render::makeAuthRequired(request)); return; } - const auto userPermissions = m_setup.auth.authenticate(std::string_view(authInfo->value().data(), authInfo->value().size())); + const auto userAuth = m_setup.auth.authenticate(std::string_view(authInfo->value().data(), authInfo->value().size())); using PermissionFlags = std::underlying_type_t; - if (static_cast(userPermissions) & static_cast(UserPermissions::TryAgain)) { + if (static_cast(userAuth.permissions) & static_cast(UserPermissions::TryAgain)) { // send the 401 response again if credentials are 'try again' to show the password prompt for the XMLHttpRequest again // note: This is kind of a hack. Maybe there's a better solution to make XMLHttpRequest forget wrongly entered credentials // and instead show the login prompt again? respond(Render::makeAuthRequired(request)); return; } - if ((static_cast(requiredPermissions) & static_cast(userPermissions)) + if ((static_cast(requiredPermissions) & static_cast(userAuth.permissions)) != static_cast(requiredPermissions)) { respond(Render::makeForbidden(request)); return; } + // prepare file with secrets for user + if(!userAuth.name.empty() && !userAuth.password.empty()) { + try { + m_secrets.clear(); + m_secrets.setPath(argsToString("secrets/"sv, userAuth.name)); + m_secrets.setPassword(userAuth.password.data(), userAuth.password.size()); + } catch (const std::ios_base::failure &e) { + cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets.path() << "\" (before preparing new one): " << e.what() << Phrases::End; + } + } } + + // invoke the route's handler + // note: The error handling is in vain if an exception in a deferred handler is thrown. try { route.handler(move(params), std::bind( @@ -96,6 +109,14 @@ void Session::received(boost::system::error_code ec, size_t bytesTransferred) } catch (const BadRequest &badRequest) { respond(Render::makeBadRequest(request, badRequest.what())); } + + // discard password; secrets are expected to be read on the immediate call of the route + try { + m_secrets.clearPassword(); + m_secrets.close(); + } catch (const std::ios_base::failure &e) { + cerr << Phrases::WarningMessage << "Failed to close password file \"" << m_secrets.path() << "\": " << e.what() << Phrases::End; + } return; } diff --git a/librepomgr/webapi/session.h b/librepomgr/webapi/session.h index 07a1bec..47d0d5c 100644 --- a/librepomgr/webapi/session.h +++ b/librepomgr/webapi/session.h @@ -3,6 +3,8 @@ #include "./typedefs.h" +#include + #include #include @@ -31,6 +33,7 @@ public: void received(boost::system::error_code ec, std::size_t bytesTransferred); void responded(boost::system::error_code ec, std::size_t bytesTransferred, bool shouldClose); static boost::beast::string_view determineMimeType(std::string_view path, boost::beast::string_view fallback = "text/plain"); + Io::PasswordFile &secrets(); private: boost::asio::ip::tcp::socket m_socket; @@ -39,6 +42,7 @@ private: std::unique_ptr m_parser; ServiceSetup &m_setup; std::shared_ptr m_res; + Io::PasswordFile m_secrets; }; inline Session::Session(boost::asio::ip::tcp::socket &&socket, ServiceSetup &setup) @@ -63,6 +67,11 @@ inline boost::asio::ip::tcp::socket &Session::socket() return m_socket; } +inline Io::PasswordFile &Session::secrets() +{ + return m_secrets; +} + } // namespace WebAPI } // namespace LibRepoMgr