mirror-kde-plasma-desktop/kcms/dateandtime/main.cpp
David Redondo 8af82f5f6a kcms/dateandtime: Auth manually before issuing calls to timedate1
Authorization for set-time implies set-ntp and set-timezone, if acquired first
then the user will only see one polkit dialog. However ntp needs to be disabled
first before the time can be changed. Unfortunately due to deficiencies inside
KAuth with the Polkit backend it's not possible to  authorize an arbitrary action,
so Polkit-qt is used for this. The code "executes" still a KAuth action to make
use of the  side channel for parent window and activation token to our agent,
but since the action doesn't have a helper will actually not execute anything.
2026-10-08 09:28:22 +02:00

134 lines
4.8 KiB
C++

/*
SPDX-FileCopyrightText: 1998 Luca Montecchiani <m.luca@usa.net>
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "main.h"
#include <time.h>
#include <unistd.h>
#include <QVBoxLayout>
#include <KAboutData>
#include <KAuth/Action>
#include <KAuth/ExecuteJob>
#include <KMessageBox>
#include <KPluginFactory>
#include <PolkitQt1/Authority>
#include <PolkitQt1/Subject>
#include <QDBusConnection>
#include "dtime.h"
#include "timedated_interface.h"
K_PLUGIN_CLASS_WITH_JSON(KclockModule, "kcm_clock.json")
KclockModule::KclockModule(QObject *parent, const KPluginMetaData &metaData)
: KCModule(parent, metaData)
{
QVBoxLayout *layout = new QVBoxLayout(widget());
layout->setContentsMargins(0, 0, 0, 0);
dtime = new Dtime(widget());
layout->addWidget(dtime);
connect(dtime, &Dtime::timeChanged, this, &KCModule::setNeedsSave);
connect(dtime, &Dtime::selectedTimeZoneChanged, this, &KCModule::setNeedsSave);
setButtons(Help | Apply);
setAuthActionName(QStringLiteral("org.freedesktop.timedate1.set-time"));
}
bool KclockModule::timedatedSave()
{
const qint64 timeDiff = dtime->userTime().toMSecsSinceEpoch() - QDateTime::currentMSecsSinceEpoch();
OrgFreedesktopTimedate1Interface timedateIface(QStringLiteral("org.freedesktop.timedate1"),
QStringLiteral("/org/freedesktop/timedate1"),
QDBusConnection::systemBus());
// Auth manually for "set-time", this implies authroization for all the other actions => the user only sees one popup
// HACK First We use KAuth here just so it sidechannels parent window and activation token for us, everything else is noop because no helper
KAuth::Action action(authActionName());
action.setParentWindow(widget()->window()->windowHandle());
auto job = action.execute();
job->exec();
// Second do the actual authorization manually
auto authority = PolkitQt1::Authority::instance();
auto authorized = authority->checkAuthorizationSync(authActionName(),
PolkitQt1::SystemBusNameSubject(QDBusConnection::systemBus().baseService()),
PolkitQt1::Authority::AllowUserInteraction);
if (authorized != PolkitQt1::Authority::Yes) {
return false;
}
// final arg in each method is "user-interaction" i.e whether it's OK for polkit to ask for auth
// we cannot send requests up front then block for all replies as we need NTP to be disabled before we can make a call to SetTime
// timedated processes these in parallel and will return an error otherwise
auto reply = timedateIface.SetNTP(dtime->ntpEnabled(), true);
reply.waitForFinished();
if (reply.isError()) {
if (reply.error().name() != QDBusError::errorString(QDBusError::AccessDenied)) {
KMessageBox::error(widget(), i18n("Unable to change NTP settings"));
}
qWarning() << "Failed to enable NTP" << reply.error().name() << reply.error().message();
return false;
}
if (!dtime->ntpEnabled()) {
//*1000 for milliseconds -> microseconds
auto reply = timedateIface.SetTime(timeDiff * 1000, true, true);
reply.waitForFinished();
if (reply.isError()) {
if (reply.error().name() != QDBusError::errorString(QDBusError::AccessDenied)) {
KMessageBox::error(widget(), i18n("Unable to set current time"));
}
qWarning() << "Failed to set current time" << reply.error().name() << reply.error().message();
return false;
}
}
QString selectedTimeZone = dtime->selectedTimeZone();
if (!selectedTimeZone.isEmpty()) {
auto reply = timedateIface.SetTimezone(selectedTimeZone, true);
reply.waitForFinished();
if (reply.isError()) {
if (reply.error().name() != QDBusError::errorString(QDBusError::AccessDenied)) {
KMessageBox::error(widget(), i18n("Unable to set timezone"));
}
qWarning() << "Failed to set timezone" << reply.error().name() << reply.error().message();
return false;
}
}
return true;
}
void KclockModule::save()
{
widget()->setDisabled(true);
if (timedatedSave()) {
QDBusMessage msg = QDBusMessage::createSignal(QStringLiteral("/org/kde/kcmshell_clock"), //
QStringLiteral("org.kde.kcmshell_clock"),
QStringLiteral("clockUpdated"));
QDBusConnection::sessionBus().send(msg);
}
load();
}
void KclockModule::load()
{
dtime->load();
widget()->setDisabled(false);
}
#include "main.moc"
#include "moc_main.cpp"