File indexing completed on 2024-12-01 03:38:30
0001 /* 0002 SPDX-FileCopyrightText: 2008 Nicola Gigante <nicola.gigante@gmail.com> 0003 SPDX-FileCopyrightText: 2009-2012 Dario Freddi <drf@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #ifndef KAUTH_ACTION_REPLY_H 0009 #define KAUTH_ACTION_REPLY_H 0010 0011 #include "kauthcore_export.h" 0012 0013 #include <QDataStream> 0014 #include <QMap> 0015 #include <QSharedDataPointer> 0016 #include <QString> 0017 #include <QVariant> 0018 0019 /** 0020 @namespace KAuth 0021 0022 @section kauth_intro Introduction 0023 0024 The KDE Authorization API allows developers to write desktop applications that 0025 run high-privileged tasks in an easy, secure and cross-platform way. 0026 Previously, if an application had to do administrative tasks, it had to be run 0027 as root, using mechanisms such as sudo or graphical equivalents, or by setting 0028 the executable's setuid bit. This approach has some drawbacks. For example, the 0029 whole application code, including GUI handling and network communication, had 0030 to be done as root. More code that runs as root means more possible security 0031 holes. 0032 0033 The solution is the caller/helper pattern. With this pattern, the privileged 0034 code is isolated in a small helper tool that runs as root. This tool includes 0035 only the few lines of code that actually need to be run with privileges, not 0036 the whole application logic. All the other parts of the application are run as 0037 a normal user, and the helper tool is called when needed, using a secure 0038 mechanism that ensures that the user is authorized to do so. This pattern is 0039 not very easy to implement, because the developer has to deal with a lot of 0040 details about how to authorize the user, how to call the helper with the right 0041 privileges, how to exchange data with the helper, etc.. This is where the new 0042 KDE Authorization API becomes useful. Thanks to this new library, every 0043 developer can implement the caller/helper pattern to write application that 0044 require high privileges, with a few lines of code in an easy, secure and 0045 cross-platform way. 0046 0047 Not only: the library can also be used to lock down some actions in your 0048 application without using a helper but just checking for authorization and 0049 verifying if the user is allowed to perform it. 0050 0051 The KDE Authorization library uses different backends depending on the system 0052 where it's built. As far as the user authorization is concerned, it currently 0053 uses polkit-1 on linux and Authorization Services on Mac OSX, and a Windows 0054 backend will eventually be written, too. At the communication layer, the 0055 library uses D-Bus on every supported platform. 0056 0057 0058 @section kauth_concepts Concepts 0059 0060 There are a few concepts to understand when using the library. Much of those 0061 are carried from underlying APIs such as polkit-1, so if you know something 0062 about them there shouldn't be problems. 0063 0064 An <i>action</i> is a single task that needs to be done by the application. You 0065 refer to an action using an action identifier, which is a string in reverse 0066 domain name syntax (to avoid duplicates). For example, if the date/time control 0067 center module needs to change the date, it would need an action like 0068 "org.kde.datatime.change". If your application has to perform more than one 0069 privileged task, you should configure more than one action. This allows system 0070 administrators to fine tune the policies that allow users to perform your 0071 actions. 0072 0073 The <i>authorization</i> is the process that is executed to decide if a user 0074 can perform an action or not. In order to execute the helper as root, the user 0075 has to be authorized. For example, on linux, che policykit backend will look at 0076 the policykit policy database to see what requirements the user has to meet in 0077 order to execute the action you requested. The policy set for that action could 0078 allow or deny that user, or could say the user has to authenticate in order to 0079 gain the authorization. 0080 0081 The <i>authentication</i> is the process that allows the system to know that 0082 the person is in front of the console is who he says to be. If an action can be 0083 allowed or not depending on the user's identity, it has to be proved by 0084 entering a password or any other identification data the system requires. 0085 0086 A typical session with the authorization API is like this: 0087 - The user want to perform some privileged task 0088 - The application asks the system if the user is authorized. 0089 - The system asks the user to authenticate, if needed, and reply the application. 0090 - The application uses some system-provided mechanism to execute the helper's 0091 code as the root user. Previously, you had to set the setuid bit to do this, 0092 but we have something cool called 0093 "D-Bus activation" that doesn't require the setuid bit and is much more flexible. 0094 - The helper code, immediately after starting, checks if the caller is 0095 authorized to do what it asks. If not the helper immediately exits! 0096 - If the caller is authorized, the helper executes the task and exits. 0097 - The application receives data back from the helper. 0098 0099 All these steps are managed by the library. Following sections will focus on 0100 how to write the helper to implement your actions and how to call the helper 0101 from the application. 0102 0103 @section kauth_helper Writing the helper tool 0104 0105 The first thing you need to do before writing anything is to decide what 0106 actions you need to implement. Every action needs to be identified by a string 0107 in the reverse domain name syntax. This helps to avoid duplicates. An example 0108 of action id is "org.kde.datetime.change" or "org.kde.ksysguard.killprocess". 0109 Action names can only contain lowercase letters and dots (not as the first or 0110 last char). You also need an identifier for your helper. An application using 0111 the KDE auth api can implement and use more than one helper, implementing 0112 different actions. An helper is uniquely identified in the system context with 0113 a string. It, again, is in reverse domain name syntax to avoid duplicates. A 0114 common approach is to call the helper like the common prefix of your action 0115 names. For example, the Date/Time kcm module could use a helper called 0116 "org.kde.datetime", to perform actions like "org.kde.datetime.changedate" and 0117 "org.kde.datetime.changetime". This naming convention simplifies the 0118 implementation of the helper. 0119 0120 From the code point of view, the helper is implemented as a QObject subclass. 0121 Every action is implemented by a public slot. In the example/ directory in the 0122 source code tree you find a complete example. Let's look at that. The 0123 helper.h file declares the class that implements the helper. It looks like: 0124 0125 @snippet helper.cpp helper_declaration 0126 0127 The slot names are the last part of the action name, without the helper's ID if 0128 it's a prefix, with all the dots replaced by underscores. In this case, the 0129 helper ID is "org.kde.kf6auth.example", so those three slots implement the 0130 actions "org.kde.kf6auth.example.read", "org.kde.kf6auth.example.write" and 0131 "org.kde.kf6auth.example.longaction". The helper ID doesn't have to appear at 0132 the beginning of the action name, but it's good practice. If you want to extend 0133 MyHelper to implement also a different action like 0134 "org.kde.datetime.changetime", since the helper ID doesn't match you'll have to 0135 implement a slot called org_kde_datetime_changetime(). 0136 0137 The slot's signature is fixed: the return type is ActionReply, a class that 0138 allows you to return results, error codes and custom data to the application 0139 when your action has finished to run. 0140 0141 Let's look at the read action implementation. Its purpose is to read files: 0142 0143 @snippet helper.cpp helper_read_action 0144 0145 First, the code creates a default reply object. The default constructor creates 0146 a reply that reports success. Then it gets the filename parameter from the 0147 argument QVariantMap, that has previously been set by the application, before 0148 calling the helper. If it fails to open the file, it creates an ActionReply 0149 object that notifies that some error has happened in the helper, then set the 0150 error code to that returned by QFile and returns. If there is no error, it 0151 reads the file. The contents are added to the reply. 0152 0153 Because this class will be compiled into a standalone executable, we need a 0154 main() function and some code to initialize everything: you don't have to write 0155 it. Instead, you use the KAUTH_HELPER_MAIN() macro that will take care of 0156 everything. It's used like this: 0157 0158 @snippet helper.cpp helper_main 0159 0160 The first parameter is the string containing the helper identifier. Please note 0161 that you need to use this same string in the application's code to tell the 0162 library which helper to call, so please stay away from typos, because we don't 0163 have any way to detect them. The second parameter is the name of the helper's 0164 class. Your helper, if complex, can be composed of a lot of source files, but 0165 the important thing is to include this macro in at least one of them. 0166 0167 To build the helper, KDE macros provide a function named 0168 kauth_install_helper_files(). Use it in your cmake file like this: 0169 0170 @code 0171 add_executable(<helper_target> your sources...) 0172 target_link_libraries(<helper_target> your libraries...) 0173 install(TARGETS <helper_target> DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) 0174 0175 kauth_install_helper_files(<helper_target> <helper_id> <user>) 0176 @endcode 0177 0178 As locale is not inherited, the auth helper will have the text codec explicitly set 0179 to use UTF-8. 0180 0181 The first argument is the cmake target name for the helper executable, which 0182 you have to build and install separately. Make sure to INSTALL THE HELPER IN 0183 @c ${KAUTH_HELPER_INSTALL_DIR}, otherwise @c kauth_install_helper_files will not work. The 0184 second argument is the helper id. Please be sure to don't misspell it, and to 0185 not quote it. The user parameter is the user that the helper has to be run as. 0186 It usually is root, but some actions could require less strict permissions, so 0187 you should use the right user where possible (for example the user apache if 0188 you have to mess with apache settings). Note that the target created by this 0189 macro already links to libkauth and QtCore. 0190 0191 @section kauth_actions Action registration 0192 0193 To be able to authorize the actions, they have to be added to the policy 0194 database. To do this in a cross-platform way, we provide a cmake macro. It 0195 looks like: 0196 @code 0197 kauth_install_actions(<helper_id> <actions definition file>) 0198 @endcode 0199 0200 The action definition file describes which actions are implemented by your code 0201 and which default security options they should have. It is a common text file 0202 in ini format, with one section for each action and some parameters. The 0203 definition for the read action is: 0204 0205 @verbatim 0206 [org.kde.kf6auth.example.read] 0207 Name=Read action 0208 Description=Read action description 0209 Policy=auth_admin 0210 Persistence=session 0211 @endverbatim 0212 0213 The name parameter is a text describing the action for <i>who reads the 0214 file</i>. The description parameter is the message shown to the user in the 0215 authentication dialog. It should be a finite phrase. The policy attribute 0216 specify the default rule that the user must satisfy to be authorized. Possible 0217 values are: 0218 - yes: the action should be always allowed 0219 - no: the action should be always denied 0220 - auth_self: the user should authenticate as itself 0221 - auth_admin: the user should authenticate as an administrator user 0222 0223 The persistence attribute is optional. It says how long an authorization should 0224 be retained for that action. The values could be: 0225 - session: the authorization persists until the user logs-out 0226 - always: the authorization will persist indefinitely 0227 0228 If this attribute is missing, the authorization will be queried every time. 0229 0230 @note Only the PolicyKit and polkit-1 backends use this attribute. 0231 @warning With the polkit-1 backend, 'session' and 'always' have the same meaning. 0232 They just make the authorization persists for a few minutes. 0233 0234 @section kauth_app Calling the helper from the application 0235 0236 Once the helper is ready, we need to call it from the main application. 0237 In examples/client.cpp you can see how this is done. To create a reference to 0238 an action, an object of type Action has to be created. Every Action object 0239 refers to an action by its action id. Two objects with the same action id will 0240 act on the same action. With an Action object, you can authorize and execute 0241 the action. To execute an action you need to retrieve an ExecuteJob, which is 0242 a standard KJob that you can run synchronously or asynchronously. 0243 See the KJob documentation (from KCoreAddons) for more details. 0244 0245 The piece of code that calls the action of the previous example is: 0246 0247 @snippet client.cpp client_how_to_call_helper 0248 0249 First of all, it creates the action object specifying the action id. Then it 0250 loads the filename (we want to read a forbidden file) into the arguments() 0251 QVariantMap, which will be directly passed to the helper in the read() slot's 0252 parameter. This example code uses a synchronous call to execute the action and 0253 retrieve the reply. If the reply succeeded, the reply data is retrieved from 0254 the returned QVariantMap object. Please note that you have 0255 to explicitly set the helper ID to the action: this is done for added safety, 0256 to prevent the caller from accidentally invoking a helper, and also because 0257 KAuth actions may be used without a helper attached (the default). 0258 0259 Please note that if your application is calling the helper multiple times it 0260 must do so from the same thread. 0261 0262 @section kauth_async Asynchronous calls, data reporting, and action termination 0263 0264 For a more advanced example, we look at the action 0265 "org.kde.kf6auth.example.longaction" in the example helper. This is an action 0266 that takes a long time to execute, so we need some features: 0267 - The helper needs to regularly send data to the application, to inform about 0268 the execution status. 0269 - The application needs to be able to stop the action execution if the user 0270 stops it or close the application. 0271 The example code follows: 0272 0273 @snippet helper.cpp helper_longaction 0274 0275 In this example, the action is only waiting a "long" time using a loop, but we 0276 can see some interesting line. The progress status is sent to the application 0277 using the HelperSupport::progressStep(int) and 0278 HelperSupport::progressStep(const QVariantMap &) methods. 0279 When those methods are called, the HelperProxy associated with this action 0280 will emit the HelperProxy::progressStep(const QString &, int) and 0281 HelperProxy::progressStepData(const QString &, const QVariantMap &) signals, 0282 respectively, reporting back the data to the application. 0283 The method that takes an integer argument is the one used here. 0284 Its meaning is application dependent, so you can use it as a sort of 0285 percentage. If you want to report custom data back to the application, you 0286 can use the other method that takes a QVariantMap object which is directly 0287 passed to the app. 0288 0289 In this example code, the loop exits when the HelperSupport::isStopped() 0290 returns true. This happens when the application calls the HelperProxy::stopAction() 0291 method on the corresponding action object. 0292 The stopAction() method, this way, asks the helper to 0293 stop the action execution. It's up to the helper to obbey to this request, and 0294 if it does so, it should return from the slot, _not_ exit. 0295 0296 @section kauth_other Other features 0297 0298 It doesn't happen very frequently that you code something that doesn't require 0299 some debugging, and you'll need some tool, even a basic one, to debug your 0300 helper code as well. For this reason, the KDE Authorization library provides a 0301 message handler for the Qt debugging system. This means that every call to 0302 qDebug() & co. will be reported to the application, and printed using the same 0303 qt debugging system, with the same debug level. If, in the helper code, you 0304 write something like: 0305 @code 0306 qDebug() << "I'm in the helper"; 0307 @endcode 0308 You'll see something like this in the <i>application</i>'s output: 0309 0310 @verbatim 0311 Debug message from the helper: I'm in the helper 0312 @endverbatim 0313 0314 Remember that the debug level is preserved, so if you use qFatal() you won't 0315 only abort the helper (which isn't suggested anyway), but also the application. 0316 0317 */ 0318 namespace KAuth 0319 { 0320 class ActionReplyData; 0321 0322 /** 0323 * @class ActionReply actionreply.h <KAuth/ActionReply> 0324 * 0325 * @brief Class that encapsulates a reply coming from the helper after executing 0326 * an action 0327 * 0328 * Helper applications will return this to describe the result of the action. 0329 * 0330 * Callers should access the reply though the KAuth::ExecuteJob job. 0331 * 0332 * @since 4.4 0333 */ 0334 class KAUTHCORE_EXPORT ActionReply 0335 { 0336 public: 0337 /** 0338 * Enumeration of the different kinds of replies. 0339 */ 0340 enum Type { 0341 KAuthErrorType, ///< An error reply generated by the library itself. 0342 HelperErrorType, ///< An error reply generated by the helper. 0343 SuccessType, ///< The action has been completed successfully 0344 }; 0345 0346 static const ActionReply SuccessReply(); ///< An empty successful reply. Same as using the default constructor 0347 static const ActionReply HelperErrorReply(); ///< An empty reply with type() == HelperError and errorCode() == -1 0348 static const ActionReply HelperErrorReply(int error); ///< An empty reply with type() == HelperError and error is set to the passed value 0349 0350 static const ActionReply NoResponderReply(); ///< errorCode() == NoResponder 0351 static const ActionReply NoSuchActionReply(); ///< errorCode() == NoSuchAction 0352 static const ActionReply InvalidActionReply(); ///< errorCode() == InvalidAction 0353 static const ActionReply AuthorizationDeniedReply(); ///< errorCode() == AuthorizationDenied 0354 static const ActionReply UserCancelledReply(); ///< errorCode() == UserCancelled 0355 static const ActionReply HelperBusyReply(); ///< errorCode() == HelperBusy 0356 static const ActionReply AlreadyStartedReply(); ///< errorCode() == AlreadyStartedError 0357 static const ActionReply DBusErrorReply(); ///< errorCode() == DBusError 0358 0359 /** 0360 * The enumeration of the possible values of errorCode() when type() is ActionReply::KAuthError 0361 */ 0362 enum Error { 0363 NoError = 0, ///< No error. 0364 NoResponderError, ///< The helper responder object hasn't been set. This shouldn't happen if you use the KAUTH_HELPER macro in the helper source 0365 NoSuchActionError, ///< The action you tried to execute doesn't exist. 0366 InvalidActionError, ///< You tried to execute an invalid action object 0367 AuthorizationDeniedError, ///< You don't have the authorization to execute the action 0368 UserCancelledError, ///< Action execution has been cancelled by the user 0369 HelperBusyError, ///< The helper is busy executing another action (or group of actions). Try later 0370 AlreadyStartedError, ///< The action was already started and is currently running 0371 DBusError, ///< An error from D-Bus occurred 0372 BackendError, ///< The underlying backend reported an error 0373 }; 0374 0375 /// Default constructor. Sets type() to Success and errorCode() to zero. 0376 ActionReply(); 0377 0378 /** 0379 * @brief Constructor to directly set the type. 0380 * 0381 * This constructor directly sets the reply type. You shouldn't need to 0382 * directly call this constructor, because you can use the more convenient 0383 * predefined replies constants. You also shouldn't create a reply with 0384 * the KAuthError type because it's reserved for errors coming from the 0385 * library. 0386 * 0387 * @param type The type of the new reply 0388 */ 0389 ActionReply(Type type); 0390 0391 /** 0392 * @brief Constructor that creates a KAuthError reply with a specified error code. 0393 * Do not use outside the library. 0394 * 0395 * This constructor is for internal use only, since it creates a reply 0396 * with KAuthError type, which is reserved for errors coming from the library. 0397 * 0398 * @param errorCode The error code of the new reply 0399 */ 0400 ActionReply(int errorCode); 0401 0402 /// Copy constructor 0403 ActionReply(const ActionReply &reply); 0404 0405 /// Virtual destructor 0406 virtual ~ActionReply(); 0407 0408 /** 0409 * @brief Sets the custom data to send back to the application 0410 * 0411 * In the helper's code you can use this function to set an QVariantMap 0412 * with custom data that will be sent back to the application. 0413 * 0414 * @param data The new QVariantMap object. 0415 */ 0416 void setData(const QVariantMap &data); 0417 0418 /** 0419 * @brief Returns the custom data coming from the helper. 0420 * 0421 * This method is used to get the object that contains the custom 0422 * data coming from the helper. In the helper's code, you can set it 0423 * using setData() or the convenience method addData(). 0424 * 0425 * @return The data coming from (or that will be sent by) the helper 0426 */ 0427 QVariantMap data() const; 0428 0429 /** 0430 * @brief Convenience method to add some data to the reply. 0431 * 0432 * This method adds the pair @c key/value to the QVariantMap used to 0433 * report back custom data to the application. 0434 * 0435 * Use this method if you don't want to create a new QVariantMap only to 0436 * add a new entry. 0437 * 0438 * @param key The new entry's key 0439 * @param value The value of the new entry 0440 */ 0441 void addData(const QString &key, const QVariant &value); 0442 0443 /// Returns the reply's type 0444 Type type() const; 0445 0446 /** 0447 * @brief Sets the reply type 0448 * 0449 * Every time you create an action reply, you implicitly set a type. 0450 * Default constructed replies or ActionReply::SuccessReply have 0451 * type() == Success. 0452 * ActionReply::HelperErrorReply has type() == HelperError. 0453 * Predefined error replies have type() == KAuthError. 0454 * 0455 * This means you rarely need to change the type after the creation, 0456 * but if you need to, don't set it to KAuthError, because it's reserved 0457 * for errors coming from the library. 0458 * 0459 * @param type The new reply type 0460 */ 0461 void setType(Type type); 0462 0463 /// Returns true if type() == Success 0464 bool succeeded() const; 0465 0466 /// Returns true if type() != Success 0467 bool failed() const; 0468 0469 /** 0470 * @brief Returns the error code of an error reply 0471 * 0472 * The error code returned is one of the values in the ActionReply::Error 0473 * enumeration if type() == KAuthError, or is totally application-dependent if 0474 * type() == HelperError. It also should be zero for successful replies. 0475 * 0476 * @return The reply error code 0477 */ 0478 int error() const; 0479 0480 /** 0481 * @brief Returns the error code of an error reply 0482 * 0483 * The error code returned is one of the values in the ActionReply::Error 0484 * enumeration if type() == KAuthError. 0485 * Result is only valid if the type() == HelperError 0486 * 0487 * @return The reply error code 0488 */ 0489 Error errorCode() const; 0490 0491 /** 0492 * @brief Sets the error code of an error reply 0493 * 0494 * If you're setting the error code in the helper because 0495 * you need to return an error to the application, please make sure 0496 * you already have set the type to HelperError, either by calling 0497 * setType() or by creating the reply in the right way. 0498 * 0499 * If the type is Success when you call this method, it will become KAuthError 0500 * 0501 * @param error The new reply error code 0502 */ 0503 void setError(int error); 0504 0505 /** 0506 * @brief Sets the error code of an error reply 0507 * 0508 * @see 0509 * If you're setting the error code in the helper, use setError(int) 0510 * 0511 * If the type is Success when you call this method, it will become KAuthError 0512 * 0513 * @param errorCode The new reply error code 0514 */ 0515 void setErrorCode(Error errorCode); 0516 0517 /** 0518 * @brief Gets a human-readble description of the error, if available 0519 * 0520 * Currently, replies of type KAuthError rarely report an error description. 0521 * This situation could change in the future. 0522 * 0523 * By now, you can use this method for custom errors of type HelperError. 0524 * 0525 * @return The error human-readable description 0526 */ 0527 QString errorDescription() const; 0528 0529 /** 0530 * @brief Sets a human-readble description of the error 0531 * 0532 * Call this method from the helper if you want to send back a description for 0533 * a custom error. Note that this method doesn't affect the errorCode in any way 0534 * 0535 * @param error The new error description 0536 */ 0537 void setErrorDescription(const QString &error); 0538 0539 /** 0540 * @brief Serialize the reply into a QByteArray. 0541 * 0542 * This is a convenience method used internally to sent the reply to a remote peer. 0543 * To recreate the reply, use deserialize() 0544 * 0545 * @return A QByteArray representation of this reply 0546 */ 0547 QByteArray serialized() const; 0548 0549 /** 0550 * @brief Deserialize a reply from a QByteArray 0551 * 0552 * This method returns a reply from a QByteArray obtained from 0553 * the serialized() method. 0554 * 0555 * @param data A QByteArray obtained with serialized() 0556 */ 0557 static ActionReply deserialize(const QByteArray &data); 0558 0559 /// Assignment operator 0560 ActionReply &operator=(const ActionReply &reply); 0561 0562 /** 0563 * @brief Comparison operator 0564 * 0565 * This operator checks if the type and the error code of two replies are the same. 0566 * It <b>doesn't</b> compare the data or the error descriptions, so be careful. 0567 * 0568 * The suggested use is to compare a reply against one of the predefined error replies: 0569 * @code 0570 * if(reply == ActionReply::HelperBusyReply) { 0571 * // Do something... 0572 * } 0573 * @endcode 0574 * 0575 * Note that you can do it also by compare errorCode() with the relative enumeration value. 0576 */ 0577 bool operator==(const ActionReply &reply) const; 0578 0579 /** 0580 * @brief Negated comparison operator 0581 * 0582 * See the operator==() for an important notice. 0583 */ 0584 bool operator!=(const ActionReply &reply) const; 0585 0586 private: 0587 QSharedDataPointer<ActionReplyData> d; 0588 }; 0589 0590 } // namespace Auth 0591 0592 Q_DECLARE_METATYPE(KAuth::ActionReply) 0593 0594 #endif