FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
pythonnative/src/pythonnative/project/permissions.py at v0.43.1 · pythonnative/pythonnative · GitHub
pythonnative
pythonnative
Repository navigation
Code
Issues
11
(11)
Pull requests
4
(4)
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
pythonnative
/
src
/
pythonnative
/
project
/
permissions.py
Copy path
More file actions
More file actions
Latest commit
History
History
History
361 lines (322 loc) · 13.9 KB
Breadcrumbs
pythonnative
/
src
/
pythonnative
/
project
/
permissions.py
Copy path
File metadata and controls
361 lines (322 loc) · 13.9 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
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
"""Cross-platform permission/capability catalog.
PythonNative apps declare the device capabilities they need in a single,
platform-agnostic ``[permissions]`` table in ``pythonnative.toml``:
```toml
[permissions]
camera = "Scan receipts with your camera."
location_when_in_use = "Show nearby stores."
notifications = true
face_id = "Unlock the app with Face ID."
```
This module maps each high-level capability to the concrete native
artifacts it requires:
- iOS: one or more ``Info.plist`` *usage description* keys (the strings
shown in the system permission prompt), plus optional
``UIBackgroundModes`` entries.
- Android: one or more ``<uses-permission>`` entries in
``AndroidManifest.xml``.
A capability's value may be either a string (used verbatim as the iOS
usage description) or ``true`` (use the capability's
[`default_reason`][pythonnative.project.permissions.Capability]). A value
of ``false`` disables the capability, useful for switching one off
without deleting the line.
The catalog is the single source of truth shared by the iOS and Android
configurators and by ``pn doctor``; adding a capability here is all that
is required to make it declarable.
"""
from
__future__
import
annotations
from
dataclasses
import
dataclass
,
field
from
typing
import
Dict
,
List
,
Mapping
,
Tuple
,
Union
PermissionValue
=
Union
[
bool
,
str
]
"""Type of a value in the ``[permissions]`` table: a reason string or a bool."""
@
dataclass
(
frozen
=
True
)
class
Capability
:
"""A declarable device capability and its native requirements.
Attributes:
key: The capability name as written in ``[permissions]``
(e.g., ``"camera"``).
summary: Human-readable description, shown by ``pn doctor`` and
the docs.
ios_usage_keys: ``Info.plist`` keys that receive the usage
description string (e.g., ``"NSCameraUsageDescription"``).
android_permissions: Fully-qualified Android permission names
(e.g., ``"android.permission.CAMERA"``).
ios_background_modes: ``UIBackgroundModes`` values to add
(e.g., ``"location"``).
ios_entitlements: Code-signing entitlement key/value pairs the
capability requires (e.g., ``aps-environment`` for remote
push notifications).
default_reason: Fallback usage description used when the
capability is declared as ``true`` instead of a string.
needs_reason: Whether iOS requires a usage description for this
capability. When ``False`` (e.g., notifications), declaring
the capability as ``true`` is sufficient and no string is
needed.
"""
key
:
str
summary
:
str
ios_usage_keys
:
Tuple
[
str
, ...]
=
()
android_permissions
:
Tuple
[
str
, ...]
=
()
ios_background_modes
:
Tuple
[
str
, ...]
=
()
ios_entitlements
:
Tuple
[
Tuple
[
str
,
object
], ...]
=
()
default_reason
:
str
=
""
needs_reason
:
bool
=
True
def
_cap
(
*
args
:
object
,
**
kwargs
:
object
)
->
Capability
:
return
Capability
(
*
args
,
**
kwargs
)
# type: ignore[arg-type]
# ======================================================================
# The catalog
# ======================================================================
CAPABILITIES
:
Dict
[
str
,
Capability
]
=
{
c
.
key
:
c
for
c
in
[
Capability
(
key
=
"camera"
,
summary
=
"Capture photos and video with the device camera."
,
ios_usage_keys
=
(
"NSCameraUsageDescription"
,),
android_permissions
=
(
"android.permission.CAMERA"
,),
default_reason
=
"This app uses the camera."
,
),
Capability
(
key
=
"microphone"
,
summary
=
"Record audio from the microphone."
,
ios_usage_keys
=
(
"NSMicrophoneUsageDescription"
,),
android_permissions
=
(
"android.permission.RECORD_AUDIO"
,),
default_reason
=
"This app uses the microphone."
,
),
Capability
(
key
=
"photo_library"
,
summary
=
"Read photos and videos from the photo library."
,
ios_usage_keys
=
(
"NSPhotoLibraryUsageDescription"
,),
android_permissions
=
(
"android.permission.READ_MEDIA_IMAGES"
,
"android.permission.READ_MEDIA_VIDEO"
,
),
default_reason
=
"This app accesses your photo library."
,
),
Capability
(
key
=
"photo_library_add"
,
summary
=
"Save photos and videos to the photo library."
,
ios_usage_keys
=
(
"NSPhotoLibraryAddUsageDescription"
,),
android_permissions
=
(),
default_reason
=
"This app saves photos to your library."
,
),
Capability
(
key
=
"location_when_in_use"
,
summary
=
"Access location while the app is in the foreground."
,
ios_usage_keys
=
(
"NSLocationWhenInUseUsageDescription"
,),
android_permissions
=
(
"android.permission.ACCESS_FINE_LOCATION"
,
"android.permission.ACCESS_COARSE_LOCATION"
,
),
default_reason
=
"This app uses your location."
,
),
Capability
(
key
=
"location_always"
,
summary
=
"Access location in the foreground and background."
,
ios_usage_keys
=
(
"NSLocationAlwaysAndWhenInUseUsageDescription"
,
"NSLocationWhenInUseUsageDescription"
,
),
android_permissions
=
(
"android.permission.ACCESS_FINE_LOCATION"
,
"android.permission.ACCESS_COARSE_LOCATION"
,
"android.permission.ACCESS_BACKGROUND_LOCATION"
,
),
ios_background_modes
=
(
"location"
,),
default_reason
=
"This app uses your location, even in the background."
,
),
Capability
(
key
=
"contacts"
,
summary
=
"Read the device address book."
,
ios_usage_keys
=
(
"NSContactsUsageDescription"
,),
android_permissions
=
(
"android.permission.READ_CONTACTS"
,),
default_reason
=
"This app accesses your contacts."
,
),
Capability
(
key
=
"calendars"
,
summary
=
"Read and write calendar events."
,
ios_usage_keys
=
(
"NSCalendarsUsageDescription"
,),
android_permissions
=
(
"android.permission.READ_CALENDAR"
,
"android.permission.WRITE_CALENDAR"
,
),
default_reason
=
"This app accesses your calendar."
,
),
Capability
(
key
=
"reminders"
,
summary
=
"Read and write reminders (iOS only)."
,
ios_usage_keys
=
(
"NSRemindersUsageDescription"
,),
android_permissions
=
(),
default_reason
=
"This app accesses your reminders."
,
),
Capability
(
key
=
"motion"
,
summary
=
"Access motion and fitness / activity data."
,
ios_usage_keys
=
(
"NSMotionUsageDescription"
,),
android_permissions
=
(
"android.permission.ACTIVITY_RECOGNITION"
,),
default_reason
=
"This app uses motion and fitness data."
,
),
Capability
(
key
=
"face_id"
,
summary
=
"Authenticate with Face ID / biometrics."
,
ios_usage_keys
=
(
"NSFaceIDUsageDescription"
,),
android_permissions
=
(
"android.permission.USE_BIOMETRIC"
,),
default_reason
=
"This app uses Face ID to authenticate you."
,
),
Capability
(
key
=
"bluetooth"
,
summary
=
"Communicate with nearby Bluetooth devices."
,
ios_usage_keys
=
(
"NSBluetoothAlwaysUsageDescription"
,),
android_permissions
=
(
"android.permission.BLUETOOTH_CONNECT"
,
"android.permission.BLUETOOTH_SCAN"
,
),
default_reason
=
"This app connects to Bluetooth devices."
,
),
Capability
(
key
=
"speech_recognition"
,
summary
=
"Perform speech recognition (iOS only)."
,
ios_usage_keys
=
(
"NSSpeechRecognitionUsageDescription"
,),
android_permissions
=
(),
default_reason
=
"This app uses speech recognition."
,
),
Capability
(
key
=
"notifications"
,
summary
=
"Show local notifications."
,
ios_usage_keys
=
(),
android_permissions
=
(
"android.permission.POST_NOTIFICATIONS"
,),
needs_reason
=
False
,
),
Capability
(
key
=
"remote_notifications"
,
summary
=
"Receive remote (APNs) push notifications on iOS."
,
ios_usage_keys
=
(),
android_permissions
=
(
"android.permission.POST_NOTIFICATIONS"
,),
ios_background_modes
=
(
"remote-notification"
,),
ios_entitlements
=
((
"aps-environment"
,
"development"
),),
needs_reason
=
False
,
),
Capability
(
key
=
"vibration"
,
summary
=
"Trigger haptic feedback / vibration."
,
ios_usage_keys
=
(),
android_permissions
=
(
"android.permission.VIBRATE"
,),
needs_reason
=
False
,
),
Capability
(
key
=
"background_audio"
,
summary
=
"Continue playing audio in the background (iOS)."
,
ios_usage_keys
=
(),
ios_background_modes
=
(
"audio"
,),
android_permissions
=
(
"android.permission.FOREGROUND_SERVICE"
,),
needs_reason
=
False
,
),
Capability
(
key
=
"background_fetch"
,
summary
=
"Perform periodic background fetches (iOS)."
,
ios_usage_keys
=
(),
ios_background_modes
=
(
"fetch"
,),
android_permissions
=
(),
needs_reason
=
False
,
),
]
}
"""Mapping of capability key → [`Capability`][pythonnative.project.permissions.Capability]."""
# Permissions every app gets, regardless of declared capabilities. Both are
# "normal" (install-time) Android permissions that never prompt the user, and
# nearly every PythonNative app needs the network (``fetch``, ``use_query``,
# ``NetInfo``, remote images).
BASE_ANDROID_PERMISSIONS
:
Tuple
[
str
, ...]
=
(
"android.permission.INTERNET"
,
"android.permission.ACCESS_NETWORK_STATE"
,
)
"""Android permissions added to every app (network access)."""
@
dataclass
class
ResolvedPermissions
:
"""The native permission artifacts for a resolved capability set.
Attributes:
ios_usage_descriptions: ``Info.plist`` usage-description keys
mapped to their reason strings.
ios_background_modes: Ordered, de-duplicated ``UIBackgroundModes``
values.
ios_entitlements: Code-signing entitlement keys mapped to their
values (empty when no capability needs entitlements).
android_permissions: Ordered, de-duplicated Android permission
names (including the always-on base set).
"""
ios_usage_descriptions
:
Dict
[
str
,
str
]
=
field
(
default_factory
=
dict
)
ios_background_modes
:
List
[
str
]
=
field
(
default_factory
=
list
)
ios_entitlements
:
Dict
[
str
,
object
]
=
field
(
default_factory
=
dict
)
android_permissions
:
List
[
str
]
=
field
(
default_factory
=
list
)
def
unknown_capabilities
(
keys
:
object
)
->
List
[
str
]:
"""Return any declared capability keys that aren't in the catalog.
Args:
keys: An iterable of capability key strings.
Returns:
The subset of ``keys`` not present in
[`CAPABILITIES`][pythonnative.project.permissions.CAPABILITIES],
in input order.
"""
result
:
List
[
str
]
=
[]
for
key
in
keys
:
if
key
not
in
CAPABILITIES
:
result
.
append
(
str
(
key
))
return
result
def
resolve_permissions
(
permissions
:
Mapping
[
str
,
PermissionValue
],
*
,
extra_android_permissions
:
object
=
(),
)
->
ResolvedPermissions
:
"""Resolve a declared capability map into native permission artifacts.
Args:
permissions: The ``[permissions]`` table, capability key to a
reason string or boolean. ``false``/``None`` values are
skipped (capability disabled).
extra_android_permissions: Additional raw Android permission
names (e.g., from ``[android].permissions``) to append.
Returns:
A [`ResolvedPermissions`][pythonnative.project.permissions.ResolvedPermissions]
with iOS usage descriptions, iOS background modes, and the full
ordered Android permission list (base set first).
Raises:
ValueError: If an unknown capability key is present. Validate
earlier with
[`unknown_capabilities`][pythonnative.project.permissions.unknown_capabilities]
for a friendlier error.
"""
resolved
=
ResolvedPermissions
()
android
:
List
[
str
]
=
list
(
BASE_ANDROID_PERMISSIONS
)
background
:
List
[
str
]
=
[]
for
key
,
value
in
permissions
.
items
():
if
key
not
in
CAPABILITIES
:
raise
ValueError
(
f"Unknown capability:
{
key
!r
}
"
)
if
value
is
False
or
value
is
None
:
continue
cap
=
CAPABILITIES
[
key
]
reason
=
value
if
isinstance
(
value
,
str
)
and
value
.
strip
()
else
cap
.
default_reason
for
plist_key
in
cap
.
ios_usage_keys
:
# First declaration wins for a shared key (e.g. location_*).
resolved
.
ios_usage_descriptions
.
setdefault
(
plist_key
,
reason
)
for
mode
in
cap
.
ios_background_modes
:
if
mode
not
in
background
:
background
.
append
(
mode
)
for
entitlement_key
,
entitlement_value
in
cap
.
ios_entitlements
:
resolved
.
ios_entitlements
.
setdefault
(
entitlement_key
,
entitlement_value
)
for
perm
in
cap
.
android_permissions
:
if
perm
not
in
android
:
android
.
append
(
perm
)
for
perm
in
extra_android_permissions
:
if
perm
and
perm
not
in
android
:
android
.
append
(
str
(
perm
))
resolved
.
ios_background_modes
=
background
resolved
.
android_permissions
=
android
return
resolved
def
describe_catalog
()
->
str
:
"""Return a multi-line, human-readable listing of all capabilities.
Used by ``pn doctor`` / docs tooling to show what can be declared.
"""
lines
:
List
[
str
]
=
[]
for
key
in
sorted
(
CAPABILITIES
):
cap
=
CAPABILITIES
[
key
]
lines
.
append
(
f"
{
key
:<22
}
{
cap
.
summary
}
"
)
return
"
\n
"
.
join
(
lines
)
Back
|
FazBrowse Home
|
New Git URL