1 | /* |
2 | SPDX-FileCopyrightText: 2009-2012 Dario Freddi <drf@kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.1-or-later |
5 | */ |
6 | |
7 | #include "executejob.h" |
8 | |
9 | #include "BackendsManager.h" |
10 | #include "kauthdebug.h" |
11 | |
12 | #include <QCoreApplication> |
13 | #include <QEventLoop> |
14 | #include <QGuiApplication> |
15 | #include <QHash> |
16 | #include <QTimer> |
17 | #include <QWindow> |
18 | |
19 | namespace KAuth |
20 | { |
21 | class ExecuteJobPrivate |
22 | { |
23 | Q_DECLARE_TR_FUNCTIONS(KAuth::ExecuteJob) |
24 | |
25 | public: |
26 | explicit ExecuteJobPrivate(ExecuteJob *parent) |
27 | : q(parent) |
28 | { |
29 | } |
30 | |
31 | ExecuteJob *q; |
32 | Action action; |
33 | |
34 | Action::ExecutionMode mode; |
35 | QVariantMap data; |
36 | |
37 | void doExecuteAction(); |
38 | void doAuthorizeAction(); |
39 | void actionPerformedSlot(const QString &action, const ActionReply &reply); |
40 | void progressStepSlot(const QString &action, int i); |
41 | void progressStepSlot(const QString &action, const QVariantMap &data); |
42 | void statusChangedSlot(const QString &action, KAuth::Action::AuthStatus status); |
43 | }; |
44 | |
45 | static QWindow *parentWindow(const Action &action) |
46 | { |
47 | QWindow *window = action.parentWindow(); |
48 | if (!window && qGuiApp) { |
49 | window = qGuiApp->focusWindow(); |
50 | if (!window) { |
51 | if (const auto windows = qGuiApp->topLevelWindows(); !windows.isEmpty()) { |
52 | window = windows.first(); |
53 | } |
54 | } |
55 | if (window) { |
56 | // Don't warn for background services with no windows. |
57 | qCWarning(KAUTH) << "Action" << action.name() << "has no parentWindow, assuming" << window; |
58 | } |
59 | } |
60 | return window; |
61 | } |
62 | |
63 | ExecuteJob::ExecuteJob(const Action &action, Action::ExecutionMode mode, QObject *parent) |
64 | : KJob(parent) |
65 | , d(new ExecuteJobPrivate(this)) |
66 | { |
67 | d->action = action; |
68 | d->mode = mode; |
69 | |
70 | HelperProxy *helper = BackendsManager::helperProxy(); |
71 | |
72 | connect(sender: helper, signal: &KAuth::HelperProxy::actionPerformed, context: this, slot: [this](const QString &action, const ActionReply &reply) { |
73 | d->actionPerformedSlot(action, reply); |
74 | }); |
75 | connect(sender: helper, signal: &KAuth::HelperProxy::progressStep, context: this, slot: [this](const QString &action, int i) { |
76 | d->progressStepSlot(action, i); |
77 | }); |
78 | connect(sender: helper, signal: &KAuth::HelperProxy::progressStepData, context: this, slot: [this](const QString &action, const QVariantMap &data) { |
79 | d->progressStepSlot(action, data); |
80 | }); |
81 | |
82 | connect(sender: BackendsManager::authBackend(), signal: &KAuth::AuthBackend::actionStatusChanged, context: this, slot: [this](const QString &action, Action::AuthStatus status) { |
83 | d->statusChangedSlot(action, status); |
84 | }); |
85 | } |
86 | |
87 | ExecuteJob::~ExecuteJob() = default; |
88 | |
89 | Action ExecuteJob::action() const |
90 | { |
91 | return d->action; |
92 | } |
93 | |
94 | QVariantMap ExecuteJob::data() const |
95 | { |
96 | return d->data; |
97 | } |
98 | |
99 | void ExecuteJob::start() |
100 | { |
101 | if (!d->action.isValid()) { |
102 | qCWarning(KAUTH) << "Tried to start an invalid action: " << d->action.name(); |
103 | ActionReply reply(ActionReply::InvalidActionError); |
104 | reply.setErrorDescription(tr(s: "Tried to start an invalid action" )); |
105 | d->actionPerformedSlot(action: d->action.name(), reply); |
106 | return; |
107 | } |
108 | |
109 | switch (d->mode) { |
110 | case Action::ExecuteMode: |
111 | QTimer::singleShot(interval: 0, receiver: this, slot: [this]() { |
112 | d->doExecuteAction(); |
113 | }); |
114 | break; |
115 | case Action::AuthorizeOnlyMode: |
116 | QTimer::singleShot(interval: 0, receiver: this, slot: [this]() { |
117 | d->doAuthorizeAction(); |
118 | }); |
119 | break; |
120 | default: { |
121 | ActionReply reply(ActionReply::InvalidActionError); |
122 | reply.setErrorDescription(tr(s: "Unknown execution mode chosen" )); |
123 | d->actionPerformedSlot(action: d->action.name(), reply); |
124 | break; |
125 | } |
126 | } |
127 | } |
128 | |
129 | bool ExecuteJob::kill(KillVerbosity verbosity) |
130 | { |
131 | BackendsManager::helperProxy()->stopAction(action: d->action.name(), helperID: d->action.helperId()); |
132 | KJob::kill(verbosity); |
133 | return true; |
134 | } |
135 | |
136 | void ExecuteJobPrivate::doExecuteAction() |
137 | { |
138 | // If this action authorizes from the client, let's do it now |
139 | if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::AuthorizeFromClientCapability) { |
140 | if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::PreAuthActionCapability) { |
141 | BackendsManager::authBackend()->preAuthAction(action: action.name(), parent: parentWindow(action)); |
142 | } |
143 | |
144 | Action::AuthStatus s = BackendsManager::authBackend()->authorizeAction(action: action.name()); |
145 | |
146 | if (s == Action::AuthorizedStatus) { |
147 | if (action.hasHelper()) { |
148 | BackendsManager::helperProxy()->executeAction(action: action.name(), helperID: action.helperId(), details: action.detailsV2(), arguments: action.arguments(), timeout: action.timeout()); |
149 | } else { |
150 | // Done |
151 | actionPerformedSlot(action: action.name(), reply: ActionReply::SuccessReply()); |
152 | } |
153 | } else { |
154 | // Abort if authorization fails |
155 | switch (s) { |
156 | case Action::DeniedStatus: |
157 | actionPerformedSlot(action: action.name(), reply: ActionReply::AuthorizationDeniedReply()); |
158 | break; |
159 | case Action::InvalidStatus: |
160 | actionPerformedSlot(action: action.name(), reply: ActionReply::InvalidActionReply()); |
161 | break; |
162 | case Action::UserCancelledStatus: |
163 | actionPerformedSlot(action: action.name(), reply: ActionReply::UserCancelledReply()); |
164 | break; |
165 | default: { |
166 | ActionReply r(ActionReply::BackendError); |
167 | r.setErrorDescription(tr(sourceText: "Unknown status for the authentication procedure" )); |
168 | actionPerformedSlot(action: action.name(), reply: r); |
169 | break; |
170 | } |
171 | } |
172 | } |
173 | } else if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::AuthorizeFromHelperCapability) { |
174 | if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::PreAuthActionCapability) { |
175 | BackendsManager::authBackend()->preAuthAction(action: action.name(), parent: parentWindow(action)); |
176 | } |
177 | if (!action.hasHelper()) { |
178 | ActionReply r(ActionReply::InvalidActionReply()); |
179 | r.setErrorDescription(tr(sourceText: "The current backend only allows helper authorization, but this action does not have a helper." )); |
180 | actionPerformedSlot(action: action.name(), reply: r); |
181 | return; |
182 | } |
183 | BackendsManager::helperProxy()->executeAction(action: action.name(), helperID: action.helperId(), details: action.detailsV2(), arguments: action.arguments(), timeout: action.timeout()); |
184 | } else { |
185 | // There's something totally wrong here |
186 | ActionReply r(ActionReply::BackendError); |
187 | r.setErrorDescription(tr(sourceText: "The backend does not specify how to authorize" )); |
188 | actionPerformedSlot(action: action.name(), reply: r); |
189 | } |
190 | } |
191 | |
192 | void ExecuteJobPrivate::doAuthorizeAction() |
193 | { |
194 | // Check the status first |
195 | Action::AuthStatus s = action.status(); |
196 | if (s == Action::AuthRequiredStatus) { |
197 | // Let's check what to do |
198 | if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::AuthorizeFromClientCapability) { |
199 | // In this case we can actually try an authorization |
200 | if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::PreAuthActionCapability) { |
201 | BackendsManager::authBackend()->preAuthAction(action: action.name(), parent: parentWindow(action)); |
202 | } |
203 | |
204 | s = BackendsManager::authBackend()->authorizeAction(action: action.name()); |
205 | } else if (BackendsManager::authBackend()->capabilities() & KAuth::AuthBackend::AuthorizeFromHelperCapability) { |
206 | // In this case, just throw out success, as the auth will take place later |
207 | s = Action::AuthorizedStatus; |
208 | } else { |
209 | // This should never, never happen |
210 | ActionReply r(ActionReply::BackendError); |
211 | r.setErrorDescription(tr(sourceText: "The backend does not specify how to authorize" )); |
212 | actionPerformedSlot(action: action.name(), reply: r); |
213 | } |
214 | } |
215 | |
216 | // Return based on the current status |
217 | if (s == Action::AuthorizedStatus) { |
218 | actionPerformedSlot(action: action.name(), reply: ActionReply::SuccessReply()); |
219 | } else { |
220 | actionPerformedSlot(action: action.name(), reply: ActionReply::AuthorizationDeniedReply()); |
221 | } |
222 | } |
223 | |
224 | void ExecuteJobPrivate::actionPerformedSlot(const QString &taction, const ActionReply &reply) |
225 | { |
226 | if (taction == action.name()) { |
227 | if (reply.failed()) { |
228 | q->setError(reply.errorCode()); |
229 | q->setErrorText(reply.errorDescription()); |
230 | } else { |
231 | data = reply.data(); |
232 | } |
233 | |
234 | q->emitResult(); |
235 | } |
236 | } |
237 | |
238 | void ExecuteJobPrivate::progressStepSlot(const QString &taction, int i) |
239 | { |
240 | if (taction == action.name()) { |
241 | q->setPercent(i); |
242 | } |
243 | } |
244 | |
245 | void ExecuteJobPrivate::progressStepSlot(const QString &taction, const QVariantMap &data) |
246 | { |
247 | if (taction == action.name()) { |
248 | Q_EMIT q->newData(data); |
249 | } |
250 | } |
251 | |
252 | void ExecuteJobPrivate::statusChangedSlot(const QString &taction, Action::AuthStatus status) |
253 | { |
254 | if (taction == action.name()) { |
255 | Q_EMIT q->statusChanged(status); |
256 | } |
257 | } |
258 | |
259 | } // namespace Auth |
260 | |
261 | #include "moc_executejob.cpp" |
262 | |