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