Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/workflows/build-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ jobs:
-fprofile-arcs
-ftest-coverage
-fprofile-abs-path
-fprofile-update=atomic

- os: ubuntu-latest
buildname: Qt 6 Clang
Expand Down Expand Up @@ -201,13 +202,13 @@ jobs:
${{runner.workspace}}/build/copyq/${{ matrix.cmake_preset }}
run: |
lcov --capture --directory . \
--base-directory '${{github.workspace}}' \
--no-external \
--rc branch_coverage=1 \
--rc geninfo_unexecuted_blocks=1 \
--ignore-errors mismatch \
--rc geninfo_no_exception_branch=1 \
--output-file coverage-full.info
# Remove system and 3rd party files
# Remove 3rd party files in source tree
lcov --remove coverage-full.info \
'/usr/*' \
'*/plugins/itemfakevim/fakevim/*' \
'*/src/gui/fix_icon_id.h' \
--output-file coverage.info
Expand Down
18 changes: 10 additions & 8 deletions src/gui/clipboardbrowserplaceholder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ void ClipboardBrowserPlaceholder::setStoreItems(bool store)

void ClipboardBrowserPlaceholder::setEncryptedExpireSeconds(int seconds)
{
if (shouldPromptForLockedTabPassword()) {
m_passwordExpiredAt = std::chrono::steady_clock::now();
}
m_encryptedExpireSeconds = seconds;
restartPasswordExpiry();
}
Expand Down Expand Up @@ -338,22 +341,18 @@ void ClipboardBrowserPlaceholder::restartExpiring()
m_timerExpire.stop();
}

int ClipboardBrowserPlaceholder::encryptedExpireSeconds() const
{
return m_encryptedExpireSeconds > 0
? m_encryptedExpireSeconds : m_sharedData->encryptedExpireSeconds;
}

int ClipboardBrowserPlaceholder::encryptedExpireRemainingMs() const
{
if (!m_sharedData->passwordPrompt)
return -1;

const int timeoutSeconds = encryptedExpireSeconds();
const int timeoutSeconds = m_encryptedExpireSeconds;
if (timeoutSeconds <= 0)
return -1;

const qint64 elapsedMs = m_sharedData->passwordPrompt->elapsedMsSinceLastSuccessfulPasswordPrompt();
const auto lastPrompt = m_sharedData->passwordPrompt->lastSuccessfulPasswordPromptTime();
const auto now = std::chrono::steady_clock::now();
const auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastPrompt).count();
const qint64 timeoutMs = static_cast<qint64>(timeoutSeconds) * 1000;
return qMax<qint64>(0, timeoutMs - elapsedMs);
}
Expand All @@ -363,6 +362,9 @@ bool ClipboardBrowserPlaceholder::shouldPromptForLockedTabPassword() const
if (!m_sharedData->tabsEncrypted || !m_sharedData->passwordPrompt || !m_sharedData->encryptionKey.isValid())
return false;

if (m_passwordExpiredAt > m_sharedData->passwordPrompt->lastSuccessfulPasswordPromptTime())
return true;

const int remainingMs = encryptedExpireRemainingMs();
return remainingMs == 0;
}
Expand Down
2 changes: 1 addition & 1 deletion src/gui/clipboardbrowserplaceholder.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ class ClipboardBrowserPlaceholder final : public QWidget

bool canExpire() const;
bool hasActiveFocus() const;
int encryptedExpireSeconds() const;
int encryptedExpireRemainingMs() const;
bool shouldPromptForLockedTabPassword() const;
void restartPasswordExpiry();
Expand All @@ -93,4 +92,5 @@ class ClipboardBrowserPlaceholder final : public QWidget
QTimer m_timerExpire;
QTimer m_timerPasswordExpire;
QByteArray m_data;
std::chrono::steady_clock::time_point m_passwordExpiredAt;
};
4 changes: 3 additions & 1 deletion src/gui/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1703,7 +1703,9 @@ ClipboardBrowserPlaceholder *MainWindow::createTab(const QString &name, TabNameM

const TabProperties tab = tabs.tabProperties(name);
placeholder->setStoreItems(tab.storeItems);
placeholder->setEncryptedExpireSeconds(tab.encryptedExpireSeconds);
placeholder->setEncryptedExpireSeconds(
tab.encryptedExpireSeconds > 0
? tab.encryptedExpireSeconds : m_sharedData->encryptedExpireSeconds);

int maxItemCount = tab.maxItemCount;
if (maxItemCount <= 0)
Expand Down
12 changes: 4 additions & 8 deletions src/gui/passwordprompt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ PasswordPrompt::PasswordPrompt(QWidget *parent)
: QObject(parent)
, m_parent(parent)
{
m_lastSuccessfulPromptElapsed.start();
m_lastSuccessfulPromptTime = std::chrono::steady_clock::now();
}

void PasswordPrompt::prompt(PasswordSource source, Callback callback)
Expand Down Expand Up @@ -47,11 +47,6 @@ Encryption::EncryptionKey PasswordPrompt::prompt(PasswordSource source)
return key;
}

qint64 PasswordPrompt::elapsedMsSinceLastSuccessfulPasswordPrompt() const
{
return m_lastSuccessfulPromptElapsed.elapsed();
}

void PasswordPrompt::runPromptQueue()
{
if (m_promptInProgress || m_pendingPrompts.isEmpty())
Expand All @@ -77,8 +72,9 @@ void PasswordPrompt::runPromptQueue()
<< (key.isValid() ? "with success" : "without success");

self->m_lastPromptKey = key;
if (key.isValid() && passwordEnteredManually)
self->m_lastSuccessfulPromptElapsed.start();
if (key.isValid() && passwordEnteredManually) {
self->m_lastSuccessfulPromptTime = std::chrono::steady_clock::now();
}

if (!self->m_pendingPrompts.isEmpty()) {
PendingPrompt pendingPrompt = std::move(self->m_pendingPrompts.first());
Expand Down
5 changes: 3 additions & 2 deletions src/gui/passwordprompt.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class PasswordPrompt final : public QObject
void prompt(PasswordSource source, Callback callback);
Encryption::EncryptionKey prompt(PasswordSource source);

qint64 elapsedMsSinceLastSuccessfulPasswordPrompt() const;
std::chrono::steady_clock::time_point lastSuccessfulPasswordPromptTime() const
{ return m_lastSuccessfulPromptTime; }

private:
struct PendingPrompt {
Expand All @@ -36,6 +37,6 @@ class PasswordPrompt final : public QObject
QWidget *m_parent = nullptr;
bool m_promptInProgress = false;
Encryption::EncryptionKey m_lastPromptKey;
QElapsedTimer m_lastSuccessfulPromptElapsed;
std::chrono::steady_clock::time_point m_lastSuccessfulPromptTime;
QList<PendingPrompt> m_pendingPrompts;
};
3 changes: 2 additions & 1 deletion src/tests/tests.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@ private slots:
void handleUnexpectedTypes();

void expireTabs();
void expireEncryptedTabsPasswordAcrossTabs();
void expireEncryptionPassword();
void expireEncryptionPasswordOnConfigChange();

void dragNDropItemOrder();
void dragNDropItemToTabTree();
Expand Down
144 changes: 144 additions & 0 deletions src/tests/tests_encryption_expire.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#include "test_utils.h"
#include "tests.h"

void Tests::expireEncryptionPassword()
{
#ifdef WITH_QCA_ENCRYPTION
const QString tab1 = testTab(1);
const QString tab2 = testTab(2);
const Args args1 = Args("tab") << tab1 << "separator" << " ";
const Args args2 = Args("tab") << tab2 << "separator" << " ";

RUN("config" << "encrypt_tabs" << "true", "true\n");

RUN(args1 << "add" << "A1", "");
RUN(args2 << "add" << "B1", "");

// Setting an expiration should not cause any expiration yet
RUN("config" << "expire_encrypted_tab_seconds" << "1", "1\n");
KEYS(clipboardBrowserId);
QTest::qWait(1500);
KEYS(clipboardBrowserId);

RUN("config" << "expire_encrypted_tab_seconds" << "2", "2\n");
KEYS(clipboardBrowserId);

TEST( m_test->stopServer() );
m_test->setEnv("COPYQ_PASSWORD", "");
TEST( m_test->startServer() );

// Start expiration timer from manual password entry.
RUN_MULTIPLE(
[&]{ RUN("show" << tab1, ""); },
[&]{ KEYS(passwordEntryCurrentId << ":TEST123" << "ENTER"); }
);

RUN_MULTIPLE(
[&]{ KEYS(clipboardBrowserId); },
[&]{ RUN("selectedTab", tab1 + "\n"); },
[&]{ RUN(args1 << "read" << "0", "A1"); },
[&]{ RUN(args2 << "read" << "0", "B1"); }
);

RUN("show" << tab2, "");
RUN("show" << tab1, "");
RUN("show" << tab2, "");

KEYS(clipboardBrowserId);
QTest::qWait(2500);
KEYS(clipboardBrowserId);

RUN_MULTIPLE(
[&]{ KEYS(passwordEntryCurrentId << ":TEST123" << "ENTER"); },
[&]{ RUN(args1 << "read" << "0", "A1"); }
);
KEYS(clipboardBrowserId);
RUN("selectedTab", tab2 + "\n");

RUN("show" << tab1, "");
RUN_MULTIPLE(
[&]{ RUN("selectedTab", tab1 + "\n"); },
[&]{ KEYS(clipboardBrowserId); },
[&]{ RUN(args1 << "read" << "0", "A1"); }
);

RUN("show" << tab2, "");
RUN(args2 << "read" << "0", "B1");

// Expire again: active tab should stay unlocked.
KEYS(clipboardBrowserId);
QTest::qWait(2500);
KEYS(clipboardBrowserId);

RUN("show" << tab2, "");
RUN(args2 << "read" << "0", "B1");

// Switching to the other expired tab should prompt again.
RUN_MULTIPLE(
[&]{ KEYS(passwordEntryCurrentId << ":TEST123" << "ENTER"); },
[&]{ RUN("show" << tab1, ""); }
);
RUN(args1 << "read" << "0", "A1");
KEYS(clipboardBrowserId);

// Avoid asking password for a new tab (if prompted recently)
const QString tab3 = testTab(3);
RUN("show" << tab3, "");

KEYS(clipboardBrowserId);
QTest::qWait(2500);
KEYS(clipboardBrowserId);

// Read multiple expired tabs items, wait for password prompt once
RUN_MULTIPLE(
[&]{ RUN(args1 << "read" << "0", "A1"); },
[&]{ RUN(args2 << "read" << "0", "B1"); },
[&]{ QTest::qWait(200); KEYS(passwordEntryCurrentId << ":TEST123" << "ENTER"); }
);

KEYS(clipboardBrowserId);
QTest::qWait(2500);
KEYS(clipboardBrowserId);

// Expired tabs should require password, even if the configuration changed
RUN("config" << "expire_encrypted_tab_seconds" << "0", "0\n");
RUN_MULTIPLE(
[&]{ KEYS(passwordEntryCurrentId << ":TEST123" << "ENTER"); },
[&]{ RUN("show" << tab1, ""); }
);
KEYS(clipboardBrowserId);
#else
SKIP("Encryption support not built-in");
#endif
}

void Tests::expireEncryptionPasswordOnConfigChange()
{
// Expired tabs should require password,
// even if the expiration was disabled afterwards
#ifdef WITH_QCA_ENCRYPTION
const QString tab1 = testTab(1);
const Args args1{"tab", tab1};
RUN(args1 << "add" << "A1", "");

RUN("config" << "encrypt_tabs" << "true", "true\n");

// Setting an expiration should not cause any expiration yet
RUN("config" << "expire_encrypted_tab_seconds" << "1", "1\n");
KEYS(clipboardBrowserId);
QTest::qWait(1500);
KEYS(clipboardBrowserId);

RUN("config" << "expire_encrypted_tab_seconds" << "0", "0\n");
RUN_MULTIPLE(
[&]{ KEYS(passwordEntryCurrentId << ":TEST123" << "ENTER"); },
[&]{ RUN("show" << tab1, ""); }
);
KEYS(clipboardBrowserId);
RUN(args1 << "read" << "0", "A1");
#else
SKIP("Encryption support not built-in");
#endif
}
Loading
Loading