Skip to content
Open
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
4 changes: 4 additions & 0 deletions docs/pages/en/guide/tips-and-faq/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-`<process id>`-`<instance number>`. 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 is the password sometimes requested only once when installing or uninstalling an application?

Package manager operations are authorized through polkit, and the policies of installing, updating and uninstalling use `auth_admin_keep`, which keeps a successful authentication for a short period of time (five minutes). The result is remembered by `ll-package-manager` per user and login session, so running `ll-cli install`, `ll-cli uninstall` and similar commands several times in the same session does not ask for the password again. Once the keep window elapsed, or in another login session, authentication is requested again. Operations whose policy is `auth_admin`, such as `ll-cli prune` and changing the repository configuration, still require authentication on every call.
4 changes: 4 additions & 0 deletions docs/pages/guide/tips-and-faq/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,7 @@ SPDX-License-Identifier: LGPL-3.0-or-later
20. 应用启动后,为什么不显示应用托盘?

这可能是由应用注册托盘使用了相同的服务名,按照 KDE/freedesktop StatusNotifierItem 规范应用注册服务名为 org.kdeStatusNotifierItem-`<process id>`-`<instance number>`,在如意玲珑应用中,应用运行时的 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`,即认证成功后的短时间内(5 分钟)不再重复认证。授权结果由 `ll-package-manager` 按「用户 + 登录会话」缓存,因此同一个登录会话内连续执行 `ll-cli install`、`ll-cli uninstall` 等命令不会反复弹出认证框;超过免密时间或更换登录会话后会重新要求认证。`ll-cli prune`、修改仓库配置等策略为 `auth_admin` 的操作,每次仍需要认证。
2 changes: 2 additions & 0 deletions libs/linglong/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ pfl_add_library(
src/linglong/package_manager/package_update.h
src/linglong/package_manager/polkit_authority.cpp
src/linglong/package_manager/polkit_authority.h
src/linglong/package_manager/polkit_authorization_cache.cpp
src/linglong/package_manager/polkit_authorization_cache.h
src/linglong/package_manager/ref_installation.cpp
src/linglong/package_manager/ref_installation.h
src/linglong/package_manager/task.cpp
Expand Down
113 changes: 111 additions & 2 deletions libs/linglong/src/linglong/package_manager/polkit_authority.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,27 @@

#include "polkit_authority.h"

#include "linglong/utils/log/log.h"

Check warning on line 7 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: "linglong/utils/log/log.h" not found.

#include <fmt/format.h>

Check warning on line 9 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusArgument> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <systemd/sd-login.h>

Check warning on line 10 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusConnection> not found. Please note: Cppcheck does not need standard library headers to get proper results.

Check warning on line 11 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusMessage> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusArgument>

Check warning on line 12 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusMetaType> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusConnection>

Check warning on line 13 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusPendingCallWatcher> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusMessage>

Check warning on line 14 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDBusPendingReply> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusMetaType>

Check warning on line 15 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QMap> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusPendingCallWatcher>

Check warning on line 16 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QString> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusPendingReply>

Check warning on line 17 in libs/linglong/src/linglong/package_manager/polkit_authority.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QVariant> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QDBusReply>
#include <QMap>
#include <QString>
#include <QVariant>

#include <cstdlib>
#include <mutex>
#include <optional>

#include <sys/types.h>

namespace {

Expand Down Expand Up @@ -79,13 +87,79 @@
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)
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<quint32> 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);

// The system bus is a local socket, so this blocking round trip is cheap
// and the bus daemon always answers.
const QDBusReply<quint32> reply = QDBusConnection::systemBus().call(msg);
if (!reply.isValid()) {
return LINGLONG_ERR(
fmt::format("{} failed: {}", method, reply.error().message().toStdString()));
}

return reply.value();
}

} // namespace

utils::error::Result<PolkitCallerIdentity>
PolkitAuthority::resolveCallerIdentity(const std::string &systemBusName) noexcept
{
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);
}

auto uid = queryBusConnection("GetConnectionUnixUser", systemBusName);
if (!uid) {
return LINGLONG_ERR(uid);
}

// 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));
}

PolkitCallerIdentity identity{ .uid = *uid, .sessionId = sessionId };
::free(sessionId);

return identity;
}

void PolkitAuthority::checkAuthorizationAsync(
const std::string &actionId,
const std::string &systemBusName,
Expand All @@ -96,6 +170,32 @@

register_type();

// polkit remembers the authorizations it grants for `auth_*_keep` actions
// per subject, and a `system-bus-name` subject is resolved to the calling
// process. Since ll-cli is started from scratch for every operation, that
// key never repeats and the retained authorization can not be reused.
// Remember the grant for the session of the caller instead, for the same
// five minutes polkit itself would have kept it.
std::optional<PolkitCallerIdentity> identity;
if (isKeepAuthorizedAction(actionId)) {
auto resolved = resolveCallerIdentity(systemBusName);
if (resolved) {
identity = std::move(*resolved);
} else {
// Without a session there is nothing to key the cache on, so fall
// back to asking polkit for every single call.
LogD("can't remember an authorization for {}: {}",
systemBusName,
resolved.error().message());
}

if (identity && PolkitAuthorizationCache::instance().isGranted(*identity, actionId)) {
LogD("reuse the authorization of session {} for {}", identity->sessionId, actionId);
callback(true);
return;
}
}

auto bus = QDBusConnection::systemBus();

auto msg = QDBusMessage::createMethodCall(QString::fromLatin1(POLKIT_SERVICE),
Expand All @@ -117,7 +217,9 @@
QObject::connect(
watcher,
&QDBusPendingCallWatcher::finished,
[callback = std::move(callback)](QDBusPendingCallWatcher *self) {
[actionId,
identity = std::move(identity),
callback = std::move(callback)](QDBusPendingCallWatcher *self) {
LINGLONG_TRACE("check polkit authorization");

LogD("CheckAuthorization return");
Expand All @@ -130,7 +232,14 @@
return;
}

callback(res.value().isAuthorized);
const bool isAuthorized = res.value().isAuthorized;
if (isAuthorized && identity) {
// polkit only retains this authorization for the connection of the
// caller, which is about to go away, so keep it for the session.
PolkitAuthorizationCache::instance().grant(*identity, actionId);
}

callback(isAuthorized);
});
}

Expand Down
19 changes: 19 additions & 0 deletions libs/linglong/src/linglong/package_manager/polkit_authority.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include "linglong/package_manager/polkit_authorization_cache.h"
#include "linglong/utils/error/error.h"

#include <functional>
Expand All @@ -16,6 +17,24 @@ class PolkitAuthority
public:
PolkitAuthority() = delete;

// Resolves the session scoped identity of the D-Bus caller behind
// `systemBusName`. The uid and the pid are read from the bus daemon, which
// owns them, and the pid is only used to find the login session of the
// caller, so the result does not change while the user stays logged in.
// Fails when the caller is already gone or does not belong to a login
// session, in which case the identity must not be remembered.
[[nodiscard]] static utils::error::Result<PolkitCallerIdentity>
resolveCallerIdentity(const std::string &systemBusName) noexcept;

// 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. The result of a keepable action is therefore remembered for the
// session of the caller and reused until the window in which polkit itself
// would have kept the authorization ends.
static void checkAuthorizationAsync(const std::string &actionId,
const std::string &systemBusName,
std::function<void(utils::error::Result<bool>)> callback,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "polkit_authorization_cache.h"

#include <algorithm>
#include <array>
#include <iterator>
#include <string_view>
#include <utility>

namespace linglong::service {

namespace {

// Actions whose policy retains the authorization. Keep them in sync with the
// `auth_admin_keep` entries of
// misc/share/polkit-1/actions/org.deepin.linglong.PackageManager1.policy.
constexpr std::array<std::string_view, 4> keepAuthorizedActions{
"org.deepin.linglong.PackageManager1.install",
"org.deepin.linglong.PackageManager1.install-from-file",
"org.deepin.linglong.PackageManager1.uninstall",
"org.deepin.linglong.PackageManager1.update",
};

} // namespace

bool isKeepAuthorizedAction(const std::string &actionId) noexcept
{
const auto candidate = std::string_view{ actionId };
return std::find(keepAuthorizedActions.cbegin(), keepAuthorizedActions.cend(), candidate)
!= keepAuthorizedActions.cend();
}

PolkitAuthorizationCache::PolkitAuthorizationCache(Clock::duration keepDuration,
std::size_t capacity,
Now now)
: mKeepDuration(keepDuration)
, mCapacity(capacity == 0 ? 1 : capacity)
, mNow(std::move(now))
{
}

bool PolkitAuthorizationCache::isGranted(const PolkitCallerIdentity &caller,
const std::string &actionId)
{
if (!caller.isValid()) {
return false;
}

std::lock_guard lock(mMutex);

const auto it = mGrants.find(Key{ caller.uid, caller.sessionId, actionId });
if (it == mGrants.end()) {
return false;
}

if (it->second <= nowLocked()) {
mGrants.erase(it);
return false;
}

return true;
}

void PolkitAuthorizationCache::grant(const PolkitCallerIdentity &caller,
const std::string &actionId)
{
if (!caller.isValid()) {
return;
}

std::lock_guard lock(mMutex);

const auto now = nowLocked();
pruneLocked(now);
mGrants[Key{ caller.uid, caller.sessionId, actionId }] = now + mKeepDuration;

while (mGrants.size() > mCapacity) {
// Drop the grant that expires first, it is the least useful one.
auto oldest = mGrants.begin();
for (auto it = std::next(oldest); it != mGrants.end(); ++it) {
if (it->second < oldest->second) {
oldest = it;
}
}
mGrants.erase(oldest);
}
}

void PolkitAuthorizationCache::clear()
{
std::lock_guard lock(mMutex);
mGrants.clear();
}

std::size_t PolkitAuthorizationCache::size()
{
std::lock_guard lock(mMutex);
pruneLocked(nowLocked());
return mGrants.size();
}

PolkitAuthorizationCache &PolkitAuthorizationCache::instance()
{
static PolkitAuthorizationCache cache;
return cache;
}

void PolkitAuthorizationCache::pruneLocked(Clock::time_point now)
{
for (auto it = mGrants.begin(); it != mGrants.end();) {
if (it->second <= now) {
it = mGrants.erase(it);
} else {
++it;
}
}
}

auto PolkitAuthorizationCache::nowLocked() const -> Clock::time_point
{
return mNow ? mNow() : Clock::now();
}

} // namespace linglong::service
Loading
Loading