fix(networkmanager): free the unlock prompt state once it completes

pull/18/head
reionwong 2 weeks ago
parent 6e71c2fa45
commit aa19d7e9e1

@ -175,8 +175,11 @@ void SecretReader::prompt(const std::string& prompt, std::function<void(bool dis
auto state = std::make_shared<State>(); auto state = std::make_shared<State>();
state->done = std::move(done); state->done = std::move(done);
std::weak_ptr<char> alive = m_alive; std::weak_ptr<char> alive = m_alive;
const auto finish = [this, id, state, alive](bool dismissed, const Paths& result) { // The match owns the state; the timer it holds must not, or neither goes.
if (alive.expired() || !state->done) std::weak_ptr<State> weakState = state;
const auto finish = [this, id, weakState, alive](bool dismissed, const Paths& result) {
const std::shared_ptr<State> state = weakState.lock();
if (!state || alive.expired() || !state->done)
return; return;
auto callback = std::move(state->done); auto callback = std::move(state->done);
state->done = nullptr; state->done = nullptr;
@ -190,7 +193,7 @@ void SecretReader::prompt(const std::string& prompt, std::function<void(bool dis
rule.path = prompt; rule.path = prompt;
rule.interface = promptInterface; rule.interface = promptInterface;
rule.member = "Completed"; rule.member = "Completed";
m_prompts.emplace(id, m_bus->subscribe(rule, [finish](const Message& message) { m_prompts.emplace(id, m_bus->subscribe(rule, [finish, state](const Message& message) {
bool dismissed = true; bool dismissed = true;
Variant result; Variant result;
message.read(dismissed, result); message.read(dismissed, result);

@ -20,3 +20,10 @@ target_link_libraries(cutefish-networkmanager-client-bus-tests PRIVATE cutefish-
# The client and the fake NetworkManager talk over a private session bus. # The client and the fake NetworkManager talk over a private session bus.
add_test(NAME cutefish-networkmanager-client-bus-tests add_test(NAME cutefish-networkmanager-client-bus-tests
COMMAND ${DBUS_RUN_SESSION} -- $<TARGET_FILE:cutefish-networkmanager-client-bus-tests>) COMMAND ${DBUS_RUN_SESSION} -- $<TARGET_FILE:cutefish-networkmanager-client-bus-tests>)
add_executable(cutefish-networkmanager-secretreader-tests secretreader_test.cpp)
set_target_properties(cutefish-networkmanager-secretreader-tests PROPERTIES AUTOMOC OFF AUTORCC OFF)
target_link_libraries(cutefish-networkmanager-secretreader-tests PRIVATE cutefish-networkmanager-client GTest::gtest)
# A fake Secret Service on a private session bus.
add_test(NAME cutefish-networkmanager-secretreader-tests
COMMAND ${DBUS_RUN_SESSION} -- $<TARGET_FILE:cutefish-networkmanager-secretreader-tests>)

@ -0,0 +1,109 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <gtest/gtest.h>
#include <chrono>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <vector>
#include "base/dbus.h"
#include "base/event_loop.h"
#include "base/timer.h"
#include "networkmanager/secretreader.h"
using namespace std::chrono_literals;
using namespace cutefish::base;
using cutefish::networkmanager::SecretReader;
using Paths = std::vector<dbus::ObjectPath>;
static EventLoop* loop = nullptr;
static bool waitFor(const std::function<bool()>& condition, std::chrono::milliseconds timeout = 3000ms)
{
if (condition())
return true;
bool met = false;
Timer poll;
poll.onTimeout = [&] {
if (condition()) {
met = true;
loop->quit();
}
};
poll.start(2ms);
Timer limit;
limit.setSingleShot(true);
limit.onTimeout = [&] { loop->quit(); };
limit.start(timeout);
loop->run();
return met || condition();
}
// A keyring whose only item is locked, and whose unlock prompt the user dismisses.
class FakeSecretService
{
public:
FakeSecretService()
: m_bus(dbus::Connection::open(dbus::Connection::Bus::Session))
, m_service(m_bus.get(), "/org/freedesktop/secrets", "org.freedesktop.Secret.Service")
, m_prompt(m_bus.get(), "/prompt/1", "org.freedesktop.Secret.Prompt")
{
m_service.addMethod("OpenSession", [](const std::string&, const dbus::Variant&) {
return std::tuple<dbus::Variant, dbus::ObjectPath>{dbus::Variant(std::string()),
dbus::ObjectPath{"/session/1"}};
});
m_service.addMethod("SearchItems", [](const std::map<std::string, std::string>&) {
return std::tuple<Paths, Paths>{Paths(), Paths{dbus::ObjectPath{"/item/1"}}};
});
m_service.addMethod("Unlock", [](const Paths&) {
return std::tuple<Paths, dbus::ObjectPath>{Paths(), dbus::ObjectPath{"/prompt/1"}};
});
m_service.publish();
m_prompt.addMethod("Prompt", [this](const std::string&) {
++prompts;
m_bus->emitSignal("/prompt/1", "org.freedesktop.Secret.Prompt", "Completed", true, dbus::Variant(Paths()));
});
m_prompt.addSignal("Completed", "bv");
m_prompt.publish();
}
bool serve() { return m_bus->requestName("org.freedesktop.secrets"); }
int prompts = 0;
private:
std::unique_ptr<dbus::Connection> m_bus;
dbus::Object m_service;
dbus::Object m_prompt;
};
TEST(SecretReaderTest, DismissedPromptAnswersWithNothing)
{
FakeSecretService service;
ASSERT_TRUE(service.serve());
std::optional<std::map<std::string, std::string>> loaded;
{
SecretReader reader;
reader.load("uuid", "vpn", [&](const std::map<std::string, std::string>& secrets) { loaded = secrets; });
ASSERT_TRUE(waitFor([&] { return loaded.has_value(); }));
}
EXPECT_EQ(service.prompts, 1);
EXPECT_TRUE(loaded->empty());
}
int main(int argc, char** argv)
{
testing::InitGoogleTest(&argc, argv);
EventLoop eventLoop;
loop = &eventLoop;
return RUN_ALL_TESTS();
}
Loading…
Cancel
Save