KAuth

actionreply.h
1 /*
2  SPDX-FileCopyrightText: 2008 Nicola Gigante <[email protected]>
3  SPDX-FileCopyrightText: 2009-2012 Dario Freddi <[email protected]>
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.kf5auth.example", so those three slots implement the
130  actions "org.kde.kf5auth.example.read", "org.kde.kf5auth.example.write" and
131  "org.kde.kf5auth.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.kf5auth.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.kf5auth.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  */
318 namespace KAuth
319 {
320 class 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  */
334 class KAUTHCORE_EXPORT ActionReply
335 {
336 public:
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 
586 private:
588 };
589 
590 } // namespace Auth
591 
592 Q_DECLARE_METATYPE(KAuth::ActionReply)
593 
594 #endif
@ AuthorizationDeniedError
You don't have the authorization to execute the action.
Definition: actionreply.h:367
Error
The enumeration of the possible values of errorCode() when type() is ActionReply::KAuthError.
Definition: actionreply.h:362
@ NoResponderError
The helper responder object hasn't been set. This shouldn't happen if you use the KAUTH_HELPER macro ...
Definition: actionreply.h:364
Class that encapsulates a reply coming from the helper after executing an action.
Definition: actionreply.h:334
@ BackendError
The underlying backend reported an error.
Definition: actionreply.h:372
@ NoSuchActionError
The action you tried to execute doesn't exist.
Definition: actionreply.h:365
@ DBusError
An error from D-Bus occurred.
Definition: actionreply.h:371
Definition: action.cpp:18
@ UserCancelledError
Action execution has been cancelled by the user.
Definition: actionreply.h:368
@ InvalidActionError
You tried to execute an invalid action object.
Definition: actionreply.h:366
@ KAuthErrorType
An error reply generated by the library itself.
Definition: actionreply.h:341
@ SuccessType
The action has been completed successfully.
Definition: actionreply.h:343
@ HelperBusyError
The helper is busy executing another action (or group of actions). Try later.
Definition: actionreply.h:369
@ HelperErrorType
An error reply generated by the helper.
Definition: actionreply.h:342
Type
Enumeration of the different kinds of replies.
Definition: actionreply.h:340
@ AlreadyStartedError
The action was already started and is currently running.
Definition: actionreply.h:370
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Jun 7 2023 04:07:41 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.