Skip to content

Commit

Permalink
feat: enter password through ui
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolaDucak committed Feb 18, 2024
1 parent d32011c commit f4c5ef2
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 89 deletions.
25 changes: 12 additions & 13 deletions source/log/local_log_repository.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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!"};
}
}
Expand Down
18 changes: 16 additions & 2 deletions source/log/log_repository_crypto_applyer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
3 changes: 3 additions & 0 deletions source/log/log_repository_crypto_applyer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
Expand Down
41 changes: 37 additions & 4 deletions source/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <ftxui/component/component.hpp>
#include <ftxui/component/component_options.hpp>
#include <ftxui/component/screen_interactive.hpp>
#include <ftxui/dom/elements.hpp>
#include <ftxui/dom/node.hpp>
#include <iomanip>
#include <iostream>
#include <optional>
Expand All @@ -19,17 +24,45 @@
#include "version.hpp"
#include <boost/program_options.hpp>

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<view::AnnualView>(date::Date::getToday(), conf.sundayStart);
auto repo = std::make_shared<log::LocalLogRepository>(pathProvider, conf.password);
auto repo = std::make_shared<log::LocalLogRepository>(pathProvider, pass);
auto editor = [&]() -> std::shared_ptr<editor::EditorBase> {
if (conf.password.empty()) {
if (pass.empty()) {
return std::make_shared<editor::EnvBasedEditor>(pathProvider);
}
return std::make_shared<editor::EncryptedFileEditor>(pathProvider, conf.password);
return std::make_shared<editor::EncryptedFileEditor>(pathProvider, pass);
}();
auto gitRepo = [&]() -> std::optional<utils::GitRepo> {
if (conf.repoConfig) {
Expand Down
149 changes: 79 additions & 70 deletions test/local_log_repository_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit f4c5ef2

Please sign in to comment.