Skip to content

Commit

Permalink
List iOS APIs requiring privacy reasons (#27770)
Browse files Browse the repository at this point in the history
# Why

As per
https://developer.apple.com/documentation/bundleresources/privacy_manifest_files,
3rd party SDKs need to provide .xcprivacy files if:

- The 3rd party dependency uses one of the APIs listed in required
reasons APIs. This is also relevant for 3rd party SDKs shipped as swift
code = entire expo SDK.

We need to add an .xcprivacy file to expo-go to pass app review after
May 1st, so we bundle xcprivacy files with expo-modules that require it.

We use this mechanism:
https://github.com/SDWebImage/SDWebImage/blob/98d058a1ea053484bc4df447153654a0e4a70549/SDWebImage.podspec#L49,
that I tested and confirmed to bundle correctly.

I identifed all expo-modules that make use of APIs listed in required
reasons – using
https://github.com/Wooder/ios_17_required_reason_api_scanner

Related to:
#27796

https://linear.app/expo/issue/ENG-11731/investigate-ios-privacy-manifest-requirements


# Test Plan

Tested by generating a privacy report using xCode – the items don't show
since they don't add any privacy labels, but after adding the label to
any of the generated xcprivacy files those labels do show up:

<img width="1761" alt="image"
src="https://github.com/expo/expo/assets/5597580/af887839-90db-456b-b76a-5ad6d9fe4511">


# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
This is required for changes to Expo modules.
-->

- [ ] Documentation is up to date to reflect these changes (eg:
https://docs.expo.dev and README.md).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).

---------

Co-authored-by: Łukasz Kosmaty <lukasz.kosmaty@swmansion.com>
  • Loading branch information
aleqsio and lukmccall committed Mar 26, 2024
1 parent 652b0f8 commit b8e4061
Show file tree
Hide file tree
Showing 38 changed files with 268 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/expo-application/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### 💡 Others

- [iOS] Add privacy manifest describing required reason API usage. ([#27770](https://github.com/expo/expo/pull/27770) by [@aleqsio](https://github.com/aleqsio))
- drop unused web `name` property. ([#27437](https://github.com/expo/expo/pull/27437) by [@EvanBacon](https://github.com/EvanBacon))

## 5.8.3 - 2024-01-18
Expand Down
1 change: 1 addition & 0 deletions packages/expo-application/ios/ApplicationModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class ApplicationModule: Module {

do {
let fileAttributes = try FileManager.default.attributesOfItem(atPath: urlToDocumentsFolder.path)
// Uses required reason API based on the following reason: C617.1
if let installDate = fileAttributes[FileAttributeKey.creationDate] as? Date {
return installDate.timeIntervalSince1970 * 1000
}
Expand Down
2 changes: 2 additions & 0 deletions packages/expo-application/ios/EXApplication.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Pod::Spec.new do |s|
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}

s.resource_bundles = {'ExpoApplication_privacy' => ['PrivacyInfo.xcprivacy']}

if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
s.source_files = "#{s.name}/**/*.h"
s.vendored_frameworks = "#{s.name}.xcframework"
Expand Down
24 changes: 24 additions & 0 deletions packages/expo-application/ios/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
1 change: 1 addition & 0 deletions packages/expo-constants/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### 💡 Others

- [iOS] Add privacy manifest describing required reason API usage. ([#27770](https://github.com/expo/expo/pull/27770) by [@aleqsio](https://github.com/aleqsio))
- [expo-updates] Migrate to requireNativeModule/requireOptionalNativeModule. ([#25648](https://github.com/expo/expo/pull/25648) by [@wschurman](https://github.com/wschurman))
- Remove most of Constants.appOwnership. ([#26313](https://github.com/expo/expo/pull/26313) by [@wschurman](https://github.com/wschurman))
- Improve updates types and clarity in expo-asset. ([#26337](https://github.com/expo/expo/pull/26337) by [@wschurman](https://github.com/wschurman))
Expand Down
3 changes: 2 additions & 1 deletion packages/expo-constants/ios/EXConstants.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ Pod::Spec.new do |s|
# Generate EXConstants.bundle without existing resources
# `get-app-config-ios.sh` will generate app.config in EXConstants.bundle
s.resource_bundles = {
'EXConstants' => []
'EXConstants' => [],
'ExpoConstants_privacy' => ['PrivacyInfo.xcprivacy']
}

end
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// Copyright 2015-present 650 Industries. All rights reserved.

#import <EXConstants/EXConstantsInstallationIdProvider.h>

static NSString * const kEXDeviceInstallationUUIDKey = @"EXDeviceInstallationUUIDKey";
Expand Down Expand Up @@ -41,7 +40,7 @@ - (nullable NSString *)getInstallationId
if (installationId) {
return installationId;
}

// Uses required reason API based on the following reason: CA92.1
NSString *legacyUUID = [[NSUserDefaults standardUserDefaults] stringForKey:kEXDeviceInstallationUUIDLegacyKey];
if (legacyUUID) {
installationId = legacyUUID;
Expand Down
24 changes: 24 additions & 0 deletions packages/expo-constants/ios/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>
2 changes: 2 additions & 0 deletions packages/expo-device/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

### 💡 Others

- [iOS] Add privacy manifest describing required reason API usage. ([#27770](https://github.com/expo/expo/pull/27770) by [@aleqsio](https://github.com/aleqsio))

## 5.9.3 - 2024-01-18

_This version does not introduce any user-facing changes._
Expand Down
1 change: 1 addition & 0 deletions packages/expo-device/ios/DeviceModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class DeviceModule: Module {
}

AsyncFunction("getUptimeAsync") { () -> Double in
// Uses required reason API based on the following reason: 35F9.1 – there's not really a matching reason here
return ProcessInfo.processInfo.systemUptime * 1000
}

Expand Down
2 changes: 2 additions & 0 deletions packages/expo-device/ios/ExpoDevice.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Pod::Spec.new do |s|
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}

s.resource_bundles = {'ExpoDevice_privacy' => ['PrivacyInfo.xcprivacy']}

if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
s.source_files = "**/*.h"
s.vendored_frameworks = "#{s.name}.xcframework"
Expand Down
24 changes: 24 additions & 0 deletions packages/expo-device/ios/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
</dict>
</plist>
1 change: 1 addition & 0 deletions packages/expo-file-system/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### 💡 Others

- [iOS] Add privacy manifest describing required reason API usage. ([#27770](https://github.com/expo/expo/pull/27770) by [@aleqsio](https://github.com/aleqsio))
- drop unused web `name` property. ([#27437](https://github.com/expo/expo/pull/27437) by [@EvanBacon](https://github.com/EvanBacon))

## 16.0.8 - 2024-03-07
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ + (void)getInfoForFile:(NSURL *)fileUri
result[@"exists"] = @(YES);
result[@"isDirectory"] = @(NO);
result[@"uri"] = fileUri;
// Uses required reason API based on the following reason: 3B52.1
result[@"modificationTime"] = @(asset.modificationDate.timeIntervalSince1970);
if (options[@"md5"] || options[@"size"]) {
[[PHImageManager defaultManager] requestImageDataAndOrientationForAsset:asset options:nil resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, CGImagePropertyOrientation orientation, NSDictionary * _Nullable info) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ + (void)getInfoForFile:(NSURL *)fileUri
result[@"md5"] = [[NSData dataWithContentsOfFile:path] md5String];
}
result[@"size"] = @([EXFileSystemLocalFileHandler getFileSize:path attributes:attributes]);
// Uses required reason API based on the following reason: 0A2A.1
result[@"modificationTime"] = @(attributes.fileModificationDate.timeIntervalSince1970);
resolve(result);
} else {
Expand Down
2 changes: 2 additions & 0 deletions packages/expo-file-system/ios/ExpoFileSystem.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Pod::Spec.new do |s|
'DEFINES_MODULE' => 'YES'
}

s.resource_bundles = {'ExpoFileSystem_privacy' => ['PrivacyInfo.xcprivacy']}

s.source_files = "**/*.{h,m,swift}"

s.exclude_files = 'Tests/'
Expand Down
2 changes: 2 additions & 0 deletions packages/expo-file-system/ios/FileSystemModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ public final class FileSystemModule: Module {
}

AsyncFunction("getFreeDiskStorageAsync") { () -> Int in
// Uses required reason API based on the following reason: E174.1 85F4.1
let resourceValues = try getResourceValues(from: documentDirectory, forKeys: [.volumeAvailableCapacityKey])

guard let availableCapacity = resourceValues?.volumeAvailableCapacity else {
Expand All @@ -265,6 +266,7 @@ public final class FileSystemModule: Module {
}

AsyncFunction("getTotalDiskCapacityAsync") { () -> Int in
// Uses required reason API based on the following reason: E174.1 85F4.1
let resourceValues = try getResourceValues(from: documentDirectory, forKeys: [.volumeTotalCapacityKey])

guard let totalCapacity = resourceValues?.volumeTotalCapacity else {
Expand Down
34 changes: 34 additions & 0 deletions packages/expo-file-system/ios/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>0A2A.1</string>
<string>3B52.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>E174.1</string>
<string>85F4.1</string>
</array>
</dict>
</array>
</dict>
</plist>
1 change: 1 addition & 0 deletions packages/expo-localization/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### 🐛 Bug fixes

- [iOS] Add privacy manifest describing required reason API usage. ([#27770](https://github.com/expo/expo/pull/27770) by [@aleqsio](https://github.com/aleqsio))
- [Android] Fix es-419 locale returning empty list. ([#27250](https://github.com/expo/expo/pull/27250) by [@aleqsio](https://github.com/aleqsio))

### 💡 Others
Expand Down
2 changes: 2 additions & 0 deletions packages/expo-localization/ios/ExpoLocalization.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Pod::Spec.new do |s|
'DEFINES_MODULE' => 'YES',
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}

s.resource_bundles = {'ExpoLocalization_privacy' => ['PrivacyInfo.xcprivacy']}

if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
s.source_files = "**/*.h"
Expand Down
1 change: 1 addition & 0 deletions packages/expo-localization/ios/LocalizationModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class LocalizationModule: Module {
// These keys are used by React Native here: https://github.com/facebook/react-native/blob/main/React/Modules/RCTI18nUtil.m
// We set them before React loads to ensure it gets rendered correctly the first time the app is opened.
// On iOS we need to set both forceRTL and allowRTL so apps don't have to include localization strings.
// Uses required reason API based on the following reason: CA92.1
UserDefaults.standard.set(supportsRTL, forKey: "RCTI18nUtil_allowRTL")
UserDefaults.standard.set(supportsRTL ? isRTLPreferredForCurrentLocale() : false, forKey: "RCTI18nUtil_forceRTL")
UserDefaults.standard.synchronize()
Expand Down
24 changes: 24 additions & 0 deletions packages/expo-localization/ios/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>
1 change: 1 addition & 0 deletions packages/expo-media-library/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

### 💡 Others

- [iOS] Add privacy manifest describing required reason API usage. ([#27770](https://github.com/expo/expo/pull/27770) by [@aleqsio](https://github.com/aleqsio))
- drop unused web `name` property. ([#27437](https://github.com/expo/expo/pull/27437) by [@EvanBacon](https://github.com/EvanBacon))
- [iOS] Migrate to expo modules. ([#25587](https://github.com/expo/expo/pull/25587) by [@alanjhughes](https://github.com/alanjhughes))

Expand Down
2 changes: 2 additions & 0 deletions packages/expo-media-library/ios/ExpoMediaLibrary.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Pod::Spec.new do |s|
s.dependency 'ExpoModulesCore'
s.dependency 'React-Core'

s.resource_bundles = {'ExpoMediaLibrary_privacy' => ['PrivacyInfo.xcprivacy']}

# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES'
Expand Down
2 changes: 2 additions & 0 deletions packages/expo-media-library/ios/MediaLibraryUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ func exportAsset(asset: PHAsset?) -> [String: Any?]? {
"mediaSubtypes": stringifyMedia(mediaSubtypes: asset.mediaSubtypes),
"width": asset.pixelWidth,
"height": asset.pixelHeight,
// Uses required reason API based on the following reason: 0A2A.1
"creationTime": exportDate(asset.creationDate),
// Uses required reason API based on the following reason: 0A2A.1
"modificationTime": exportDate(asset.modificationDate),
"duration": asset.duration
]
Expand Down
24 changes: 24 additions & 0 deletions packages/expo-media-library/ios/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>0A2A.1</string>
</array>
</dict>
</array>
</dict>
</plist>
1 change: 1 addition & 0 deletions packages/expo-notifications/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

### 💡 Others

- [iOS] Add privacy manifest describing required reason API usage. ([#27770](https://github.com/expo/expo/pull/27770) by [@aleqsio](https://github.com/aleqsio))
- drop unused web `name` property. ([#27437](https://github.com/expo/expo/pull/27437) by [@EvanBacon](https://github.com/EvanBacon))

## 0.27.5 - 2024-01-25
Expand Down
2 changes: 2 additions & 0 deletions packages/expo-notifications/ios/EXNotifications.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ Pod::Spec.new do |s|

s.dependency 'ExpoModulesCore'

s.resource_bundles = {'ExpoNotifications_privacy' => ['PrivacyInfo.xcprivacy']}

if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
s.source_files = "#{s.name}/**/*.h"
s.vendored_frameworks = "#{s.name}.xcframework"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ - (nullable NSString *)fetchInstallationId
return installationId;
}

// Uses required reason API based on the following reason: CA92.1
NSString *legacyUUID = [[NSUserDefaults standardUserDefaults] stringForKey:kEXDeviceInstallationUUIDLegacyKey];
if (legacyUUID) {
installationId = legacyUUID;
Expand Down
24 changes: 24 additions & 0 deletions packages/expo-notifications/ios/PrivacyInfo.xcprivacy
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array>
</array>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
</array>
</dict>
</plist>

0 comments on commit b8e4061

Please sign in to comment.