From e36eb3d90af4e7040829c86b8f678d6734e3e97e Mon Sep 17 00:00:00 2001 From: zhang hongyuan <100520587+Metastarx@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:56:18 +0800 Subject: [PATCH] fix(package-manager): let polkit retain session authorizations Use the caller's login session as the polkit subject for actions whose policy retains authorization. Polkit now handles active/inactive policy, expiry, logout, and revocation instead of a separate daemon cache. Keep ordinary admin actions and callers without a login session on the original D-Bus subject. Limit the credential lookup timeout and cover subject selection in tests. Refs: https://github.com/OpenAtom-Linyaps/linyaps/issues/1904 Signed-off-by: zhang hongyuan <100520587+Metastarx@users.noreply.github.com> --- docs/pages/en/guide/tips-and-faq/faq.md | 4 + docs/pages/guide/tips-and-faq/faq.md | 4 + .../package_manager/polkit_authority.cpp | 107 +++++++++++++++++- .../package_manager/polkit_authority.h | 19 ++++ libs/linglong/tests/ll-tests/CMakeLists.txt | 1 + .../package_manager/polkit_authority_test.cpp | 45 ++++++++ 6 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 libs/linglong/tests/ll-tests/src/linglong/package_manager/polkit_authority_test.cpp diff --git a/docs/pages/en/guide/tips-and-faq/faq.md b/docs/pages/en/guide/tips-and-faq/faq.md index 914c92920..ff2c1928e 100644 --- a/docs/pages/en/guide/tips-and-faq/faq.md +++ b/docs/pages/en/guide/tips-and-faq/faq.md @@ -89,3 +89,7 @@ SPDX-License-Identifier: LGPL-3.0-or-later 20. Why doesn't the application tray display after the application starts? This may be because the application registered the tray using the same service name. According to the KDE/freedesktop StatusNotifierItem specification, applications should register service names as org.kde.StatusNotifierItem-``-``. In Linyaps applications, the pid during application runtime is 19. You can check whether a service has been registered using the following command: `dbus-send --session --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.NameHasOwner string:org.kde.StatusNotifierItem-19-1`. If `boolean true` exists, it means the service has been registered. + +21. Why do some installation or removal commands not ask for a password again? + + Package manager operations are authorized through polkit. The policies for installing, updating and uninstalling use `auth_admin_keep`, so polkit may retain a successful authorization for the caller's login session for a short period. A later `ll-cli` command in that session may reuse it until polkit expires or revokes it. A caller without a login session is checked using its D-Bus connection instead. Operations whose policy is `auth_admin`, such as `ll-cli prune` and changing the repository configuration, still require authentication on every call. diff --git a/docs/pages/guide/tips-and-faq/faq.md b/docs/pages/guide/tips-and-faq/faq.md index 69d96466c..d153ba8fa 100644 --- a/docs/pages/guide/tips-and-faq/faq.md +++ b/docs/pages/guide/tips-and-faq/faq.md @@ -89,3 +89,7 @@ SPDX-License-Identifier: LGPL-3.0-or-later 20. 应用启动后,为什么不显示应用托盘? 这可能是由应用注册托盘使用了相同的服务名,按照 KDE/freedesktop StatusNotifierItem 规范应用注册服务名为 org.kdeStatusNotifierItem-``-``,在如意玲珑应用中,应用运行时的 pid 为19,可以通过以下命令查看是否有注册过的服务,`dbus-send --session --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.NameHasOwner string:org.kde.StatusNotifierItem-19-1`,如果存在 `boolean true` 说明服务被注册过。 + +21. 安装、卸载应用时需要输入密码,为什么同一会话内有时不会再次提示? + + 如意玲珑的包管理操作通过 polkit 鉴权。安装、更新、卸载等策略使用 `auth_admin_keep`;polkit 可在短时间内为调用者的登录会话保留认证,并自行处理过期和撤销。同一会话内再次执行 `ll-cli` 时可能复用该授权;没有登录会话的调用者仍按其 D-Bus 连接单独校验。`ll-cli prune`、修改仓库配置等策略为 `auth_admin` 的操作,每次仍需要认证。 diff --git a/libs/linglong/src/linglong/package_manager/polkit_authority.cpp b/libs/linglong/src/linglong/package_manager/polkit_authority.cpp index 68cb933d4..d62a7eef8 100644 --- a/libs/linglong/src/linglong/package_manager/polkit_authority.cpp +++ b/libs/linglong/src/linglong/package_manager/polkit_authority.cpp @@ -6,17 +6,27 @@ #include "linglong/utils/log/log.h" +#include +#include + #include #include #include #include #include #include +#include #include #include #include +#include +#include +#include #include +#include + +#include namespace { @@ -79,6 +89,10 @@ constexpr const char *POLKIT_SERVICE = "org.freedesktop.PolicyKit1"; constexpr const char *POLKIT_PATH = "/org/freedesktop/PolicyKit1/Authority"; constexpr const char *POLKIT_INTERFACE = "org.freedesktop.PolicyKit1.Authority"; +constexpr const char *DBUS_SERVICE = "org.freedesktop.DBus"; +constexpr const char *DBUS_PATH = "/org/freedesktop/DBus"; +constexpr const char *DBUS_INTERFACE = "org.freedesktop.DBus"; + } // namespace Q_DECLARE_METATYPE(PolkitSubject) @@ -86,6 +100,79 @@ Q_DECLARE_METATYPE(PolkitResult) namespace linglong::service { +namespace { + +// Reads a numeric property of the connection behind `systemBusName` from the +// bus daemon. Only the bus daemon knows the credentials of a connection, so the +// caller can not fake them. +utils::error::Result queryBusConnection(const char *method, + const std::string &systemBusName) +{ + LINGLONG_TRACE("query a property of a dbus connection"); + + auto msg = QDBusMessage::createMethodCall(QString::fromLatin1(DBUS_SERVICE), + QString::fromLatin1(DBUS_PATH), + QString::fromLatin1(DBUS_INTERFACE), + QString::fromLatin1(method)); + msg << QString::fromStdString(systemBusName); + + // A stalled bus must not block the package manager dispatch thread forever. + const QDBusReply reply = QDBusConnection::systemBus().call(msg, QDBus::Block, 1000); + if (!reply.isValid()) { + return LINGLONG_ERR( + fmt::format("{} failed: {}", method, reply.error().message().toStdString())); + } + + return reply.value(); +} + +utils::error::Result resolveCallerSession(const std::string &systemBusName) +{ + LINGLONG_TRACE("resolve the identity of a polkit caller"); + + if (systemBusName.empty()) { + return LINGLONG_ERR("the caller did not provide its bus name"); + } + + auto pid = queryBusConnection("GetConnectionUnixProcessID", systemBusName); + if (!pid) { + return LINGLONG_ERR(pid); + } + + // The pid only serves as a lookup key for the login session: unlike the pid + // the session stays the same while ll-cli is restarted for every operation. + char *sessionId = nullptr; + if (::sd_pid_get_session(static_cast<::pid_t>(*pid), &sessionId) < 0 || sessionId == nullptr) { + return LINGLONG_ERR( + fmt::format("the caller {} does not belong to a login session", systemBusName)); + } + + std::string session{ sessionId }; + ::free(sessionId); + + return session; +} + +constexpr std::array keepActions{ + "org.deepin.linglong.PackageManager1.install", + "org.deepin.linglong.PackageManager1.install-from-file", + "org.deepin.linglong.PackageManager1.uninstall", + "org.deepin.linglong.PackageManager1.update", +}; + +} // namespace + +PolkitSubjectIdentity selectPolkitSubject(std::string_view actionId, + std::string_view busName, + std::string_view sessionId) +{ + if (!sessionId.empty() + && std::find(keepActions.cbegin(), keepActions.cend(), actionId) != keepActions.cend()) { + return { "unix-session", std::string{ sessionId } }; + } + return { "system-bus-name", std::string{ busName } }; +} + void PolkitAuthority::checkAuthorizationAsync( const std::string &actionId, const std::string &systemBusName, @@ -96,6 +183,18 @@ void PolkitAuthority::checkAuthorizationAsync( register_type(); + std::string sessionId; + if (std::find(keepActions.cbegin(), keepActions.cend(), actionId) != keepActions.cend()) { + auto resolved = resolveCallerSession(systemBusName); + if (resolved) { + sessionId = std::move(*resolved); + } else { + LogD("can't resolve the login session of {}: {}", + systemBusName, + resolved.error().message()); + } + } + auto bus = QDBusConnection::systemBus(); auto msg = QDBusMessage::createMethodCall(QString::fromLatin1(POLKIT_SERVICE), @@ -104,9 +203,11 @@ void PolkitAuthority::checkAuthorizationAsync( QStringLiteral("CheckAuthorization")); PolkitSubject subject; - subject.kind = QStringLiteral("system-bus-name"); - subject.details.insert(QStringLiteral("name"), - QVariant::fromValue(QString::fromStdString(systemBusName))); + const auto selected = selectPolkitSubject(actionId, systemBusName, sessionId); + subject.kind = QString::fromLatin1(selected.kind.data(), selected.kind.size()); + subject.details.insert(selected.kind == "unix-session" ? QStringLiteral("session-id") + : QStringLiteral("name"), + QVariant::fromValue(QString::fromStdString(selected.value))); msg << QVariant::fromValue(subject) << QString::fromStdString(actionId) << QVariant::fromValue(QMap()) diff --git a/libs/linglong/src/linglong/package_manager/polkit_authority.h b/libs/linglong/src/linglong/package_manager/polkit_authority.h index cb4a6db07..7ca01b206 100644 --- a/libs/linglong/src/linglong/package_manager/polkit_authority.h +++ b/libs/linglong/src/linglong/package_manager/polkit_authority.h @@ -8,14 +8,33 @@ #include #include +#include namespace linglong::service { +struct PolkitSubjectIdentity +{ + std::string_view kind; + std::string value; +}; + +[[nodiscard]] PolkitSubjectIdentity selectPolkitSubject(std::string_view actionId, + std::string_view busName, + std::string_view sessionId); + class PolkitAuthority { public: PolkitAuthority() = delete; + // Checks whether `systemBusName` may perform `actionId`. + // + // polkit remembers the authorizations it grants for `auth_*_keep` actions + // per subject, and a `system-bus-name` subject is resolved to the calling + // process. As ll-cli is started from scratch for every operation, that key + // never repeats and the user would have to authenticate for every command + // line. A keepable action uses the caller's login session as its polkit + // subject, letting polkit own the retention and revocation rules. static void checkAuthorizationAsync(const std::string &actionId, const std::string &systemBusName, std::function)> callback, diff --git a/libs/linglong/tests/ll-tests/CMakeLists.txt b/libs/linglong/tests/ll-tests/CMakeLists.txt index abe892fe5..e4a547b27 100644 --- a/libs/linglong/tests/ll-tests/CMakeLists.txt +++ b/libs/linglong/tests/ll-tests/CMakeLists.txt @@ -45,6 +45,7 @@ pfl_add_executable( src/linglong/package_manager/task_test.cpp src/linglong/package_manager/task_queue_test.cpp src/linglong/package_manager/package_update_test.cpp + src/linglong/package_manager/polkit_authority_test.cpp src/linglong/package_manager/ref_installation_test.cpp src/linglong/package_manager/uab_installation_test.cpp src/linglong/package/reference_test.cpp diff --git a/libs/linglong/tests/ll-tests/src/linglong/package_manager/polkit_authority_test.cpp b/libs/linglong/tests/ll-tests/src/linglong/package_manager/polkit_authority_test.cpp new file mode 100644 index 000000000..c6f8a7be6 --- /dev/null +++ b/libs/linglong/tests/ll-tests/src/linglong/package_manager/polkit_authority_test.cpp @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include + +#include "linglong/package_manager/polkit_authority.h" + +namespace { + +using linglong::service::selectPolkitSubject; + +TEST(PolkitAuthorityTest, KeepActionsUseLoginSession) +{ + for (const auto *action : { + "org.deepin.linglong.PackageManager1.install", + "org.deepin.linglong.PackageManager1.install-from-file", + "org.deepin.linglong.PackageManager1.uninstall", + "org.deepin.linglong.PackageManager1.update", + }) { + const auto subject = selectPolkitSubject(action, ":1.42", "c3"); + EXPECT_EQ(subject.kind, "unix-session"); + EXPECT_EQ(subject.value, "c3"); + } +} + +TEST(PolkitAuthorityTest, PlainAdminActionUsesCallerBusName) +{ + const auto subject = selectPolkitSubject("org.deepin.linglong.PackageManager1.prune", + ":1.42", + "c3"); + EXPECT_EQ(subject.kind, "system-bus-name"); + EXPECT_EQ(subject.value, ":1.42"); +} + +TEST(PolkitAuthorityTest, MissingSessionFallsBackToCallerBusName) +{ + const auto subject = selectPolkitSubject("org.deepin.linglong.PackageManager1.install", + ":1.42", + ""); + EXPECT_EQ(subject.kind, "system-bus-name"); + EXPECT_EQ(subject.value, ":1.42"); +} + +} // namespace