From 68b671ce04b8b5d6b37025ad093c73a3e14d4d64 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 8 Mar 2016 22:04:56 -0600 Subject: add fingerprint snap decisions; test with notification dbusmock --- src/usb-snap.cpp | 254 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) create mode 100644 src/usb-snap.cpp (limited to 'src/usb-snap.cpp') diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp new file mode 100644 index 0000000..40f02a2 --- /dev/null +++ b/src/usb-snap.cpp @@ -0,0 +1,254 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include + +/*** +**** +***/ + +class UsbSnap::Impl +{ +public: + + explicit Impl(const std::string& fingerprint): + m_fingerprint{fingerprint}, + m_cancellable{g_cancellable_new()} + { + g_bus_get (G_BUS_TYPE_SESSION, m_cancellable, on_bus_ready_static, this); + } + + ~Impl() + { + g_cancellable_cancel(m_cancellable); + g_clear_object(&m_cancellable); + + if (m_subscription_id != 0) + g_dbus_connection_signal_unsubscribe (m_bus, m_subscription_id); + + if (m_notification_id != 0) { + GError* error {}; + g_dbus_connection_call_sync(m_bus, + BUS_NAME, + OBJECT_PATH, + IFACE_NAME, + "CloseNotification", + g_variant_new("(u)", m_notification_id), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &error); + if (error != nullptr) { + g_warning("Error closing notification: %s", error->message); + g_clear_error(&error); + } + } + + g_clear_object(&m_bus); + } + + core::Signal& on_user_response() + { + return m_on_user_response; + } + +private: + + static void on_bus_ready_static(GObject* /*source*/, GAsyncResult* res, gpointer gself) + { + GError* error {}; + auto bus = g_bus_get_finish (res, &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("UsbSnap: Error getting session bus: %s", error->message); + g_clear_error(&error); + } else { + static_cast(gself)->on_bus_ready(bus); + } + g_clear_object(&bus); + } + + void on_bus_ready(GDBusConnection* bus) + { + m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(bus))); + + auto body = g_strdup_printf(_("The computer's RSA key fingerprint is: %s"), m_fingerprint.c_str()); + + GVariantBuilder actions_builder; + g_variant_builder_init(&actions_builder, G_VARIANT_TYPE_STRING_ARRAY); + g_variant_builder_add(&actions_builder, "s", ACTION_ALLOW); + g_variant_builder_add(&actions_builder, "s", _("Allow")); + g_variant_builder_add(&actions_builder, "s", ACTION_DENY); + g_variant_builder_add(&actions_builder, "s", _("Deny")); + + GVariantBuilder hints_builder; + g_variant_builder_init(&hints_builder, G_VARIANT_TYPE_VARDICT); + g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-non-shaped-icon", g_variant_new_string("true")); + g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-snap-decisions", g_variant_new_string("true")); + g_variant_builder_add(&hints_builder, "{sv}", "x-canonical-private-affirmative-tint", g_variant_new_string("true")); + + auto args = g_variant_new("(susssasa{sv}i)", + "", + uint32_t(0), + "computer-symbolic", + _("Allow USB Debugging?"), + body, + &actions_builder, + &hints_builder, + -1); + g_dbus_connection_call(m_bus, + BUS_NAME, + OBJECT_PATH, + IFACE_NAME, + "Notify", + args, + G_VARIANT_TYPE("(u)"), + G_DBUS_CALL_FLAGS_NONE, + -1, // timeout + m_cancellable, + on_notify_reply_static, + this); + + g_clear_pointer(&body, g_free); + } + + static void on_notify_reply_static(GObject* obus, GAsyncResult* res, gpointer gself) + { + GError* error {}; + auto reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION(obus), res, &error); + if (error != nullptr) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("UsbSnap: Error calling Notify: %s", error->message); + g_clear_error(&error); + } else { + uint32_t id {}; + g_variant_get(reply, "(u)", &id); + static_cast(gself)->on_notify_reply(id); + } + g_clear_pointer(&reply, g_variant_unref); + } + + void on_notify_reply(uint32_t id) + { + m_notification_id = id; + + m_subscription_id = g_dbus_connection_signal_subscribe(m_bus, + BUS_NAME, + IFACE_NAME, + nullptr, + OBJECT_PATH, + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + on_notification_signal_static, + this, + nullptr); + } + + static void on_notification_signal_static(GDBusConnection* /*connection*/, + const gchar* /*sender_name*/, + const gchar* object_path, + const gchar* interface_name, + const gchar* signal_name, + GVariant* parameters, + gpointer gself) + { + g_return_if_fail(!g_strcmp0(object_path, OBJECT_PATH)); + g_return_if_fail(!g_strcmp0(interface_name, IFACE_NAME)); + + auto self = static_cast(gself); + + if (!g_strcmp0(signal_name, "ActionInvoked")) + { + uint32_t id {}; + const char* action_name {}; + g_variant_get(parameters, "(u&s)", &id, &action_name); + if (id == self->m_notification_id) + self->on_action_invoked(action_name); + } + else if (!g_strcmp0(signal_name, "NotificationClosed")) + { + uint32_t id {}; + uint32_t close_reason {}; + g_variant_get(parameters, "(uu)", &id, &close_reason); + if (id == self->m_notification_id) + self->on_notification_closed(close_reason); + } + } + + void on_action_invoked(const char* action_name) + { + const auto response = !g_strcmp0(action_name, ACTION_ALLOW) + ? AdbdClient::PKResponse::ALLOW + : AdbdClient::PKResponse::DENY; + + // FIXME: the current default is to cover the most common use case. + // We need to get the notification ui's checkbox working ASAP so + // that the user can provide this flag + const bool remember_this_choice = response == AdbdClient::PKResponse::ALLOW; + + m_on_user_response(response, remember_this_choice); + } + + void on_notification_closed(uint32_t close_reason) + { + if (close_reason == CloseReason::EXPIRED) + m_on_user_response(AdbdClient::PKResponse::DENY, false); + + m_notification_id = 0; + } + + static constexpr char const * ACTION_ALLOW{"allow"}; + static constexpr char const * ACTION_DENY{"deny"}; + + static constexpr char const * BUS_NAME {"org.freedesktop.Notifications" }; + static constexpr char const * IFACE_NAME {"org.freedesktop.Notifications" }; + static constexpr char const * OBJECT_PATH {"/org/freedesktop/Notifications" }; + enum CloseReason { EXPIRED=1, DISMISSED=2, API=3, UNDEFINED=4 }; + + const std::string m_fingerprint; + core::Signal m_on_user_response; + GCancellable* m_cancellable {}; + GDBusConnection* m_bus {}; + uint32_t m_notification_id {}; + unsigned int m_subscription_id {}; +}; + +/*** +**** +***/ + +UsbSnap::UsbSnap(const std::string& public_key): + impl{new Impl{public_key}} +{ +} + +UsbSnap::~UsbSnap() +{ +} + +core::Signal& +UsbSnap::on_user_response() +{ + return impl->on_user_response(); +} + -- cgit v1.2.3 From 13a0b901492901638a7abc90bb2935a9c0387f75 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 9 Mar 2016 17:19:23 -0600 Subject: add human-readable fingerprint extraction from the adb public keys --- src/adbd-client.cpp | 39 +++++++++++++++++++++++++++++++++++++++ src/adbd-client.h | 1 + src/main.cpp | 2 +- src/usb-snap.cpp | 4 ++-- 4 files changed, 43 insertions(+), 3 deletions(-) (limited to 'src/usb-snap.cpp') diff --git a/src/adbd-client.cpp b/src/adbd-client.cpp index 38f202f..edd403c 100644 --- a/src/adbd-client.cpp +++ b/src/adbd-client.cpp @@ -22,6 +22,9 @@ #include #include +#include +#include +#include #include #include #include @@ -89,6 +92,7 @@ private: auto self = data->self; struct PKRequest req; req.public_key = data->public_key; + req.fingerprint = get_fingerprint(req.public_key); req.respond = [self](PKResponse response){self->on_public_key_response(response);}; self->m_on_pk_request(req); } @@ -219,6 +223,37 @@ private: } } + static std::string get_fingerprint(const std::string& public_key) + { + // The first token is base64-encoded data, so cut on the first whitespace + const std::string base64 ( + public_key.begin(), + std::find_if( + public_key.begin(), public_key.end(), + [](const std::string::value_type& ch){return std::isspace(ch);} + ) + ); + + gsize digest_len {}; + auto digest = g_base64_decode(base64.c_str(), &digest_len); + + auto checksum = g_compute_checksum_for_data(G_CHECKSUM_MD5, digest, digest_len); + const gsize checksum_len = checksum ? strlen(checksum) : 0; + + // insert ':' between character pairs; eg "ff27b5f3" --> "ff:27:b5:f3" + std::string fingerprint; + for (gsize i=0; i respond; }; diff --git a/src/main.cpp b/src/main.cpp index 62eca62..151b642 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,7 +61,7 @@ main(int /*argc*/, char** /*argv*/) static constexpr char const * ADB_SOCKET_PATH {"/dev/socket/adb"}; GAdbdClient adbd_client{ADB_SOCKET_PATH}; adbd_client.on_pk_request().connect([](const AdbdClient::PKRequest& req){ - auto snap = new UsbSnap(req.public_key); + auto snap = new UsbSnap(req.fingerprint); snap->on_user_response().connect([req,snap](AdbdClient::PKResponse response, bool /*FIXME: remember_choice*/){ req.respond(response); g_idle_add([](gpointer gsnap){delete static_cast(gsnap); return G_SOURCE_REMOVE;}, snap); // delete-later diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp index 40f02a2..87f4673 100644 --- a/src/usb-snap.cpp +++ b/src/usb-snap.cpp @@ -217,8 +217,8 @@ private: m_notification_id = 0; } - static constexpr char const * ACTION_ALLOW{"allow"}; - static constexpr char const * ACTION_DENY{"deny"}; + static constexpr char const * ACTION_ALLOW {"allow"}; + static constexpr char const * ACTION_DENY {"deny"}; static constexpr char const * BUS_NAME {"org.freedesktop.Notifications" }; static constexpr char const * IFACE_NAME {"org.freedesktop.Notifications" }; -- cgit v1.2.3 From ee369babc9185bac7c7910a68a1e58bab7efa64c Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 15 Mar 2016 16:12:05 -0500 Subject: oops, last commit's diagnosis was incorrect. The timing test issue came from async dbus handling interfering with fast setup/teardown of automated tests. Revert the last change and fix by setting up the dbus signal subscription immediately upon getting the dbus connection. --- src/usb-snap.cpp | 22 +++++++++++----------- tests/unit/usb-snap-test.cpp | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'src/usb-snap.cpp') diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp index 87f4673..c42f9f0 100644 --- a/src/usb-snap.cpp +++ b/src/usb-snap.cpp @@ -92,6 +92,17 @@ private: { m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(bus))); + m_subscription_id = g_dbus_connection_signal_subscribe(m_bus, + BUS_NAME, + IFACE_NAME, + nullptr, + OBJECT_PATH, + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + on_notification_signal_static, + this, + nullptr); + auto body = g_strdup_printf(_("The computer's RSA key fingerprint is: %s"), m_fingerprint.c_str()); GVariantBuilder actions_builder; @@ -151,17 +162,6 @@ private: void on_notify_reply(uint32_t id) { m_notification_id = id; - - m_subscription_id = g_dbus_connection_signal_subscribe(m_bus, - BUS_NAME, - IFACE_NAME, - nullptr, - OBJECT_PATH, - nullptr, - G_DBUS_SIGNAL_FLAGS_NONE, - on_notification_signal_static, - this, - nullptr); } static void on_notification_signal_static(GDBusConnection* /*connection*/, diff --git a/tests/unit/usb-snap-test.cpp b/tests/unit/usb-snap-test.cpp index 4300c47..84555cc 100644 --- a/tests/unit/usb-snap-test.cpp +++ b/tests/unit/usb-snap-test.cpp @@ -163,7 +163,7 @@ TEST_F(UsbSnapFixture, TestRoundTrip) QVariantList() << id << test.action_to_invoke); // test that UsbSnap emits on_user_response() as a result - wait_for([&user_response_set](){return user_response_set;}, 2000); + wait_for([&user_response_set](){return user_response_set;}); EXPECT_TRUE(user_response_set); ASSERT_EQ(test.expected_response, user_response); -- cgit v1.2.3 From c63d90da0f1d9cbd1eee5dd66a9828c51cc8dcc9 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Thu, 17 Mar 2016 09:59:32 -0500 Subject: de-dupe use of dbus names --- src/dbus-names.h | 42 ++++++++++++++++++++++++++++++++++ src/usb-snap.cpp | 34 ++++++++++++--------------- tests/integration/usb-manager-test.cpp | 13 ++++++----- tests/unit/usb-snap-test.cpp | 13 ++++++----- tests/utils/dbus-types.h | 5 ---- 5 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 src/dbus-names.h (limited to 'src/usb-snap.cpp') diff --git a/src/dbus-names.h b/src/dbus-names.h new file mode 100644 index 0000000..753b8c8 --- /dev/null +++ b/src/dbus-names.h @@ -0,0 +1,42 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#pragma once + +namespace DBusNames +{ + namespace Notify + { + static constexpr char const * NAME = "org.freedesktop.Notifications"; + static constexpr char const * PATH = "/org/freedesktop/Notifications"; + static constexpr char const * INTERFACE = "org.freedesktop.Notifications"; + + namespace ActionInvoked + { + static constexpr char const * NAME = "ActionInvoked"; + } + + namespace NotificationClosed + { + static constexpr char const * NAME = "NotificationClosed"; + enum Reason { EXPIRED=1, DISMISSED=2, API=3, UNDEFINED=4 }; + } + } +} + diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp index c42f9f0..41c78c6 100644 --- a/src/usb-snap.cpp +++ b/src/usb-snap.cpp @@ -17,6 +17,7 @@ * Charles Kerr */ +#include #include #include @@ -48,9 +49,9 @@ public: if (m_notification_id != 0) { GError* error {}; g_dbus_connection_call_sync(m_bus, - BUS_NAME, - OBJECT_PATH, - IFACE_NAME, + DBusNames::Notify::NAME, + DBusNames::Notify::PATH, + DBusNames::Notify::INTERFACE, "CloseNotification", g_variant_new("(u)", m_notification_id), nullptr, @@ -93,10 +94,10 @@ private: m_bus = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(bus))); m_subscription_id = g_dbus_connection_signal_subscribe(m_bus, - BUS_NAME, - IFACE_NAME, + DBusNames::Notify::NAME, + DBusNames::Notify::INTERFACE, nullptr, - OBJECT_PATH, + DBusNames::Notify::PATH, nullptr, G_DBUS_SIGNAL_FLAGS_NONE, on_notification_signal_static, @@ -128,9 +129,9 @@ private: &hints_builder, -1); g_dbus_connection_call(m_bus, - BUS_NAME, - OBJECT_PATH, - IFACE_NAME, + DBusNames::Notify::NAME, + DBusNames::Notify::PATH, + DBusNames::Notify::INTERFACE, "Notify", args, G_VARIANT_TYPE("(u)"), @@ -172,12 +173,12 @@ private: GVariant* parameters, gpointer gself) { - g_return_if_fail(!g_strcmp0(object_path, OBJECT_PATH)); - g_return_if_fail(!g_strcmp0(interface_name, IFACE_NAME)); + g_return_if_fail(!g_strcmp0(object_path, DBusNames::Notify::PATH)); + g_return_if_fail(!g_strcmp0(interface_name, DBusNames::Notify::INTERFACE)); auto self = static_cast(gself); - if (!g_strcmp0(signal_name, "ActionInvoked")) + if (!g_strcmp0(signal_name, DBusNames::Notify::ActionInvoked::NAME)) { uint32_t id {}; const char* action_name {}; @@ -185,7 +186,7 @@ private: if (id == self->m_notification_id) self->on_action_invoked(action_name); } - else if (!g_strcmp0(signal_name, "NotificationClosed")) + else if (!g_strcmp0(signal_name, DBusNames::Notify::NotificationClosed::NAME)) { uint32_t id {}; uint32_t close_reason {}; @@ -211,7 +212,7 @@ private: void on_notification_closed(uint32_t close_reason) { - if (close_reason == CloseReason::EXPIRED) + if (close_reason == DBusNames::Notify::NotificationClosed::Reason::EXPIRED) m_on_user_response(AdbdClient::PKResponse::DENY, false); m_notification_id = 0; @@ -220,11 +221,6 @@ private: static constexpr char const * ACTION_ALLOW {"allow"}; static constexpr char const * ACTION_DENY {"deny"}; - static constexpr char const * BUS_NAME {"org.freedesktop.Notifications" }; - static constexpr char const * IFACE_NAME {"org.freedesktop.Notifications" }; - static constexpr char const * OBJECT_PATH {"/org/freedesktop/Notifications" }; - enum CloseReason { EXPIRED=1, DISMISSED=2, API=3, UNDEFINED=4 }; - const std::string m_fingerprint; core::Signal m_on_user_response; GCancellable* m_cancellable {}; diff --git a/tests/integration/usb-manager-test.cpp b/tests/integration/usb-manager-test.cpp index 5e3377d..82c170e 100644 --- a/tests/integration/usb-manager-test.cpp +++ b/tests/integration/usb-manager-test.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -93,10 +94,10 @@ protected: OrgFreedesktopDBusMockInterface& notificationsMockInterface() { - return dbusMock.mockInterface("org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications", - QDBusConnection::SessionBus); + return dbusMock.mockInterface(DBusNames::Notify::NAME, + DBusNames::Notify::PATH, + DBusNames::Notify::INTERFACE, + QDBusConnection::SessionBus); } QtDBusTest::DBusTestRunner dbusTestRunner; @@ -151,8 +152,8 @@ TEST_F(UsbManagerFixture, Allow) // click on allow in the notification notificationsMockInterface().EmitSignal( - DBusTypes::NOTIFY_DBUS_INTERFACE, - "ActionInvoked", + DBusNames::Notify::INTERFACE, + DBusNames::Notify::ActionInvoked::NAME, "us", QVariantList() << uint32_t(1) << "allow" ); diff --git a/tests/unit/usb-snap-test.cpp b/tests/unit/usb-snap-test.cpp index e8f8bb2..dc17696 100644 --- a/tests/unit/usb-snap-test.cpp +++ b/tests/unit/usb-snap-test.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -75,10 +76,10 @@ protected: OrgFreedesktopDBusMockInterface& notificationsMockInterface() { - return dbusMock.mockInterface("org.freedesktop.Notifications", - "/org/freedesktop/Notifications", - "org.freedesktop.Notifications", - QDBusConnection::SessionBus); + return dbusMock.mockInterface(DBusNames::Notify::NAME, + DBusNames::Notify::PATH, + DBusNames::Notify::INTERFACE, + QDBusConnection::SessionBus); } QtDBusTest::DBusTestRunner dbusTestRunner; @@ -144,8 +145,8 @@ TEST_F(UsbSnapFixture, TestRoundTrip) // fake a user interaction with the fdo notification notificationsMockInterface().EmitSignal( - DBusTypes::NOTIFY_DBUS_INTERFACE, - "ActionInvoked", + DBusNames::Notify::INTERFACE, + DBusNames::Notify::ActionInvoked::NAME, "us", QVariantList() << id << test.action_to_invoke); diff --git a/tests/utils/dbus-types.h b/tests/utils/dbus-types.h index c2dfb81..3b3a02d 100644 --- a/tests/utils/dbus-types.h +++ b/tests/utils/dbus-types.h @@ -39,9 +39,4 @@ namespace DBusTypes qDBusRegisterMetaType(); qDBusRegisterMetaType(); } - static constexpr char const* NOTIFY_DBUS_NAME = "org.freedesktop.Notifications"; - - static constexpr char const* NOTIFY_DBUS_INTERFACE = "org.freedesktop.Notifications"; - - static constexpr char const* NOTIFY_DBUS_PATH = "/org/freedesktop/Notifications"; } -- cgit v1.2.3 From 7a25132c125f6e5e413ad26ea950ae22bee982f5 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 21 Mar 2016 13:40:11 -0500 Subject: if our USB device is disconnected while prompting the user for ADBD, cancel the prompt. --- CMakeLists.txt | 1 + debian/control | 1 + src/CMakeLists.txt | 1 + src/adbd-client.cpp | 1 + src/main.cpp | 4 +- src/usb-manager.cpp | 63 +++++++++++++++++--------- src/usb-manager.h | 11 ++++- src/usb-monitor.cpp | 81 ++++++++++++++++++++++++++++++++++ src/usb-monitor.h | 52 ++++++++++++++++++++++ src/usb-snap.cpp | 1 + tests/integration/usb-manager-test.cpp | 44 ++++++++++++++++-- tests/unit/usb-snap-test.cpp | 1 - tests/utils/mock-usb-monitor.h | 32 ++++++++++++++ tests/utils/qdbus-helpers.h | 21 --------- tests/utils/qt-fixture.h | 18 +++++++- 15 files changed, 284 insertions(+), 48 deletions(-) create mode 100644 src/usb-monitor.cpp create mode 100644 src/usb-monitor.h create mode 100644 tests/utils/mock-usb-monitor.h delete mode 100644 tests/utils/qdbus-helpers.h (limited to 'src/usb-snap.cpp') diff --git a/CMakeLists.txt b/CMakeLists.txt index bb7568e..8a1a6aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ set(GLIB_MINIMUM 2.36) pkg_check_modules(SERVICE_DEPS REQUIRED gio-unix-2.0>=${GLIB_MINIMUM} glib-2.0>=${GLIB_MINIMUM} + gudev-1.0 ) include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS} diff --git a/debian/control b/debian/control index 529fa37..90e2590 100644 --- a/debian/control +++ b/debian/control @@ -7,6 +7,7 @@ Build-Depends: cmake, cmake-extras (>= 0.4), dbus, libglib2.0-dev (>= 2.36), + libgudev-1.0-dev, libproperties-cpp-dev, # for coverage reports lcov, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d3a021b..cdd2384 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ add_library( indicator.cpp rotation-lock.cpp usb-manager.cpp + usb-monitor.cpp usb-snap.cpp ) diff --git a/src/adbd-client.cpp b/src/adbd-client.cpp index 4f7d28f..937215e 100644 --- a/src/adbd-client.cpp +++ b/src/adbd-client.cpp @@ -45,6 +45,7 @@ public: { // tell the worker thread to stop whatever it's doing and exit. g_cancellable_cancel(m_cancellable); + m_pkresponse_cv.notify_one(); m_sleep_cv.notify_one(); m_worker_thread.join(); g_clear_object(&m_cancellable); diff --git a/src/main.cpp b/src/main.cpp index 7d6eb5f..27e6bcc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include // bindtextdomain() #include @@ -59,7 +60,8 @@ main(int /*argc*/, char** /*argv*/) // even though it doesn't have an indicator component yet static constexpr char const * ADB_SOCKET_PATH {"/dev/socket/adbd"}; static constexpr char const * PUBLIC_KEYS_FILENAME {"/data/misc/adb/adb_keys"}; - UsbManager usb_manager {ADB_SOCKET_PATH, PUBLIC_KEYS_FILENAME}; + auto usb_monitor = std::make_shared(); + UsbManager usb_manager {ADB_SOCKET_PATH, PUBLIC_KEYS_FILENAME, usb_monitor}; // let's go! g_main_loop_run(loop); diff --git a/src/usb-manager.cpp b/src/usb-manager.cpp index 7f43520..840a04b 100644 --- a/src/usb-manager.cpp +++ b/src/usb-manager.cpp @@ -28,39 +28,57 @@ #include #include +#include + class UsbManager::Impl { public: explicit Impl( const std::string& socket_path, - const std::string& public_keys_filename + const std::string& public_keys_filename, + const std::shared_ptr& usb_monitor ): m_adbd_client{std::make_shared(socket_path)}, - m_public_keys_filename{public_keys_filename} + m_public_keys_filename{public_keys_filename}, + m_usb_monitor{usb_monitor} { - m_adbd_client->on_pk_request().connect([this](const AdbdClient::PKRequest& req){ - auto snap = new UsbSnap(req.fingerprint); - snap->on_user_response().connect([this,req,snap](AdbdClient::PKResponse response, bool remember_choice){ - g_debug("%s user responded! response %d, remember %d", G_STRLOC, int(response), int(remember_choice)); - req.respond(response); - if (remember_choice && (response == AdbdClient::PKResponse::ALLOW)) - write_public_key(req.public_key); - // delete_later - g_idle_add([](gpointer gsnap){delete static_cast(gsnap); return G_SOURCE_REMOVE;}, snap); - }); + m_usb_monitor->on_usb_disconnected().connect([this](const std::string& /*usb_name*/) { + m_snap.reset(); }); - } - ~Impl() - { + m_adbd_client->on_pk_request().connect( + [this](const AdbdClient::PKRequest& req){ + + m_snap.reset(new UsbSnap(req.fingerprint), + [this](UsbSnap* snap){ + m_snap_connections.clear(); + delete snap; + } + ); + + m_snap_connections.insert((*m_snap).on_user_response().connect( + [this,req](AdbdClient::PKResponse response, bool remember_choice){ + g_message("%s user responded! response %d, remember %d", G_STRLOC, int(response), int(remember_choice)); + req.respond(response); + g_message("%s", G_STRLOC); + if (remember_choice && (response == AdbdClient::PKResponse::ALLOW)) + write_public_key(req.public_key); + g_idle_add([](gpointer gself){static_cast(gself)->m_snap.reset(); return G_SOURCE_REMOVE;}, this); + } + )); + } + ); + } + ~Impl() =default; + private: void write_public_key(const std::string& public_key) { - g_debug("writing public key '%s' to '%s'", public_key.c_str(), m_public_keys_filename.c_str()); + g_message("%s writing public key '%s' to '%s'", G_STRLOC, public_key.c_str(), m_public_keys_filename.c_str()); // confirm the directory exists auto dirname = g_path_get_dirname(m_public_keys_filename.c_str()); @@ -78,12 +96,12 @@ private: S_IRUSR|S_IWUSR|S_IRGRP ); if (fd == -1) { - g_warning("Error opening ADB datafile '%s': %s", m_public_keys_filename.c_str(), g_strerror(errno)); + g_warning("Error opening ADB datafile: %s", g_strerror(errno)); return; } // write the new public key on its own line - const std::string buf {public_key + '\n'}; + std::string buf {public_key + '\n'}; if (write(fd, buf.c_str(), buf.size()) == -1) g_warning("Error writing ADB datafile: %d %s", errno, g_strerror(errno)); close(fd); @@ -91,6 +109,10 @@ private: std::shared_ptr m_adbd_client; const std::string m_public_keys_filename; + std::shared_ptr m_usb_monitor; + + std::shared_ptr m_snap; + std::set m_snap_connections; }; /*** @@ -99,9 +121,10 @@ private: UsbManager::UsbManager( const std::string& socket_path, - const std::string& public_keys_filename + const std::string& public_keys_filename, + const std::shared_ptr& usb_monitor ): - impl{new Impl{socket_path, public_keys_filename}} + impl{new Impl{socket_path, public_keys_filename, usb_monitor}} { } diff --git a/src/usb-manager.h b/src/usb-manager.h index ec405c0..960d634 100644 --- a/src/usb-manager.h +++ b/src/usb-manager.h @@ -19,6 +19,8 @@ #pragma once +#include + #include #include @@ -28,10 +30,17 @@ class UsbManager { public: - UsbManager(const std::string& socket_path, const std::string& public_key_filename); + + UsbManager( + const std::string& socket_path, + const std::string& public_key_filename, + const std::shared_ptr& + ); + ~UsbManager(); protected: + class Impl; std::unique_ptr impl; }; diff --git a/src/usb-monitor.cpp b/src/usb-monitor.cpp new file mode 100644 index 0000000..5fc5a6d --- /dev/null +++ b/src/usb-monitor.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include + +class GUDevUsbMonitor::Impl +{ +public: + + Impl() + { + const char* subsystems[] = {"android_usb", nullptr}; + m_udev_client = g_udev_client_new(subsystems); + g_signal_connect(m_udev_client, "uevent", G_CALLBACK(on_android_usb_event), this); + } + + ~Impl() + { + g_signal_handlers_disconnect_by_data(m_udev_client, this); + g_clear_object(&m_udev_client); + } + + core::Signal& on_usb_disconnected() + { + return m_on_usb_disconnected; + } + +private: + + static void on_android_usb_event(GUdevClient*, gchar* action, GUdevDevice* device, gpointer gself) + { + if (!g_strcmp0(action, "change")) + if (!g_strcmp0(g_udev_device_get_property(device, "USB_STATE"), "DISCONNECTED")) + static_cast(gself)->m_on_usb_disconnected(g_udev_device_get_name(device)); + } + + core::Signal m_on_usb_disconnected; + + GUdevClient* m_udev_client = nullptr; +}; + +/*** +**** +***/ + +UsbMonitor::UsbMonitor() =default; + +UsbMonitor::~UsbMonitor() =default; + +GUDevUsbMonitor::GUDevUsbMonitor(): + impl{new Impl{}} +{ +} + +GUDevUsbMonitor::~GUDevUsbMonitor() =default; + +core::Signal& +GUDevUsbMonitor::on_usb_disconnected() +{ + return impl->on_usb_disconnected(); +} + diff --git a/src/usb-monitor.h b/src/usb-monitor.h new file mode 100644 index 0000000..d9be539 --- /dev/null +++ b/src/usb-monitor.h @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#pragma once + +#include + +#include +#include + +/** + * Simple interface that emits signals on USB device state changes + */ +class UsbMonitor +{ +public: + UsbMonitor(); + virtual ~UsbMonitor(); + virtual core::Signal& on_usb_disconnected() =0; +}; + +/** + * Simple GUDev wrapper that notifies on android_usb device state changes + */ +class GUDevUsbMonitor: public UsbMonitor +{ +public: + GUDevUsbMonitor(); + virtual ~GUDevUsbMonitor(); + core::Signal& on_usb_disconnected() override; + +protected: + class Impl; + std::unique_ptr impl; +}; + diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp index 41c78c6..349d80e 100644 --- a/src/usb-snap.cpp +++ b/src/usb-snap.cpp @@ -148,6 +148,7 @@ private: { GError* error {}; auto reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION(obus), res, &error); +g_message("%s got notify response %s", G_STRLOC, g_variant_print(reply, true)); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning("UsbSnap: Error calling Notify: %s", error->message); diff --git a/tests/integration/usb-manager-test.cpp b/tests/integration/usb-manager-test.cpp index 21fdc97..19c0401 100644 --- a/tests/integration/usb-manager-test.cpp +++ b/tests/integration/usb-manager-test.cpp @@ -17,10 +17,9 @@ * Charles Kerr */ -#define QT_NO_KEYWORDS - #include #include +#include #include #include @@ -64,6 +63,8 @@ protected: { super::SetUp(); + m_usb_monitor.reset(new MockUsbMonitor{}); + char tmpl[] = {"usb-manager-test-XXXXXX"}; m_tmpdir.reset(new std::string{g_mkdtemp(tmpl)}, file_deleter); g_message("using tmpdir '%s'", m_tmpdir->c_str()); @@ -83,6 +84,7 @@ protected: QtDBusTest::DBusTestRunner dbusTestRunner; QtDBusMock::DBusMock dbusMock; std::shared_ptr m_tmpdir; + std::shared_ptr m_usb_monitor; }; TEST_F(UsbManagerFixture, Allow) @@ -102,7 +104,7 @@ TEST_F(UsbManagerFixture, Allow) auto adbd_server = std::make_shared(*socket_path, std::vector{"PK"+public_key}); // set up a UsbManager to process the request - auto usb_manager = std::make_shared(*socket_path, *public_keys_path); + auto usb_manager = std::make_shared(*socket_path, *public_keys_path, m_usb_monitor); // wait for the notification to show up, confirm it looks right wait_for_signals(notificationsSpy, 1); @@ -151,3 +153,39 @@ TEST_F(UsbManagerFixture, Allow) ASSERT_EQ(1, lines.size()); EXPECT_EQ(public_key, lines[0]); } + +TEST_F(UsbManagerFixture, Cancel) +{ + const std::shared_ptr socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter}; + const std::shared_ptr public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter}; + + // add a signal spy to listen to the notification daemon + QSignalSpy notificationsSpy( + ¬ificationsMockInterface(), + SIGNAL(MethodCalled(const QString &, const QVariantList &)) + ); + + // start a mock AdbdServer ready to submit a request + const std::string public_key {"public_key"}; + auto adbd_server = std::make_shared(*socket_path, std::vector{"PK"+public_key}); + + // set up a UsbManager to process the request + auto usb_manager = std::make_shared(*socket_path, *public_keys_path, m_usb_monitor); + + // wait for a notification to show up + wait_for_signals(notificationsSpy, 1); + EXPECT_EQ("Notify", notificationsSpy.at(0).at(0)); + notificationsSpy.clear(); + + // wait for UsbSnap to receive dbusmock's response to the Notify request. + // there's no event to key off of for this, so just wait for a moment + wait_msec(); + + // disconnect the USB before the user has a chance to allow/deny + m_usb_monitor->m_on_usb_disconnected("android0"); + + // confirm that we requested the notification to be pulled down + wait_for_signals(notificationsSpy, 1); + EXPECT_EQ("CloseNotification", notificationsSpy.at(0).at(0)); + notificationsSpy.clear(); +} diff --git a/tests/unit/usb-snap-test.cpp b/tests/unit/usb-snap-test.cpp index 663f9e6..40de94a 100644 --- a/tests/unit/usb-snap-test.cpp +++ b/tests/unit/usb-snap-test.cpp @@ -17,7 +17,6 @@ * Charles Kerr */ -#define QT_NO_KEYWORDS #include #include diff --git a/tests/utils/mock-usb-monitor.h b/tests/utils/mock-usb-monitor.h new file mode 100644 index 0000000..92b89db --- /dev/null +++ b/tests/utils/mock-usb-monitor.h @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#pragma once + +#include + +class MockUsbMonitor: public UsbMonitor +{ +public: + MockUsbMonitor() =default; + virtual ~MockUsbMonitor() =default; + core::Signal& on_usb_disconnected() override {return m_on_usb_disconnected;} + core::Signal m_on_usb_disconnected; +}; + diff --git a/tests/utils/qdbus-helpers.h b/tests/utils/qdbus-helpers.h deleted file mode 100644 index f873e23..0000000 --- a/tests/utils/qdbus-helpers.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#define QT_NO_KEYWORDS -#include -#include - -bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map) -{ - if (variant.canConvert()) - { - QDBusArgument value(variant.value()); - if (value.currentType() == QDBusArgument::MapType) - { - value >> map; - return true; - } - } - - return false; -} - diff --git a/tests/utils/qt-fixture.h b/tests/utils/qt-fixture.h index 321d56e..0f5722b 100644 --- a/tests/utils/qt-fixture.h +++ b/tests/utils/qt-fixture.h @@ -22,12 +22,13 @@ #define QT_NO_KEYWORDS #include -#include #include #include #include +#include +#include #include class QtFixture: public GlibFixture @@ -54,5 +55,20 @@ protected: ASSERT_EQ(signalsExpected, signalSpy.size()); } + + bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map) + { + if (variant.canConvert()) + { + QDBusArgument value(variant.value()); + if (value.currentType() == QDBusArgument::MapType) + { + value >> map; + return true; + } + } + + return false; + } }; -- cgit v1.2.3 From 1c4f005f0765f460b28808a624fbec7737324b1a Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 21 Mar 2016 17:05:01 -0500 Subject: in UsbManager, reset AdbdClient on usb disconnect --- src/usb-manager.cpp | 44 ++++++++++++++++++++++++++------------------ src/usb-snap.cpp | 1 - 2 files changed, 26 insertions(+), 19 deletions(-) (limited to 'src/usb-snap.cpp') diff --git a/src/usb-manager.cpp b/src/usb-manager.cpp index 840a04b..f5957d9 100644 --- a/src/usb-manager.cpp +++ b/src/usb-manager.cpp @@ -39,29 +39,40 @@ public: const std::string& public_keys_filename, const std::shared_ptr& usb_monitor ): - m_adbd_client{std::make_shared(socket_path)}, + m_socket_path{socket_path}, m_public_keys_filename{public_keys_filename}, m_usb_monitor{usb_monitor} { m_usb_monitor->on_usb_disconnected().connect([this](const std::string& /*usb_name*/) { - m_snap.reset(); + restart(); }); + restart(); + } + + ~Impl() =default; + +private: + + void restart() + { + // clear out old state + m_snap_connections.clear(); + m_snap.reset(); + m_adbd_client.reset(); + + // add a new client + m_adbd_client.reset(new GAdbdClient{m_socket_path}); m_adbd_client->on_pk_request().connect( - [this](const AdbdClient::PKRequest& req){ + [this](const AdbdClient::PKRequest& req) { - m_snap.reset(new UsbSnap(req.fingerprint), - [this](UsbSnap* snap){ - m_snap_connections.clear(); - delete snap; - } - ); + g_debug("%s got pk request", G_STRLOC); + m_snap = std::make_shared(req.fingerprint); m_snap_connections.insert((*m_snap).on_user_response().connect( [this,req](AdbdClient::PKResponse response, bool remember_choice){ - g_message("%s user responded! response %d, remember %d", G_STRLOC, int(response), int(remember_choice)); + g_debug("%s user responded! response %d, remember %d", G_STRLOC, int(response), int(remember_choice)); req.respond(response); - g_message("%s", G_STRLOC); if (remember_choice && (response == AdbdClient::PKResponse::ALLOW)) write_public_key(req.public_key); g_idle_add([](gpointer gself){static_cast(gself)->m_snap.reset(); return G_SOURCE_REMOVE;}, this); @@ -69,16 +80,11 @@ public: )); } ); - } - ~Impl() =default; - -private: - void write_public_key(const std::string& public_key) { - g_message("%s writing public key '%s' to '%s'", G_STRLOC, public_key.c_str(), m_public_keys_filename.c_str()); + g_debug("%s writing public key '%s' to '%s'", G_STRLOC, public_key.c_str(), m_public_keys_filename.c_str()); // confirm the directory exists auto dirname = g_path_get_dirname(m_public_keys_filename.c_str()); @@ -107,10 +113,12 @@ private: close(fd); } - std::shared_ptr m_adbd_client; + const std::string m_socket_path; const std::string m_public_keys_filename; + std::shared_ptr m_usb_monitor; + std::shared_ptr m_adbd_client; std::shared_ptr m_snap; std::set m_snap_connections; }; diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp index 349d80e..41c78c6 100644 --- a/src/usb-snap.cpp +++ b/src/usb-snap.cpp @@ -148,7 +148,6 @@ private: { GError* error {}; auto reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION(obus), res, &error); -g_message("%s got notify response %s", G_STRLOC, g_variant_print(reply, true)); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning("UsbSnap: Error calling Notify: %s", error->message); -- cgit v1.2.3 From 82588108a40fb50b2bbd3c7b89b990f76f488edc Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 23 Mar 2016 12:16:06 -0500 Subject: replace text 'Deny' with 'Don't Allow' for consistency with other permission prompts --- src/usb-snap.cpp | 2 +- tests/integration/usb-manager-test.cpp | 2 +- tests/unit/usb-snap-test.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/usb-snap.cpp') diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp index 41c78c6..ba964fb 100644 --- a/src/usb-snap.cpp +++ b/src/usb-snap.cpp @@ -111,7 +111,7 @@ private: g_variant_builder_add(&actions_builder, "s", ACTION_ALLOW); g_variant_builder_add(&actions_builder, "s", _("Allow")); g_variant_builder_add(&actions_builder, "s", ACTION_DENY); - g_variant_builder_add(&actions_builder, "s", _("Deny")); + g_variant_builder_add(&actions_builder, "s", _("Don't Allow")); GVariantBuilder hints_builder; g_variant_builder_init(&hints_builder, G_VARIANT_TYPE_VARDICT); diff --git a/tests/integration/usb-manager-test.cpp b/tests/integration/usb-manager-test.cpp index 03c80c7..d62756f 100644 --- a/tests/integration/usb-manager-test.cpp +++ b/tests/integration/usb-manager-test.cpp @@ -122,7 +122,7 @@ TEST_F(UsbManagerFixture, Allow) EXPECT_EQ("computer-symbolic", args.at(2)); // icon name EXPECT_EQ("Allow USB Debugging?", args.at(3)); // summary EXPECT_EQ(QString::fromUtf8("The computer's RSA key fingerprint is: ") + QString::fromUtf8(fingerprint.c_str()), args.at(4)); // body - EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Deny"}), args.at(5)); // actions + EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Don't Allow"}), args.at(5)); // actions EXPECT_EQ(-1, args.at(7)); QVariantMap hints; diff --git a/tests/unit/usb-snap-test.cpp b/tests/unit/usb-snap-test.cpp index 40de94a..3b778dd 100644 --- a/tests/unit/usb-snap-test.cpp +++ b/tests/unit/usb-snap-test.cpp @@ -106,7 +106,7 @@ TEST_F(UsbSnapFixture, TestRoundTrip) EXPECT_EQ("computer-symbolic", args.at(2)); // icon name EXPECT_EQ("Allow USB Debugging?", args.at(3)); // summary EXPECT_EQ(QString::fromUtf8("The computer's RSA key fingerprint is: ") + test.fingerprint, args.at(4)); // body - EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Deny"}), args.at(5)); // actions + EXPECT_EQ(QStringList({"allow", "Allow", "deny", "Don't Allow"}), args.at(5)); // actions EXPECT_EQ(-1, args.at(7)); QVariantMap hints; -- cgit v1.2.3