1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qpermissions.h"
5#include "qpermissions_p.h"
6#include "qhashfunctions.h"
7
8#include <QtCore/qshareddata.h>
9#include <QtCore/qdebug.h>
10
11QT_BEGIN_NAMESPACE
12
13Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg);
14
15/*!
16 \page permissions.html
17 \title Application Permissions
18 \brief Managing application permissions
19
20 Many features of today's devices and operating systems can have
21 significant privacy, security, and performance implications if
22 misused. It's therefore increasingly common for platforms to
23 require explicit consent from the user before accessing these
24 features.
25
26 The Qt permission APIs allow the application to check or request
27 permission for such features in a cross platform manner.
28
29 \section1 Usage
30
31 A feature that commonly requires user consent is access to the
32 microphone of the device. An application for recording voice
33 memos would perhaps look something like this initially:
34
35 \code
36 void VoiceMemoWidget::onRecordingInitiated()
37 {
38 m_microphone->startRecording();
39 }
40 \endcode
41
42 To ensure this application works well on platforms that
43 require user consent for microphone access we would extend
44 it like this:
45
46 \code
47 void VoiceMemoWidget::onRecordingInitiated()
48 {
49 QMicrophonePermission microphonePermission;
50 switch (qApp->checkPermission(microphonePermission)) {
51 case Qt::PermissionStatus::Undetermined:
52 qApp->requestPermission(microphonePermission, this,
53 &VoiceMemoWidget::onRecordingInitiated);
54 return;
55 case Qt::PermissionStatus::Denied:
56 m_permissionInstructionsDialog->show();
57 return;
58 case Qt::PermissionStatus::Granted:
59 m_microphone->startRecording();
60 }
61 }
62 \endcode
63
64 We first check if we already know the status of the microphone permission.
65 If we don't we initiate a permission request to determine the current
66 status, which will potentially ask the user for consent. We connect the
67 result of the request to the slot we're already in, so that we get another
68 chance at evaluating the permission status.
69
70 Once the permission status is known, either because we had been granted or
71 denied permission at an earlier time, or after getting the result back from
72 the request we just initiated, we redirect the user to a dialog explaining
73 why we can not record voice memos at this time (if the permission was denied),
74 or proceed to using the microphone (if permission was granted).
75
76 \note On \macOS and iOS permissions can currently only be requested for
77 GUI applications.
78
79 \section2 Declaring Permissions
80
81 Some platforms require that the permissions you request are declared
82 up front at build time.
83
84 \section3 Apple platforms
85 \target apple-usage-description
86
87 Each permission you request must be accompanied by a so called
88 \e {usage description} string in the application's \c Info.plist
89 file, describing why the application needs to access the given
90 permission. For example:
91
92 \badcode
93 <key>NSMicrophoneUsageDescription</key>
94 <string>The microphone is used to record voice memos.</string>
95 \endcode
96
97 The relevant usage description keys are described in the documentation
98 for each permission type.
99
100 \sa {Information Property List Files}.
101
102 \section3 Android
103 \target android-uses-permission
104
105 Each permission you request must be accompanied by a \c uses-permission
106 entry in the application's \c AndroidManifest.xml file. For example:
107
108 \badcode
109 <manifest ...>
110 <uses-permission android:name="android.permission.RECORD_AUDIO"/>
111 </manifest>
112 \endcode
113
114 The relevant permission names are described in the documentation
115 for each permission type.
116
117 \sa {Qt Creator: Editing Manifest Files}.
118
119 \section1 Available Permissions
120
121 The following permissions types are available:
122
123 \annotatedlist permissions
124
125 \section1 Best Practices
126
127 To ensure the best possible user experience for the end user we recommend
128 adopting the following best practices for managing application permissions:
129
130 \list
131
132 \li Request the minimal set of permissions needed. For example, if you only
133 need access to the microphone, do \e not request camera permission just in case.
134 Use the properties of individual permission types to limit the permission scope
135 even further, for example QContactsPermission::setReadOnly() to request read
136 only access.
137
138 \li Request permissions in response to specific actions by the user. For example,
139 defer requesting microphone permission until the user presses the button to record
140 audio. Associating the permission request to a specific action gives the user a clearer
141 context of why the permission is needed. Do \e not request all needed permission on
142 startup.
143
144 \li Present extra context and explanation if needed. Sometimes the action by the user
145 is not enough context. Consider presenting an explanation-dialog after the user has
146 initiated the action, but before requesting the permission, so the user is aware of
147 what's about to happen when the system permission dialog subsequently pops up.
148
149 \li Be transparent and explicit about why permissions are needed. In explanation
150 dialogs and usage descriptions, be transparent about why the particular permission
151 is needed for your application to provide a specific feature, so users can make
152 informed decisions.
153
154 \li Account for denied permissions. The permissions you request may be denied
155 for various reasons. You should always account for this situation, by gracefully
156 degrading the experience of your application, and presenting clear explanations
157 the user about the situation.
158
159 \li Never request permissions from a library. The request of permissions should
160 be done as close as possible to the user, where the information needed to make
161 good decisions on the points above is available. Libraries can check permissions,
162 to ensure they have the prerequisites for doing their work, but if the permission
163 is undetermined or denied this should be reflected through the library's API,
164 so that the application in turn can request the necessary permissions.
165
166 \endlist
167*/
168
169
170/*!
171 \class QPermission
172 \inmodule QtCore
173 \inheaderfile QPermissions
174 \since 6.5
175 \brief An opaque wrapper of a typed permission.
176
177 The QPermission class is an opaque wrapper of a \l{typed permission},
178 used when checking or requesting permissions. You do not need to construct
179 this type explicitly, as the type is automatically used when checking or
180 requesting permissions:
181
182 \code
183 qApp->checkPermission(QCameraPermission{});
184 \endcode
185
186 When requesting permissions, the given functor will
187 be passed an instance of a QPermission, which can be used
188 to check the result of the request:
189
190 \code
191 qApp->requestPermission(QCameraPermission{}, [](const QPermission &permission) {
192 if (permission.status() == Qt::PermissionStatus:Granted)
193 takePhoto();
194 });
195 \endcode
196
197 To inspect the properties of the original, typed permission,
198 use the \l {QPermission::}{value()} function:
199
200 \code
201 QLocationPermission locationPermission;
202 locationPermission.setAccuracy(QLocationPermission::Precise);
203 qApp->requestPermission(locationPermission, this, &LocationWidget::permissionUpdated);
204 \endcode
205
206 \code
207 void LocationWidget::permissionUpdated(const QPermission &permission)
208 {
209 if (permission.status() != Qt::PermissionStatus:Granted)
210 return;
211 auto locationPermission = permission.value<QLocationPermission>();
212 if (!locationPermission || locationPermission->accuracy() != QLocationPermission::Precise)
213 return;
214 updatePreciseLocation();
215 }
216 \endcode
217
218 \target typed permission
219 \section2 Typed Permissions
220
221 The following permissions are available:
222
223 \annotatedlist permissions
224
225 \sa {Application Permissions}
226*/
227
228/*!
229 \fn template <typename T, if_permission<T>> QPermission::QPermission(const T &type)
230
231 Constructs a permission from the given \l{typed permission} \a type.
232
233 You do not need to construct this type explicitly, as the type is automatically
234 used when checking or requesting permissions.
235
236 This constructor participates in overload resolution only if \c T is one of
237 the \l{typed permission} classes:
238
239 \annotatedlist permissions
240*/
241
242/*!
243 \fn template <typename T, if_permission<T>> std::optional<T> QPermission::value() const
244
245 Returns the \l{typed permission} of type \c T, or \c{std::nullopt} if this
246 QPermission object doesn't contain one.
247
248 Use type() for dynamically choosing which typed permission to request.
249
250 This function participates in overload resolution only if \c T is one of
251 the \l{typed permission} classes:
252
253 \annotatedlist permissions
254*/
255
256/*!
257 \fn Qt::PermissionStatus QPermission::status() const
258 Returns the status of the permission.
259*/
260
261/*!
262 \fn QMetaType QPermission::type() const
263 Returns the type of the permission.
264*/
265
266/*
267 \internal
268*/
269const void *QPermission::data(QMetaType requestedType) const
270{
271 const auto actualType = type();
272 if (requestedType != actualType)
273 return nullptr;
274 return m_data.data();
275}
276
277// check alignof(AlignmentCheck) instead of alignof(void*), in case
278// pointers have different alignment inside structs:
279struct AlignmentCheck { void *p; };
280
281#define QT_PERMISSION_IMPL_COMMON(ClassName) \
282 /* Class##Private is unused until we need it: */ \
283 static_assert(sizeof(ClassName) == sizeof(void*), \
284 "You have added too many members to " #ClassName "::ShortData. " \
285 "Decrease their size or switch to using a d-pointer."); \
286 static_assert(alignof(ClassName) == alignof(AlignmentCheck), \
287 "You have added members to " #ClassName "::ShortData that are overaligned. " \
288 "Decrease their alignment or switch to using a d-pointer."); \
289 ClassName::ClassName(const ClassName &other) noexcept = default; \
290 ClassName::~ClassName() = default; \
291 ClassName &ClassName::operator=(const ClassName &other) noexcept = default; \
292 ClassName::ClassName() \
293 /* impl supplied by caller */
294
295
296/*!
297 \class QCameraPermission
298 \brief Access the camera for taking pictures or videos.
299
300 \section1 Requirements
301
302 \include permissions.qdocinc begin-usage-declarations
303 \row
304 \li Apple
305 \li \l{apple-usage-description}{Usage description}
306 \li \c NSCameraUsageDescription
307 \row
308 \li Android
309 \li \l{android-uses-permission}{\c{uses-permission}}
310 \li \c android.permission.CAMERA
311 \include permissions.qdocinc end-usage-declarations
312
313 \include permissions.qdocinc permission-metadata
314*/
315
316QT_PERMISSION_IMPL_COMMON(QCameraPermission)
317 : u{} // stateless, atm
318{}
319
320/*!
321 \class QMicrophonePermission
322 \brief Access the microphone for monitoring or recording sound.
323
324 \section1 Requirements
325
326 \include permissions.qdocinc begin-usage-declarations
327 \row
328 \li Apple
329 \li \l{apple-usage-description}{Usage description}
330 \li \c NSMicrophoneUsageDescription
331 \row
332 \li Android
333 \li \l{android-uses-permission}{\c{uses-permission}}
334 \li \c android.permission.RECORD_AUDIO
335 \include permissions.qdocinc end-usage-declarations
336
337 \include permissions.qdocinc permission-metadata
338*/
339
340QT_PERMISSION_IMPL_COMMON(QMicrophonePermission)
341 : u{} // stateless, atm
342{}
343
344/*!
345 \class QBluetoothPermission
346 \brief Access Bluetooth peripherals.
347
348 \section1 Requirements
349
350 \include permissions.qdocinc begin-usage-declarations
351 \row
352 \li Apple
353 \li \l{apple-usage-description}{Usage description}
354 \li \c NSBluetoothAlwaysUsageDescription
355 \row
356 \li Android
357 \li \l{android-uses-permission}{\c{uses-permission}}
358 \li Up to Android 11 (API Level < 31):
359 \list
360 \li \c android.permission.BLUETOOTH
361 \li \c android.permission.ACCESS_FINE_LOCATION
362 \endlist
363
364 Starting from Android 12 (API Level >= 31):
365 \list
366 \li \c android.permission.BLUETOOTH_ADVERTISE
367 \li \c android.permission.BLUETOOTH_CONNECT
368 \li \c android.permission.BLUETOOTH_SCAN
369 \li \c android.permission.ACCESS_FINE_LOCATION
370 \endlist
371 \include permissions.qdocinc end-usage-declarations
372
373 \note Currently on Android the \c android.permission.ACCESS_FINE_LOCATION
374 permission is requested together with Bluetooth permissions. This is
375 required for Bluetooth to work properly, unless the application provides a
376 strong assertion in the application manifest that it does not use Bluetooth
377 to derive a physical location. This permission coupling may change in
378 future.
379
380 \include permissions.qdocinc permission-metadata
381*/
382
383QT_PERMISSION_IMPL_COMMON(QBluetoothPermission)
384 : u{ShortData{.mode: CommunicationMode::Default, .reserved: {}}}
385{}
386
387/*!
388 \enum QBluetoothPermission::CommunicationMode
389 \since 6.6
390
391 This enum is used to control the allowed Bluetooth communication modes.
392
393 \value Access Allow this device to access other Bluetooth devices. This
394 includes scanning for nearby devices and connecting to them.
395 \value Advertise Allow other Bluetooth devices to discover this device.
396 \value Default This configuration is used by default.
397
398 \note The fine-grained permissions are currently supported only on
399 Android 12 and newer. On older Android versions, as well as on Apple
400 operating systems, any mode results in full Bluetooth access.
401
402 \note For now the \c Access mode on Android also requests the
403 \l {QLocationPermission::Precise}{precise location} permission.
404 This permission coupling may change in the future.
405*/
406
407/*!
408 \since 6.6
409
410 Sets the allowed Bluetooth communication modes to \a modes.
411
412 \note A default-constructed instance of \l {QBluetoothPermission::}
413 {CommunicationModes} has no sense, so an attempt to set such a mode will
414 raise a \c {qWarning()} and fall back to using the
415 \l {QBluetoothPermission::}{Default} mode.
416*/
417void QBluetoothPermission::setCommunicationModes(CommunicationModes modes)
418{
419 if (modes == CommunicationModes{}) {
420 qCWarning(lcPermissions, "QBluetoothPermission: trying to set an invalid empty mode. "
421 "Falling back to CommunicationMode::Default.");
422 u.data.mode = Default;
423 } else {
424 u.data.mode = static_cast<CommunicationMode>(modes.toInt());
425 }
426}
427
428/*!
429 \since 6.6
430
431 Returns the allowed Bluetooth communication modes.
432*/
433QBluetoothPermission::CommunicationModes QBluetoothPermission::communicationModes() const
434{
435 return u.data.mode;
436}
437
438/*!
439 \class QLocationPermission
440 \brief Access the user's location.
441
442 By default the request is for approximate accuracy,
443 and only while the application is in use. Use
444 setAccuracy() and/or setAvailability() to override
445 the default.
446
447 \section1 Requirements
448
449 \include permissions.qdocinc begin-usage-declarations
450 \row
451 \li \macos
452 \li \l{apple-usage-description}{Usage description}
453 \li \c NSLocationUsageDescription
454 \row
455 \li iOS
456 \li \l{apple-usage-description}{Usage description}
457 \li \c NSLocationWhenInUseUsageDescription, and
458 \c NSLocationAlwaysAndWhenInUseUsageDescription if requesting
459 QLocationPermission::Always
460 \row
461 \li Android
462 \li \l{android-uses-permission}{\c{uses-permission}}
463 \li \list
464 \li \c android.permission.ACCESS_FINE_LOCATION for QLocationPermission::Precise
465 \li \c android.permission.ACCESS_COARSE_LOCATION for QLocationPermission::Approximate
466 \li \c android.permission.ACCESS_BACKGROUND_LOCATION for QLocationPermission::Always
467 \endlist
468 \note QLocationPermission::Always \c uses-permission string has
469 to be combined with one or both of QLocationPermission::Precise
470 and QLocationPermission::Approximate strings.
471 \include permissions.qdocinc end-usage-declarations
472
473 \include permissions.qdocinc permission-metadata
474*/
475
476QT_PERMISSION_IMPL_COMMON(QLocationPermission)
477 : u{ShortData{.accuracy: Accuracy::Approximate, .availability: Availability::WhenInUse, .reserved: {}}}
478{}
479
480/*!
481 \enum QLocationPermission::Accuracy
482
483 This enum is used to control the accuracy of the location data.
484
485 \value Approximate An approximate location is requested.
486 \value Precise A precise location is requested.
487*/
488
489/*!
490 \enum QLocationPermission::Availability
491
492 This enum is used to control the availability of the location data.
493
494 \value WhenInUse The location is only available only when the
495 application is in use.
496 \value Always The location is available at all times, including when
497 the application is in the background.
498*/
499
500/*!
501 Sets the desired \a accuracy of the request.
502*/
503void QLocationPermission::setAccuracy(Accuracy accuracy)
504{
505 u.data.accuracy = accuracy;
506}
507
508/*!
509 Returns the accuracy of the request.
510*/
511QLocationPermission::Accuracy QLocationPermission::accuracy() const
512{
513 return u.data.accuracy;
514}
515
516/*!
517 Sets the desired \a availability of the request.
518*/
519void QLocationPermission::setAvailability(Availability availability)
520{
521 u.data.availability = availability;
522}
523
524/*!
525 Returns the availability of the request.
526*/
527QLocationPermission::Availability QLocationPermission::availability() const
528{
529 return u.data.availability;
530}
531
532/*!
533 \class QContactsPermission
534 \brief Access the user's contacts.
535
536 By default the request is for read-only access.
537 Use setAccessMode() to override the default.
538
539 \section1 Requirements
540
541 \include permissions.qdocinc begin-usage-declarations
542 \row
543 \li Apple
544 \li \l{apple-usage-description}{Usage description}
545 \li \c NSContactsUsageDescription
546 \row
547 \li Android
548 \li \l{android-uses-permission}{\c{uses-permission}}
549 \li \c android.permission.READ_CONTACTS. \c android.permission.WRITE_CONTACTS if
550 QContactsPermission::accessMode() is set to AccessMode::ReadWrite.
551 \include permissions.qdocinc end-usage-declarations
552
553 \include permissions.qdocinc permission-metadata
554*/
555
556/*!
557 \enum QContactsPermission::AccessMode
558
559 This enum is used to control access to the contacts data.
560
561 \value ReadOnly Read-only access to the contacts data (the default).
562 \value ReadWrite Read and write access to the contacts data.
563
564 \sa setAccessMode, accessMode
565*/
566
567QT_PERMISSION_IMPL_COMMON(QContactsPermission)
568 : u{ShortData{.mode: AccessMode::ReadOnly, .reserved: {}}}
569{}
570
571/*!
572 Sets whether the request is for read-write (\a mode == AccessMode::ReadOnly) or
573 read-only (\a mode == AccessMode::ReadOnly) access to the contacts.
574*/
575void QContactsPermission::setAccessMode(AccessMode mode)
576{
577 u.data.mode = mode;
578}
579
580/*!
581 Returns AccessMode::ReadWrite when the request is for read-write and
582 AccessMode::ReadOnly when it is for read-only access to the contacts.
583*/
584QContactsPermission::AccessMode QContactsPermission::accessMode() const
585{
586 return u.data.mode;
587}
588
589/*!
590 \class QCalendarPermission
591 \brief Access the user's calendar.
592
593 By default the request is for read-only access.
594 Use setAccessMode() to override the default.
595
596 \section1 Requirements
597
598 \include permissions.qdocinc begin-usage-declarations
599 \row
600 \li Apple
601 \li \l{apple-usage-description}{Usage description}
602 \li \c NSCalendarsUsageDescription
603 \row
604 \li Android
605 \li \l{android-uses-permission}{\c{uses-permission}}
606 \li \c android.permission.READ_CALENDAR. \c android.permission.WRITE_CALENDAR if
607 QCalendarPermission::accessMode() is set to AccessMode::ReadWrite.
608 \include permissions.qdocinc end-usage-declarations
609
610 \include permissions.qdocinc permission-metadata
611*/
612
613/*!
614 \enum QCalendarPermission::AccessMode
615
616 This enum is used to control access to the calendar data.
617
618 \value ReadOnly Read-only access to the calendar data (the default).
619 \value ReadWrite Read and write access to the calendar data.
620
621 \sa setAccessMode, accessMode
622*/
623
624QT_PERMISSION_IMPL_COMMON(QCalendarPermission)
625 : u{ShortData{.mode: AccessMode::ReadOnly, .reserved: {}}}
626{}
627
628/*!
629 Sets whether the request is for read-write (\a mode == AccessMode::ReadOnly) or
630 read-only (\a mode == AccessMode::ReadOnly) access to the calendar.
631*/
632void QCalendarPermission::setAccessMode(AccessMode mode)
633{
634 u.data.mode = mode;
635}
636
637/*!
638 Returns AccessMode::ReadWrite when the request is for read-write and
639 AccessMode::ReadOnly when it is for read-only access to the calendar.
640*/
641QCalendarPermission::AccessMode QCalendarPermission::accessMode() const
642{
643 return u.data.mode;
644}
645
646/*!
647 * \internal
648*/
649
650QPermissionPlugin::~QPermissionPlugin() = default;
651
652#ifndef QT_NO_DEBUG_STREAM
653QDebug operator<<(QDebug debug, const QPermission &permission)
654{
655 const auto verbosity = debug.verbosity();
656 QDebugStateSaver saver(debug);
657 debug.nospace().setVerbosity(0);
658 if (verbosity >= QDebug::DefaultVerbosity)
659 debug << permission.type().name() << "(";
660 debug << permission.status();
661 if (verbosity >= QDebug::DefaultVerbosity)
662 debug << ")";
663 return debug;
664}
665#endif
666
667#undef QT_PERMISSION_IMPL_COMMON
668
669#if !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) && !defined(Q_OS_WASM)
670// Default backend for platforms without a permission implementation.
671// Always returns Granted, to match behavior when not using permission APIs
672// https://bugreports.qt.io/browse/QTBUG-90498?focusedCommentId=725085#comment-725085
673namespace QPermissions::Private
674{
675 Qt::PermissionStatus checkPermission(const QPermission &permission)
676 {
677 qCDebug(lcPermissions) << "No permission backend on this platform."
678 << "Optimistically returning Granted for" << permission;
679 return Qt::PermissionStatus::Granted;
680 }
681
682 void requestPermission(const QPermission &permission, const PermissionCallback &callback)
683 {
684 qCDebug(lcPermissions) << "No permission backend on this platform."
685 << "Optimistically returning Granted for" << permission;
686 callback(Qt::PermissionStatus::Granted);
687 }
688}
689#endif
690
691QT_END_NAMESPACE
692
693#include "moc_qpermissions.cpp"
694

source code of qtbase/src/corelib/kernel/qpermissions.cpp