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

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