blog icon indicating copy to clipboard operation
blog copied to clipboard

2024年5月1号起,苹果新政:“Describing use of required reason API”

Open diamont1001 opened this issue 10 months ago • 0 comments

最近苹果又双叒叕出了新政了,关于 Describing use of required reason API 的问题,2024年5月1号之后,所有用到这些接口的(其实一般都要用到,比如本地存储 UserDefaults),都需要新建一个 PrivacyInfo.xcprivacy 文件,并把使用到的 API 和相关信息填上。

PS: Widget 项目也需要单独添加!

PrivacyInfo.xcprivacy 文件添加

参考:https://developer.apple.com/documentation/bundleresources/privacy_manifest_files#4284009

解决

方法一:打个包上去提审一次,会驳回并收到包含 ISSUE 邮件

5月1号前提审的应用,会收到如下的提醒邮件,但不影响当次的审核。邮件里面会列出来你哪个包里有哪些API需要声明权限,如下:

image

再查看文档 https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api 找出对应接口的可选值,按要求填上即可。

方法二:自主扫描

网上有大神已经提供了一个解决方案:ios_17_required_reason_api_scanner

通过以上提供的脚本文件 xxx.sh,可以很快速的搜索到你的项目所用到的 required reason API

有两个脚本:required_reason_api_text_scanner.shrequired_reason_api_scanner_binary.sh,一个是代码搜索,一个是二进制文件搜索。可以两个都跑下,确保没有遗漏。

比如,下面是我用 required_reason_api_scanner_binary.sh 扫描的结果:

image

结果显示用到了两个:NSURLCreationDateKeyNSUserDefaults,再查看文档 https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api 找出对应接口的可选值,按要求填上即可。

PrivacyInfo.xcprivacy 例子

下面提供一个我项目中 PrivacyInfo.xcprivacy 的例子,仅供参考:

<?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>NSPrivacyAccessedAPITypes</key>
	<array>
        <dict>
            <key>NSPrivacyAccessedAPIType</key>
            <string>NSPrivacyAccessedAPICategoryUserDefaults</string>
            <key>NSPrivacyAccessedAPITypeReasons</key>
            <array>
                <string>1C8F.1</string>
            </array>
        </dict>
        <dict>
            <key>NSPrivacyAccessedAPIType</key>
            <string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
            <key>NSPrivacyAccessedAPITypeReasons</key>
            <array>
                <string>C617.1</string>
            </array>
        </dict>
        <dict>
            <key>NSPrivacyAccessedAPIType</key>
            <string>NSPrivacyAccessedAPICategoryDiskSpace</string>
            <key>NSPrivacyAccessedAPITypeReasons</key>
            <array>
                <string>E174.1</string>
            </array>
        </dict>
	</array>
</dict>
</plist>

Property List 版本:

image

注意,红色框内的值,需要根据项目实际用途选填具体的值!!!

脚本备份

为了防止上面的脚本项目被删,下面给手动备份一下(最新手动更新时间:2024.04.08)。

required_reason_api_text_scanner.sh

#!/bin/bash

# Array of directories to exclude from the search
excluded_dirs=() # e.g. ("Pods" "3rdparty")

# Global variable for search strings that may indicate a use of "iOS required reason API"
# taken from here: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api
search_string=(".creationDate"
               ".creationDateKey"
               ".modificationDate"
               ".fileModificationDate" 
               ".contentModificationDateKey" 
               ".creationDateKey"
               "getattrlist(" 
               "getattrlistbulk(" 
               "fgetattrlist(" 
               "stat.st_" # see https://developer.apple.com/documentation/kernel/stat
               "fstat("
               "fstatat("
               "lstat("
               "getattrlistat("
               "systemUptime"
               "mach_absolute_time()"
               "volumeAvailableCapacityKey"
               "volumeAvailableCapacityForImportantUsageKey"
               "volumeAvailableCapacityForOpportunisticUsageKey"
               "volumeTotalCapacityKey"
               "systemFreeSize"
               "systemSize"
               "statfs("
               "statvfs("
               "fstatfs("
               "fstatvfs("
               "getattrlist("
               "fgetattrlist("
               "getattrlistat("
               "activeInputModes"
               "UserDefaults"
               "NSUserDefaults"
               "NSFileCreationDate"
               "NSFileModificationDate"
               "NSFileSystemFreeSize"
               "NSFileSystemSize"
               "NSURLContentModificationDateKey"
               "NSURLCreationDateKey"
               "NSURLVolumeAvailableCapacityForImportantUsageKey"
               "NSURLVolumeAvailableCapacityForOpportunisticUsageKey"
               "NSURLVolumeAvailableCapacityKey"
               "NSURLVolumeTotalCapacityKey"
               "AppStorage"
               )

# Function to search for required reason API strings in a Swift files
search_in_swift_file() {
    local file="$1"

    # Loop through each search string
    for string in "${search_string[@]}"; do
        # Search for the string in the file and get the line numbers
        lines=$(grep -n "$string" "$file" | cut -d ":" -f 1)
        if [ -n "$lines" ]; then
            echo "Found potentially required reason API usage '$string' in '$file'"
            one_line_string=$(echo "$lines" | tr '\n' ' ')
            echo "Line numbers: $one_line_string"
        fi
    done
}

# Function to check if a directory is in the excluded list
is_excluded_dir() {
    local dir_name="$1"
    for excluded_dir in "${excluded_dirs[@]}"; do
        if [ "$dir_name" == "$excluded_dir" ]; then
            return 0
        fi
    done
    return 1
}


# Function to traverse directories recursively and search for Swift files
traverse_and_search() {
    local folder="$1"

    # Get the name of the current directory
    local dir_name=$(basename "$folder")

    # Check if the directory is in the excluded list
    if is_excluded_dir "$dir_name"; then
        return
    fi

    # Loop through each item in the folder
    for item in "$folder"/*; do
        if [ -d "$item" ]; then
            # If it's a directory, call the function recursively
            traverse_and_search "$item"
        elif [ -f "$item" ] && ([[ "$item" == *.swift ]] || [[ "$item" == *.m ]] || [[ "$item" == *.h ]]); then
            # If it's a file with .swift extension, search for the strings
            search_in_swift_file "$item"
        fi
    done
}

# Check if a directory path is provided as an argument
if [ -z "$1" ]; then
    echo "Usage: $0 <directory-path>"
    exit 1
fi

# Start the search
echo "Searching for use of required reason API"
echo "See https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api"

traverse_and_search "$1"

required_reason_api_binary_scanner.sh

#!/bin/bash

set -e

symbols=(
activeInputModes
fgetattrlist
fstat
fstatat
fstatfs
fstatvfs
getattrlist
getattrlistat
getattrlistbulk
lstat
mach_absolute_time
NSFileCreationDate
NSFileModificationDate
NSFileSystemFreeSize
NSFileSystemSize
NSURLContentModificationDateKey
NSURLCreationDateKey
NSURLVolumeAvailableCapacityForImportantUsageKey
NSURLVolumeAvailableCapacityForOpportunisticUsageKey
NSURLVolumeAvailableCapacityKey
NSURLVolumeTotalCapacityKey
NSUserDefaults
stat
statfs
statvfs
systemUptime
)

function join_by {
  local d=${1-} f=${2-}
  if shift 2; then
    printf %s "$f" "${@/#/$d}"
  fi
}

function search_binaries_with_directory {
    extension=$1
    tempfile=$(mktemp)
    trap "rm -f $tempfile" EXIT
    find . -iname "*.$extension" -print0 > "$tempfile"
    while IFS= read -r -d '' binary; do
        name=$(basename "$binary")
        binary_name=$(echo "$name" | sed "s/.$extension//")
        binaries+=("$(dirname "$binary")/${name}/${binary_name}")
    done < "$tempfile"
}

function search_binaries_with_filename {
    extension=$1
    tempfile=$(mktemp)
    trap "rm -f $tempfile" EXIT
    find . -iname "*.$extension" -print0 > "$tempfile"
    while IFS= read -r -d '' binary; do
        binaries+=("$binary")
    done < "$tempfile"
}


cd "$1"

declare -a binaries

# Adds the .app binary
search_binaries_with_directory app
# Adds the (dynamic/static) .framework binaries
search_binaries_with_directory framework
# Adds the static libs .a binaries
search_binaries_with_filename a

echo "Analyzing binaries: ${binaries[@]}"
echo '---'

for binary in "${binaries[@]}"; do
    if ! [ -f "$binary" ]; then
        echo "binary '$binary' doesn't exist"
        exit 1
    fi
    used_symbols=()
    for symbol in "${symbols[@]}"; do
        if nm "$binary" 2>/dev/null | xcrun swift-demangle | grep -E "$symbol$" >/dev/null; then
            used_symbols+=($symbol)
        fi
    done
    if [ ${#used_symbols[@]} -gt 0 ]; then
        echo "Used symbols in binary $binary: $(join_by ', ' ${used_symbols[@]})"
    fi
done

diamont1001 avatar Mar 27 '24 02:03 diamont1001