Allow secrets stored in encrypted files to be loaded from route handlers
This commit is contained in:
parent
05176d70ef
commit
df2b5ba9f6
|
@ -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)
|
||||
|
|
|
@ -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::unique_ptr<std::uint8_t[]>, std::uint32_t> data;
|
||||
try {
|
||||
data = CppUtilities::decodeBase64(authorizationHeader.data() + 6, static_cast<std::uint32_t>(authorizationHeader.size() - 6));
|
||||
} catch (const CppUtilities::ConversionException &) {
|
||||
return UserPermissions::DefaultPermissions;
|
||||
return auth;
|
||||
}
|
||||
const auto parts = CppUtilities::splitStringSimple<std::vector<std::string_view>>(
|
||||
std::string_view(reinterpret_cast<const char *>(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
|
||||
|
|
|
@ -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<UserPermissions>(
|
||||
|
|
|
@ -162,7 +162,7 @@ struct LIBREPOMGR_EXPORT ServiceSetup : public LibPkg::Lockable {
|
|||
std::unordered_map<std::string, UserInfo> users;
|
||||
|
||||
void applyConfig(const std::string &userName, const std::multimap<std::string, std::string> &multimap);
|
||||
UserPermissions authenticate(std::string_view authorizationHeader) const;
|
||||
UserAuth authenticate(std::string_view authorizationHeader) const;
|
||||
} auth;
|
||||
|
||||
struct LIBREPOMGR_EXPORT Locks {
|
||||
|
|
|
@ -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<UserPermissions>;
|
||||
if (static_cast<PermissionFlags>(userPermissions) & static_cast<PermissionFlags>(UserPermissions::TryAgain)) {
|
||||
if (static_cast<PermissionFlags>(userAuth.permissions) & static_cast<PermissionFlags>(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<PermissionFlags>(requiredPermissions) & static_cast<PermissionFlags>(userPermissions))
|
||||
if ((static_cast<PermissionFlags>(requiredPermissions) & static_cast<PermissionFlags>(userAuth.permissions))
|
||||
!= static_cast<PermissionFlags>(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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "./typedefs.h"
|
||||
|
||||
#include <passwordfile/io/passwordfile.h>
|
||||
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/http.hpp>
|
||||
|
||||
|
@ -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<RequestParser> m_parser;
|
||||
ServiceSetup &m_setup;
|
||||
std::shared_ptr<void> 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
|
||||
|
||||
|
|
Loading…
Reference in New Issue