KAuth

actionreply.h
1/*
2 SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com>
3 SPDX-FileCopyrightText: 2009-2012 Dario Freddi <drf@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
8#ifndef KAUTH_ACTION_REPLY_H
9#define KAUTH_ACTION_REPLY_H
10
11#include "kauthcore_export.h"
12
13#include <QDataStream>
14#include <QMap>
15#include <QSharedDataPointer>
16#include <QString>
17#include <QVariant>
18
19/**
20 @namespace KAuth
21
22 @section kauth_intro Introduction
23
24 The KDE Authorization API allows developers to write desktop applications that
25 run high-privileged tasks in an easy, secure and cross-platform way.
26 Previously, if an application had to do administrative tasks, it had to be run
27 as root, using mechanisms such as sudo or graphical equivalents, or by setting
28 the executable's setuid bit. This approach has some drawbacks. For example, the
29 whole application code, including GUI handling and network communication, had
30 to be done as root. More code that runs as root means more possible security
31 holes.
32
33 The solution is the caller/helper pattern. With this pattern, the privileged
34 code is isolated in a small helper tool that runs as root. This tool includes
35 only the few lines of code that actually need to be run with privileges, not
36 the whole application logic. All the other parts of the application are run as
37 a normal user, and the helper tool is called when needed, using a secure
38 mechanism that ensures that the user is authorized to do so. This pattern is
39 not very easy to implement, because the developer has to deal with a lot of
40 details about how to authorize the user, how to call the helper with the right
41 privileges, how to exchange data with the helper, etc.. This is where the new
42 KDE Authorization API becomes useful. Thanks to this new library, every
43 developer can implement the caller/helper pattern to write application that
44 require high privileges, with a few lines of code in an easy, secure and
45 cross-platform way.
46
47 Not only: the library can also be used to lock down some actions in your
48 application without using a helper but just checking for authorization and
49 verifying if the user is allowed to perform it.
50
51 The KDE Authorization library uses different backends depending on the system
52 where it's built. As far as the user authorization is concerned, it currently
53 uses polkit-1 on linux and Authorization Services on Mac OSX, and a Windows
54 backend will eventually be written, too. At the communication layer, the
55 library uses D-Bus on every supported platform.
56
57
58 @section kauth_concepts Concepts
59
60 There are a few concepts to understand when using the library. Much of those
61 are carried from underlying APIs such as polkit-1, so if you know something
62 about them there shouldn't be problems.
63
64 An <i>action</i> is a single task that needs to be done by the application. You
65 refer to an action using an action identifier, which is a string in reverse
66 domain name syntax (to avoid duplicates). For example, if the date/time control
67 center module needs to change the date, it would need an action like
68 "org.kde.datatime.change". If your application has to perform more than one
69 privileged task, you should configure more than one action. This allows system
70 administrators to fine tune the policies that allow users to perform your
71 actions.
72
73 The <i>authorization</i> is the process that is executed to decide if a user
74 can perform an action or not. In order to execute the helper as root, the user
75 has to be authorized. For example, on linux, che policykit backend will look at
76 the policykit policy database to see what requirements the user has to meet in
77 order to execute the action you requested. The policy set for that action could
78 allow or deny that user, or could say the user has to authenticate in order to
79 gain the authorization.
80
81 The <i>authentication</i> is the process that allows the system to know that
82 the person is in front of the console is who he says to be. If an action can be
83 allowed or not depending on the user's identity, it has to be proved by
84 entering a password or any other identification data the system requires.
85
86 A typical session with the authorization API is like this:
87 - The user want to perform some privileged task
88 - The application asks the system if the user is authorized.
89 - The system asks the user to authenticate, if needed, and reply the application.
90 - The application uses some system-provided mechanism to execute the helper's
91 code as the root user. Previously, you had to set the setuid bit to do this,
92 but we have something cool called
93 "D-Bus activation" that doesn't require the setuid bit and is much more flexible.
94 - The helper code, immediately after starting, checks if the caller is
95 authorized to do what it asks. If not the helper immediately exits!
96 - If the caller is authorized, the helper executes the task and exits.
97 - The application receives data back from the helper.
98
99 All these steps are managed by the library. Following sections will focus on
100 how to write the helper to implement your actions and how to call the helper
101 from the application.
102
103 @section kauth_helper Writing the helper tool
104
105 The first thing you need to do before writing anything is to decide what
106 actions you need to implement. Every action needs to be identified by a string
107 in the reverse domain name syntax. This helps to avoid duplicates. An example
108 of action id is "org.kde.datetime.change" or "org.kde.ksysguard.killprocess".
109 Action names can only contain lowercase letters and dots (not as the first or
110 last char). You also need an identifier for your helper. An application using
111 the KDE auth api can implement and use more than one helper, implementing
112 different actions. An helper is uniquely identified in the system context with
113 a string. It, again, is in reverse domain name syntax to avoid duplicates. A
114 common approach is to call the helper like the common prefix of your action
115 names. For example, the Date/Time kcm module could use a helper called
116 "org.kde.datetime", to perform actions like "org.kde.datetime.changedate" and
117 "org.kde.datetime.changetime". This naming convention simplifies the
118 implementation of the helper.
119
120 From the code point of view, the helper is implemented as a QObject subclass.
121 Every action is implemented by a public slot. In the example/ directory in the
122 source code tree you find a complete example. Let's look at that. The
123 helper.h file declares the class that implements the helper. It looks like:
124
125 @snippet helper.cpp helper_declaration
126
127 The slot names are the last part of the action name, without the helper's ID if
128 it's a prefix, with all the dots replaced by underscores. In this case, the
129 helper ID is "org.kde.kf6auth.example", so those three slots implement the
130 actions "org.kde.kf6auth.example.read", "org.kde.kf6auth.example.write" and
131 "org.kde.kf6auth.example.longaction". The helper ID doesn't have to appear at
132 the beginning of the action name, but it's good practice. If you want to extend
133 MyHelper to implement also a different action like
134 "org.kde.datetime.changetime", since the helper ID doesn't match you'll have to
135 implement a slot called org_kde_datetime_changetime().
136
137 The slot's signature is fixed: the return type is ActionReply, a class that
138 allows you to return results, error codes and custom data to the application
139 when your action has finished to run.
140
141 Let's look at the read action implementation. Its purpose is to read files:
142
143 @snippet helper.cpp helper_read_action
144
145 First, the code creates a default reply object. The default constructor creates
146 a reply that reports success. Then it gets the filename parameter from the
147 argument QVariantMap, that has previously been set by the application, before
148 calling the helper. If it fails to open the file, it creates an ActionReply
149 object that notifies that some error has happened in the helper, then set the
150 error code to that returned by QFile and returns. If there is no error, it
151 reads the file. The contents are added to the reply.
152
153 Because this class will be compiled into a standalone executable, we need a
154 main() function and some code to initialize everything: you don't have to write
155 it. Instead, you use the KAUTH_HELPER_MAIN() macro that will take care of
156 everything. It's used like this:
157
158 @snippet helper.cpp helper_main
159
160 The first parameter is the string containing the helper identifier. Please note
161 that you need to use this same string in the application's code to tell the
162 library which helper to call, so please stay away from typos, because we don't
163 have any way to detect them. The second parameter is the name of the helper's
164 class. Your helper, if complex, can be composed of a lot of source files, but
165 the important thing is to include this macro in at least one of them.
166
167 To build the helper, KDE macros provide a function named
168 kauth_install_helper_files(). Use it in your cmake file like this:
169
170 @code
171 add_executable(<helper_target> your sources...)
172 target_link_libraries(<helper_target> your libraries...)
173 install(TARGETS <helper_target> DESTINATION ${KAUTH_HELPER_INSTALL_DIR})
174
175 kauth_install_helper_files(<helper_target> <helper_id> <user>)
176 @endcode
177
178 As locale is not inherited, the auth helper will have the text codec explicitly set
179 to use UTF-8.
180
181 The first argument is the cmake target name for the helper executable, which
182 you have to build and install separately. Make sure to INSTALL THE HELPER IN
183 @c ${KAUTH_HELPER_INSTALL_DIR}, otherwise @c kauth_install_helper_files will not work. The
184 second argument is the helper id. Please be sure to don't misspell it, and to
185 not quote it. The user parameter is the user that the helper has to be run as.
186 It usually is root, but some actions could require less strict permissions, so
187 you should use the right user where possible (for example the user apache if
188 you have to mess with apache settings). Note that the target created by this
189 macro already links to libkauth and QtCore.
190
191 @section kauth_actions Action registration
192
193 To be able to authorize the actions, they have to be added to the policy
194 database. To do this in a cross-platform way, we provide a cmake macro. It
195 looks like:
196 @code
197 kauth_install_actions(<helper_id> <actions definition file>)
198 @endcode
199
200 The action definition file describes which actions are implemented by your code
201 and which default security options they should have. It is a common text file
202 in ini format, with one section for each action and some parameters. The
203 definition for the read action is:
204
205 @verbatim
206 [org.kde.kf6auth.example.read]
207 Name=Read action
208 Description=Read action description
209 Policy=auth_admin
210 Persistence=session
211 @endverbatim
212
213 The name parameter is a text describing the action for <i>who reads the
214 file</i>. The description parameter is the message shown to the user in the
215 authentication dialog. It should be a finite phrase. The policy attribute
216 specify the default rule that the user must satisfy to be authorized. Possible
217 values are:
218 - yes: the action should be always allowed
219 - no: the action should be always denied
220 - auth_self: the user should authenticate as itself
221 - auth_admin: the user should authenticate as an administrator user
222
223 The persistence attribute is optional. It says how long an authorization should
224 be retained for that action. The values could be:
225 - session: the authorization persists until the user logs-out
226 - always: the authorization will persist indefinitely
227
228 If this attribute is missing, the authorization will be queried every time.
229
230 @note Only the PolicyKit and polkit-1 backends use this attribute.
231 @warning With the polkit-1 backend, 'session' and 'always' have the same meaning.
232 They just make the authorization persists for a few minutes.
233
234 @section kauth_app Calling the helper from the application
235
236 Once the helper is ready, we need to call it from the main application.
237 In examples/client.cpp you can see how this is done. To create a reference to
238 an action, an object of type Action has to be created. Every Action object
239 refers to an action by its action id. Two objects with the same action id will
240 act on the same action. With an Action object, you can authorize and execute
241 the action. To execute an action you need to retrieve an ExecuteJob, which is
242 a standard KJob that you can run synchronously or asynchronously.
243 See the KJob documentation (from KCoreAddons) for more details.
244
245 The piece of code that calls the action of the previous example is:
246
247 @snippet client.cpp client_how_to_call_helper
248
249 First of all, it creates the action object specifying the action id. Then it
250 loads the filename (we want to read a forbidden file) into the arguments()
251 QVariantMap, which will be directly passed to the helper in the read() slot's
252 parameter. This example code uses a synchronous call to execute the action and
253 retrieve the reply. If the reply succeeded, the reply data is retrieved from
254 the returned QVariantMap object. Please note that you have
255 to explicitly set the helper ID to the action: this is done for added safety,
256 to prevent the caller from accidentally invoking a helper, and also because
257 KAuth actions may be used without a helper attached (the default).
258
259 Please note that if your application is calling the helper multiple times it
260 must do so from the same thread.
261
262 @section kauth_async Asynchronous calls, data reporting, and action termination
263
264 For a more advanced example, we look at the action
265 "org.kde.kf6auth.example.longaction" in the example helper. This is an action
266 that takes a long time to execute, so we need some features:
267 - The helper needs to regularly send data to the application, to inform about
268 the execution status.
269 - The application needs to be able to stop the action execution if the user
270 stops it or close the application.
271 The example code follows:
272
273 @snippet helper.cpp helper_longaction
274
275 In this example, the action is only waiting a "long" time using a loop, but we
276 can see some interesting line. The progress status is sent to the application
277 using the HelperSupport::progressStep(int) and
278 HelperSupport::progressStep(const QVariantMap &) methods.
279 When those methods are called, the HelperProxy associated with this action
280 will emit the HelperProxy::progressStep(const QString &, int) and
281 HelperProxy::progressStepData(const QString &, const QVariantMap &) signals,
282 respectively, reporting back the data to the application.
283 The method that takes an integer argument is the one used here.
284 Its meaning is application dependent, so you can use it as a sort of
285 percentage. If you want to report custom data back to the application, you
286 can use the other method that takes a QVariantMap object which is directly
287 passed to the app.
288
289 In this example code, the loop exits when the HelperSupport::isStopped()
290 returns true. This happens when the application calls the HelperProxy::stopAction()
291 method on the corresponding action object.
292 The stopAction() method, this way, asks the helper to
293 stop the action execution. It's up to the helper to obbey to this request, and
294 if it does so, it should return from the slot, _not_ exit.
295
296 @section kauth_other Other features
297
298 It doesn't happen very frequently that you code something that doesn't require
299 some debugging, and you'll need some tool, even a basic one, to debug your
300 helper code as well. For this reason, the KDE Authorization library provides a
301 message handler for the Qt debugging system. This means that every call to
302 qDebug() & co. will be reported to the application, and printed using the same
303 qt debugging system, with the same debug level. If, in the helper code, you
304 write something like:
305 @code
306 qDebug() << "I'm in the helper";
307 @endcode
308 You'll see something like this in the <i>application</i>'s output:
309
310 @verbatim
311 Debug message from the helper: I'm in the helper
312 @endverbatim
313
314 Remember that the debug level is preserved, so if you use qFatal() you won't
315 only abort the helper (which isn't suggested anyway), but also the application.
316
317 */
318namespace KAuth
319{
320class ActionReplyData;
321
322/**
323 * @class ActionReply actionreply.h <KAuth/ActionReply>
324 *
325 * @brief Class that encapsulates a reply coming from the helper after executing
326 * an action
327 *
328 * Helper applications will return this to describe the result of the action.
329 *
330 * Callers should access the reply though the KAuth::ExecuteJob job.
331 *
332 * @since 4.4
333 */
334class KAUTHCORE_EXPORT ActionReply
335{
336public:
337 /**
338 * Enumeration of the different kinds of replies.
339 */
340 enum Type {
341 KAuthErrorType, ///< An error reply generated by the library itself.
342 HelperErrorType, ///< An error reply generated by the helper.
343 SuccessType, ///< The action has been completed successfully
344 };
345
346 static const ActionReply SuccessReply(); ///< An empty successful reply. Same as using the default constructor
347 static const ActionReply HelperErrorReply(); ///< An empty reply with type() == HelperError and errorCode() == -1
348 static const ActionReply HelperErrorReply(int error); ///< An empty reply with type() == HelperError and error is set to the passed value
349
350 static const ActionReply NoResponderReply(); ///< errorCode() == NoResponder
351 static const ActionReply NoSuchActionReply(); ///< errorCode() == NoSuchAction
352 static const ActionReply InvalidActionReply(); ///< errorCode() == InvalidAction
353 static const ActionReply AuthorizationDeniedReply(); ///< errorCode() == AuthorizationDenied
354 static const ActionReply UserCancelledReply(); ///< errorCode() == UserCancelled
355 static const ActionReply HelperBusyReply(); ///< errorCode() == HelperBusy
356 static const ActionReply AlreadyStartedReply(); ///< errorCode() == AlreadyStartedError
357 static const ActionReply DBusErrorReply(); ///< errorCode() == DBusError
358
359 /**
360 * The enumeration of the possible values of errorCode() when type() is ActionReply::KAuthError
361 */
362 enum Error {
363 NoError = 0, ///< No error.
364 NoResponderError, ///< The helper responder object hasn't been set. This shouldn't happen if you use the KAUTH_HELPER macro in the helper source
365 NoSuchActionError, ///< The action you tried to execute doesn't exist.
366 InvalidActionError, ///< You tried to execute an invalid action object
367 AuthorizationDeniedError, ///< You don't have the authorization to execute the action
368 UserCancelledError, ///< Action execution has been cancelled by the user
369 HelperBusyError, ///< The helper is busy executing another action (or group of actions). Try later
370 AlreadyStartedError, ///< The action was already started and is currently running
371 DBusError, ///< An error from D-Bus occurred
372 BackendError, ///< The underlying backend reported an error
373 };
374
375 /// Default constructor. Sets type() to Success and errorCode() to zero.
376 ActionReply();
377
378 /**
379 * @brief Constructor to directly set the type.
380 *
381 * This constructor directly sets the reply type. You shouldn't need to
382 * directly call this constructor, because you can use the more convenient
383 * predefined replies constants. You also shouldn't create a reply with
384 * the KAuthError type because it's reserved for errors coming from the
385 * library.
386 *
387 * @param type The type of the new reply
388 */
389 ActionReply(Type type);
390
391 /**
392 * @brief Constructor that creates a KAuthError reply with a specified error code.
393 * Do not use outside the library.
394 *
395 * This constructor is for internal use only, since it creates a reply
396 * with KAuthError type, which is reserved for errors coming from the library.
397 *
398 * @param errorCode The error code of the new reply
399 */
400 ActionReply(int errorCode);
401
402 /// Copy constructor
403 ActionReply(const ActionReply &reply);
404
405 /// Virtual destructor
406 virtual ~ActionReply();
407
408 /**
409 * @brief Sets the custom data to send back to the application
410 *
411 * In the helper's code you can use this function to set an QVariantMap
412 * with custom data that will be sent back to the application.
413 *
414 * @param data The new QVariantMap object.
415 */
416 void setData(const QVariantMap &data);
417
418 /**
419 * @brief Returns the custom data coming from the helper.
420 *
421 * This method is used to get the object that contains the custom
422 * data coming from the helper. In the helper's code, you can set it
423 * using setData() or the convenience method addData().
424 *
425 * @return The data coming from (or that will be sent by) the helper
426 */
427 QVariantMap data() const;
428
429 /**
430 * @brief Convenience method to add some data to the reply.
431 *
432 * This method adds the pair @c key/value to the QVariantMap used to
433 * report back custom data to the application.
434 *
435 * Use this method if you don't want to create a new QVariantMap only to
436 * add a new entry.
437 *
438 * @param key The new entry's key
439 * @param value The value of the new entry
440 */
441 void addData(const QString &key, const QVariant &value);
442
443 /// Returns the reply's type
444 Type type() const;
445
446 /**
447 * @brief Sets the reply type
448 *
449 * Every time you create an action reply, you implicitly set a type.
450 * Default constructed replies or ActionReply::SuccessReply have
451 * type() == Success.
452 * ActionReply::HelperErrorReply has type() == HelperError.
453 * Predefined error replies have type() == KAuthError.
454 *
455 * This means you rarely need to change the type after the creation,
456 * but if you need to, don't set it to KAuthError, because it's reserved
457 * for errors coming from the library.
458 *
459 * @param type The new reply type
460 */
461 void setType(Type type);
462
463 /// Returns true if type() == Success
464 bool succeeded() const;
465
466 /// Returns true if type() != Success
467 bool failed() const;
468
469 /**
470 * @brief Returns the error code of an error reply
471 *
472 * The error code returned is one of the values in the ActionReply::Error
473 * enumeration if type() == KAuthError, or is totally application-dependent if
474 * type() == HelperError. It also should be zero for successful replies.
475 *
476 * @return The reply error code
477 */
478 int error() const;
479
480 /**
481 * @brief Returns the error code of an error reply
482 *
483 * The error code returned is one of the values in the ActionReply::Error
484 * enumeration if type() == KAuthError.
485 * Result is only valid if the type() == HelperError
486 *
487 * @return The reply error code
488 */
489 Error errorCode() const;
490
491 /**
492 * @brief Sets the error code of an error reply
493 *
494 * If you're setting the error code in the helper because
495 * you need to return an error to the application, please make sure
496 * you already have set the type to HelperError, either by calling
497 * setType() or by creating the reply in the right way.
498 *
499 * If the type is Success when you call this method, it will become KAuthError
500 *
501 * @param error The new reply error code
502 */
503 void setError(int error);
504
505 /**
506 * @brief Sets the error code of an error reply
507 *
508 * @see
509 * If you're setting the error code in the helper, use setError(int)
510 *
511 * If the type is Success when you call this method, it will become KAuthError
512 *
513 * @param errorCode The new reply error code
514 */
515 void setErrorCode(Error errorCode);
516
517 /**
518 * @brief Gets a human-readble description of the error, if available
519 *
520 * Currently, replies of type KAuthError rarely report an error description.
521 * This situation could change in the future.
522 *
523 * By now, you can use this method for custom errors of type HelperError.
524 *
525 * @return The error human-readable description
526 */
527 QString errorDescription() const;
528
529 /**
530 * @brief Sets a human-readble description of the error
531 *
532 * Call this method from the helper if you want to send back a description for
533 * a custom error. Note that this method doesn't affect the errorCode in any way
534 *
535 * @param error The new error description
536 */
537 void setErrorDescription(const QString &error);
538
539 /**
540 * @brief Serialize the reply into a QByteArray.
541 *
542 * This is a convenience method used internally to sent the reply to a remote peer.
543 * To recreate the reply, use deserialize()
544 *
545 * @return A QByteArray representation of this reply
546 */
547 QByteArray serialized() const;
548
549 /**
550 * @brief Deserialize a reply from a QByteArray
551 *
552 * This method returns a reply from a QByteArray obtained from
553 * the serialized() method.
554 *
555 * @param data A QByteArray obtained with serialized()
556 */
557 static ActionReply deserialize(const QByteArray &data);
558
559 /// Assignment operator
560 ActionReply &operator=(const ActionReply &reply);
561
562 /**
563 * @brief Comparison operator
564 *
565 * This operator checks if the type and the error code of two replies are the same.
566 * It <b>doesn't</b> compare the data or the error descriptions, so be careful.
567 *
568 * The suggested use is to compare a reply against one of the predefined error replies:
569 * @code
570 * if(reply == ActionReply::HelperBusyReply) {
571 * // Do something...
572 * }
573 * @endcode
574 *
575 * Note that you can do it also by compare errorCode() with the relative enumeration value.
576 */
577 bool operator==(const ActionReply &reply) const;
578
579 /**
580 * @brief Negated comparison operator
581 *
582 * See the operator==() for an important notice.
583 */
584 bool operator!=(const ActionReply &reply) const;
585
586private:
588};
589
590} // namespace Auth
591
592Q_DECLARE_METATYPE(KAuth::ActionReply)
593
594#endif
Class that encapsulates a reply coming from the helper after executing an action.
Error
The enumeration of the possible values of errorCode() when type() is ActionReply::KAuthError.
@ DBusError
An error from D-Bus occurred.
@ AuthorizationDeniedError
You don't have the authorization to execute the action.
@ InvalidActionError
You tried to execute an invalid action object.
@ NoResponderError
The helper responder object hasn't been set. This shouldn't happen if you use the KAUTH_HELPER macro ...
@ BackendError
The underlying backend reported an error.
@ HelperBusyError
The helper is busy executing another action (or group of actions). Try later.
@ NoSuchActionError
The action you tried to execute doesn't exist.
@ UserCancelledError
Action execution has been cancelled by the user.
@ AlreadyStartedError
The action was already started and is currently running.
Type
Enumeration of the different kinds of replies.
@ KAuthErrorType
An error reply generated by the library itself.
@ HelperErrorType
An error reply generated by the helper.
@ SuccessType
The action has been completed successfully.
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:10 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.