diff --git a/source/log/local_log_repository.cpp b/source/log/local_log_repository.cpp index 1cd7a5c..f27e311 100644 --- a/source/log/local_log_repository.cpp +++ b/source/log/local_log_repository.cpp @@ -18,20 +18,19 @@ using namespace date; LocalLogRepository::LocalLogRepository(LocalFSLogFilePathProvider pathProvider, std::string password) : m_pathProvider(std::move(pathProvider)), m_password{std::move(password)} { - // create log directory if it isn't already created std::filesystem::create_directories(m_pathProvider.getLogDirPath()); - // in case that there is an encryption marker file & no password is provided, throw - auto clePath = - m_pathProvider.getLogDirPath() / LogRepositoryCryptoApplier::encryptetLogRepoMarkerFile; - if (std::filesystem::exists(clePath) && m_password.empty()) { - throw std::runtime_error{"Password is required to open encrypted log repository!"}; - } - // in case that the decrypted contents of the encryptetLogRepoMarkerFile does not start - // with encryption marker, throw due to invalid password - if (std::filesystem::exists(clePath) && not m_password.empty()) { - auto cleStream = std::ifstream{clePath}; - auto decryptedMarker = utils::decrypt(m_password, cleStream); - if (decryptedMarker.find(LogRepositoryCryptoApplier::encryptetLogRepoMarker) != 0) { + const auto isEncrypted = + LogRepositoryCryptoApplier::isEncrypted(m_pathProvider.getLogDirPath()); + if (m_password.empty()) { + if (isEncrypted) { + throw std::runtime_error{"Password is required to open encrypted log repository!"}; + } + } else { + if (not isEncrypted) { + throw std::runtime_error{"Password provided for a non encrypted repository!"}; + } + if (not LogRepositoryCryptoApplier::isDecryptionPasswordValid( + m_pathProvider.getLogDirPath(), m_password)) { throw std::runtime_error{"Invalid password provided!"}; } } diff --git a/source/log/log_repository_crypto_applyer.cpp b/source/log/log_repository_crypto_applyer.cpp index 698e886..b76e0f8 100644 --- a/source/log/log_repository_crypto_applyer.cpp +++ b/source/log/log_repository_crypto_applyer.cpp @@ -6,7 +6,7 @@ void updateEncryptionMarkerfile(Crypto crypto, const std::filesystem::path &logD const std::string &password) { const auto markerFilePath = logDirPath / LogRepositoryCryptoApplier::encryptetLogRepoMarkerFile; if (crypto == Crypto::Encrypt) { - std::ofstream cle(markerFilePath); + std::ofstream cle{markerFilePath, std::ios::binary}; if (not cle.is_open()) { // This would be an unfortuane situation, the repo has already been encrypted // but the encryption marker file is not added. @@ -103,7 +103,6 @@ void LogRepositoryCryptoApplier::apply(const std::string &password, try { processLogFile(entry); } catch (const std::exception &e) { - // Handle exceptions here or rethrow them if necessary // Do not throw here, accumulate errors instead errors.push_back(e); } @@ -124,4 +123,19 @@ void LogRepositoryCryptoApplier::apply(const std::string &password, updateEncryptionMarkerfile(crypto, logDirPath, password); } +bool LogRepositoryCryptoApplier::isEncrypted(const std::filesystem::path &logDirPath) { + bool encryptionMarkerfilePresent = std::filesystem::exists( + logDirPath / LogRepositoryCryptoApplier::encryptetLogRepoMarkerFile); + return encryptionMarkerfilePresent; +} + +bool LogRepositoryCryptoApplier::isDecryptionPasswordValid(const std::filesystem::path &logDirPath, + const std::string &password) { + const auto encryptionMarkerfile = + logDirPath / LogRepositoryCryptoApplier::encryptetLogRepoMarkerFile; + auto cleStream = std::ifstream{encryptionMarkerfile}; + auto decryptedMarker = utils::decrypt(password, cleStream); + return decryptedMarker.find(LogRepositoryCryptoApplier::encryptetLogRepoMarker) == 0; +} + } // namespace caps_log diff --git a/source/log/log_repository_crypto_applyer.hpp b/source/log/log_repository_crypto_applyer.hpp index bd0e0d7..2047aa9 100644 --- a/source/log/log_repository_crypto_applyer.hpp +++ b/source/log/log_repository_crypto_applyer.hpp @@ -37,6 +37,9 @@ class LogRepositoryCryptoApplier { static constexpr auto encryptetLogRepoMarkerFile = ".cle"; static void apply(const std::string &password, const std::filesystem::path &logDirPath, const std::string &logFilenameFormat, Crypto crypto); + static bool isEncrypted(const std::filesystem::path &logDirPath); + static bool isDecryptionPasswordValid(const std::filesystem::path &logDirPath, + const std::string &password); private: LogRepositoryCryptoApplier() {} diff --git a/source/main.cpp b/source/main.cpp index a98ef57..2892a2d 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,6 +1,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -19,17 +24,45 @@ #include "version.hpp" #include +std::string promptPassword(const caps_log::Config &config) { + using namespace ftxui; + auto screen = ScreenInteractive::Fullscreen(); + std::string password; + const auto input = Input(InputOption{.content = &password, + .password = true, + .multiline = false, + .on_enter = screen.ExitLoopClosure()}); + const auto button_submit = Button("Submit", screen.ExitLoopClosure(), ButtonOption::Ascii()); + const auto button_quit = Button("Quit", screen.ExitLoopClosure(), ButtonOption::Ascii()); + const auto container = Container::Vertical({input, button_submit, button_quit}); + + const Component renderer = Renderer(container, [container]() { + return window(text("Pasword required!"), container->Render()) | center; + }); + + screen.Loop(renderer); + return password; +} + +std::string getPassword(const caps_log::Config &conf) { + std::string pass = conf.password; + if (caps_log::LogRepositoryCryptoApplier::isEncrypted(conf.logDirPath) && pass.empty()) { + pass = promptPassword(conf); + } + return pass; +} + auto makeCapsLog(const caps_log::Config &conf) { using namespace caps_log; - + const auto pass = getPassword(conf); auto pathProvider = log::LocalFSLogFilePathProvider{conf.logDirPath, conf.logFilenameFormat}; auto view = std::make_shared(date::Date::getToday(), conf.sundayStart); - auto repo = std::make_shared(pathProvider, conf.password); + auto repo = std::make_shared(pathProvider, pass); auto editor = [&]() -> std::shared_ptr { - if (conf.password.empty()) { + if (pass.empty()) { return std::make_shared(pathProvider); } - return std::make_shared(pathProvider, conf.password); + return std::make_shared(pathProvider, pass); }(); auto gitRepo = [&]() -> std::optional { if (conf.repoConfig) { diff --git a/test/local_log_repository_test.cpp b/test/local_log_repository_test.cpp index 80dcf55..d009f99 100644 --- a/test/local_log_repository_test.cpp +++ b/test/local_log_repository_test.cpp @@ -80,115 +80,126 @@ TEST_F(LocalLogRepositoryTest, Write) { ASSERT_TRUE(std::filesystem::exists(TMPDirPathProvider.path(date))); } -TEST_F(LocalLogRepositoryTest, EncryptedRead) { +class EncryptedLocalLogRepositoryTest : public LocalLogRepositoryTest { + public: + void SetUp() override { + LocalLogRepositoryTest::SetUp(); + writeDummyEncryptedRepoMarkerFile(TMPDirPathProvider.getLogDirPath(), dummyPassword); + } + + private: + static void writeDummyEncryptedRepoMarkerFile(const std::filesystem::path &path, + const std::string &password) { + std::ofstream cle{path / caps_log::LogRepositoryCryptoApplier::encryptetLogRepoMarkerFile, + std::ios::binary}; + if (not cle.is_open()) { + throw std::runtime_error{"Failed writing encryption marker file"}; + } + std::istringstream oss{caps_log::LogRepositoryCryptoApplier::encryptetLogRepoMarker}; + cle << caps_log::utils::encrypt(password, oss); + } + + protected: + static constexpr auto dummyLogContent = "Dummy string"; + static constexpr auto dummyPassword = "dummy"; + static constexpr auto encryptedDummyLogContent = "\x16\x1A/l\x1\x1" + "7\a\x1\xEE\xD2n"; +}; + +TEST_F(EncryptedLocalLogRepositoryTest, EncryptedRead) { const auto selectedDate = Date{25, 5, 2005}; - const auto *const dummyPassword = "dummy"; auto repo = LocalLogRepository(TMPDirPathProvider, dummyPassword); - const std::string logContent = "Dummy string"; - const std::string encLogContent = "\x16\x1A/l\x1\x1" - "7\a\x1\xEE\xD2n"; ASSERT_FALSE(repo.read(selectedDate).has_value()); - writeDummyLog(selectedDate, encLogContent); + writeDummyLog(selectedDate, encryptedDummyLogContent); auto log = repo.read(selectedDate); ASSERT_TRUE(log.has_value()); ASSERT_EQ(log->getDate(), selectedDate); - ASSERT_EQ(log->getContent(), logContent); + ASSERT_EQ(log->getContent(), dummyLogContent); - EXPECT_EQ(readFile(TMPDirPathProvider.path(selectedDate)), encLogContent); + EXPECT_EQ(readFile(TMPDirPathProvider.path(selectedDate)), encryptedDummyLogContent); } -TEST_F(LocalLogRepositoryTest, EncryptedWrite) { +TEST_F(EncryptedLocalLogRepositoryTest, EncryptedWrite) { const auto date = Date{25, 5, 2005}; - const auto *const dummyPassword = "dummy"; auto repo = LocalLogRepository(TMPDirPathProvider, dummyPassword); - const std::string logContent = "Dummy string"; - const std::string encLogContent = "\x16\x1A/l\x1\x1" - "7\a\x1\xEE\xD2n"; - repo.write(LogFile{date, logContent}); + repo.write(LogFile{date, dummyLogContent}); ASSERT_TRUE(std::filesystem::exists(TMPDirPathProvider.path(date))); - EXPECT_EQ(readFile(TMPDirPathProvider.path(date)), encLogContent); + EXPECT_EQ(readFile(TMPDirPathProvider.path(date)), encryptedDummyLogContent); } -TEST_F(LocalLogRepositoryTest, EncryptionRoundtrip) { +TEST_F(EncryptedLocalLogRepositoryTest, EncryptionRoundtrip) { const auto date = Date{25, 5, 2005}; - const auto *const dummyPassword = "dummy"; auto repo = LocalLogRepository(TMPDirPathProvider, dummyPassword); - const std::string logContent = "Dummy string"; - repo.write(LogFile{date, logContent}); + repo.write(LogFile{date, dummyLogContent}); ASSERT_TRUE(std::filesystem::exists(TMPDirPathProvider.path(date))); auto log = repo.read(date); ASSERT_TRUE(log.has_value()); ASSERT_EQ(log->getDate(), date); - ASSERT_EQ(log->getContent(), logContent); + ASSERT_EQ(log->getContent(), dummyLogContent); } -TEST_F(LocalLogRepositoryTest, RoundtripCryptoApplier) { +class LogRepositoryCryptoApplierTest : public LocalLogRepositoryTest { + protected: + static constexpr auto dummyLogContent = "Dummy string"; + static constexpr auto dummyPassword = "dummy"; + static constexpr auto encryptedDummyLogContent = "\x16\x1A/l\x1\x1" + "7\a\x1\xEE\xD2n"; + static const Date dummyDate; +}; +const Date LogRepositoryCryptoApplierTest::dummyDate{25, 5, 2005}; + +TEST_F(LogRepositoryCryptoApplierTest, RoundtripCryptoApplier) { const auto date1 = Date{25, 5, 2005}; const auto date2 = Date{25, 5, 2015}; - const std::string logContent = "Dummy string"; - const auto *const dummyPassword = "dummy"; - const std::string encLogContent = "\x16\x1A/l\x1\x1" - "7\a\x1\xEE\xD2n"; // create unencrypted files - writeDummyLog(date1, logContent); - writeDummyLog(date2, logContent); - EXPECT_EQ(logContent, readFile(TMPDirPathProvider.path(date1))); - EXPECT_EQ(logContent, readFile(TMPDirPathProvider.path(date2))); + writeDummyLog(date1, dummyLogContent); + writeDummyLog(date2, dummyLogContent); + EXPECT_EQ(dummyLogContent, readFile(TMPDirPathProvider.path(date1))); + EXPECT_EQ(dummyLogContent, readFile(TMPDirPathProvider.path(date2))); // encrypt them & verify encription caps_log::LogRepositoryCryptoApplier::apply(dummyPassword, TMPDirPathProvider.getLogDirPath(), TMPDirPathProvider.getLogFilenameFormat(), caps_log::Crypto::Encrypt); - EXPECT_EQ(encLogContent, readFile(TMPDirPathProvider.path(date1))); - EXPECT_EQ(encLogContent, readFile(TMPDirPathProvider.path(date2))); + EXPECT_EQ(encryptedDummyLogContent, readFile(TMPDirPathProvider.path(date1))); + EXPECT_EQ(encryptedDummyLogContent, readFile(TMPDirPathProvider.path(date2))); // decrypt them & verify encription caps_log::LogRepositoryCryptoApplier::apply(dummyPassword, TMPDirPathProvider.getLogDirPath(), TMPDirPathProvider.getLogFilenameFormat(), caps_log::Crypto::Decrypt); - EXPECT_EQ(logContent, readFile(TMPDirPathProvider.path(date1))); - EXPECT_EQ(logContent, readFile(TMPDirPathProvider.path(date2))); + EXPECT_EQ(dummyLogContent, readFile(TMPDirPathProvider.path(date1))); + EXPECT_EQ(dummyLogContent, readFile(TMPDirPathProvider.path(date2))); } -TEST_F(LocalLogRepositoryTest, CryptoApplier_IgnoresFilesNotMatchingTheLogFilenamePattern) { - const auto date1 = Date{25, 5, 2005}; - const std::string logContent = "Dummy string"; - const auto *const dummyPassword = "dummy"; +TEST_F(LogRepositoryCryptoApplierTest, IgnoresFilesNotMatchingTheLogFilenamePattern) { const auto *const dummyfileName = "dummy.txt"; - const std::string encLogContent = "\x16\x1A/l\x1\x1" - "7\a\x1\xEE\xD2n"; - // create unencrypted files - writeDummyLog(date1, logContent); - writeDummyFile(TEST_LOG_DIRECTORY / dummyfileName, logContent); - EXPECT_EQ(logContent, readFile(TMPDirPathProvider.path(date1))); - EXPECT_EQ(logContent, readFile(TEST_LOG_DIRECTORY / dummyfileName)); + writeDummyLog(dummyDate, dummyLogContent); + writeDummyFile(TEST_LOG_DIRECTORY / dummyfileName, dummyLogContent); + EXPECT_EQ(dummyLogContent, readFile(TMPDirPathProvider.path(dummyDate))); + EXPECT_EQ(dummyLogContent, readFile(TEST_LOG_DIRECTORY / dummyfileName)); // encrypt them & verify encription caps_log::LogRepositoryCryptoApplier::apply(dummyPassword, TMPDirPathProvider.getLogDirPath(), TMPDirPathProvider.getLogFilenameFormat(), caps_log::Crypto::Encrypt); - EXPECT_EQ(encLogContent, readFile(TMPDirPathProvider.path(date1))); - EXPECT_EQ(logContent, readFile(TEST_LOG_DIRECTORY / dummyfileName)); + EXPECT_EQ(encryptedDummyLogContent, readFile(TMPDirPathProvider.path(dummyDate))); + EXPECT_EQ(dummyLogContent, readFile(TEST_LOG_DIRECTORY / dummyfileName)); } -TEST_F(LocalLogRepositoryTest, CryptoApplier_DoesNotApplyAnOperationTwice) { - const auto date1 = Date{25, 5, 2005}; - const std::string logContent = "Dummy string"; - const auto *const dummyPassword = "dummy"; - const std::string encLogContent = "\x16\x1A/l\x1\x1" - "7\a\x1\xEE\xD2n"; +TEST_F(LogRepositoryCryptoApplierTest, DoesNotApplyAnOperationTwice) { - writeDummyLog(date1, logContent); + writeDummyLog(dummyDate, dummyLogContent); caps_log::LogRepositoryCryptoApplier::apply(dummyPassword, TMPDirPathProvider.getLogDirPath(), TMPDirPathProvider.getLogFilenameFormat(), caps_log::Crypto::Encrypt); @@ -198,31 +209,29 @@ TEST_F(LocalLogRepositoryTest, CryptoApplier_DoesNotApplyAnOperationTwice) { caps_log::CryptoAlreadyAppliedError); } -TEST_F(LocalLogRepositoryTest, ErrorOnEncryptedRepoWithNoPassword) { +class LogRepoConstructionAfterCryptoApplier : public LocalLogRepositoryTest { + protected: + static constexpr auto dummyLogContent = "Dummy string"; + static constexpr auto dummyPassword = "dummy"; +}; + +TEST_F(LogRepoConstructionAfterCryptoApplier, ErrorOnEncryptedRepoWithNoPassword) { const auto date = Date{25, 5, 2005}; - const auto *const dummyPassword = "dummy"; - const std::string logContent = "Dummy string"; - { - writeDummyLog(date, logContent); - caps_log::LogRepositoryCryptoApplier::apply( - dummyPassword, TMPDirPathProvider.getLogDirPath(), - TMPDirPathProvider.getLogFilenameFormat(), caps_log::Crypto::Encrypt); + writeDummyLog(date, dummyLogContent); + caps_log::LogRepositoryCryptoApplier::apply(dummyPassword, TMPDirPathProvider.getLogDirPath(), + TMPDirPathProvider.getLogFilenameFormat(), + caps_log::Crypto::Encrypt); - EXPECT_THROW({ const auto repo = LocalLogRepository(TMPDirPathProvider); }, - std::runtime_error); - EXPECT_NO_THROW( - { const auto repo = LocalLogRepository(TMPDirPathProvider, dummyPassword); }); - } + EXPECT_THROW({ const auto repo = LocalLogRepository(TMPDirPathProvider); }, std::runtime_error); + EXPECT_NO_THROW({ const auto repo = LocalLogRepository(TMPDirPathProvider, dummyPassword); }); } -TEST_F(LocalLogRepositoryTest, ErrorOnEncryptedRepoWithBadPassword) { +TEST_F(LogRepoConstructionAfterCryptoApplier, ErrorOnEncryptedRepoWithBadPassword) { const auto date = Date{25, 5, 2005}; - const auto *const dummyPassword = "dummy"; const auto *const badPassword = "bad dummy"; - const std::string logContent = "Dummy string"; - writeDummyLog(date, logContent); + writeDummyLog(date, dummyLogContent); caps_log::LogRepositoryCryptoApplier::apply(dummyPassword, TMPDirPathProvider.getLogDirPath(), TMPDirPathProvider.getLogFilenameFormat(), caps_log::Crypto::Encrypt);