Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Release Tooling] xcprivacy generation tooling (3) #12149

Closed
wants to merge 8 commits into from
Closed
2 changes: 1 addition & 1 deletion ReleaseTooling/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ let package = Package(
),
.target(
name: "PrivacyManifestGenerator",
dependencies: ["ArgumentParser", "PrivacyKit"]
dependencies: ["ArgumentParser", "PrivacyKit", "Utils"]
),
.target(
name: "PrivacyKit"
Expand Down
202 changes: 202 additions & 0 deletions ReleaseTooling/Sources/PrivacyKit/AccessedAPIType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Foundation

/// A structure containing metadata about the usage of API that requires justification for use.
/// https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api
public struct AccessedAPIType: Encodable {
public enum Category: String, CaseIterable, Encodable {
case fileTimestamp = "NSPrivacyAccessedAPICategoryFileTimestamp"
case systemBootTime = "NSPrivacyAccessedAPICategorySystemBootTime"
case diskSpace = "NSPrivacyAccessedAPICategoryDiskSpace"
case activeKeyboards = "NSPrivacyAccessedAPICategoryActiveKeyboards"
case userDefaults = "NSPrivacyAccessedAPICategoryUserDefaults"

public var description: String {
switch self {
case .fileTimestamp: return "File Timestamp"
case .systemBootTime: return "System Boot Time"
case .diskSpace: return "Disk Space"
case .activeKeyboards: return "Active Keyboards"
case .userDefaults: return "User Defaults"
}
}

/// The possible reasons that this category of API may be accessed.
ncooke3 marked this conversation as resolved.
Show resolved Hide resolved
/// The below relationships are documented at
/// https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api
public var possibleReasons: [Reason] {
// TODO(ncooke3): It would be nice if reasons were scoped to categories
// in a type-safe way.
switch self {
case .fileTimestamp: return [._DDA9_1, ._C617_1, ._3B52_1]
case .systemBootTime: return [._35F9_1]
case .diskSpace: return [._85F4_1, ._E174_1]
case .activeKeyboards: return [._3EC4_1, ._54BD_1]
case .userDefaults: return [._CA92_1]
}
}

/// The symbols associated with this catergory that require a reason for use.
public var associatedSymbols: [String] {
switch self {
case .fileTimestamp:
return [
"NSFileCreationDate",
"NSFileModificationDate",
"NSURLContentModificationDateKey",
"NSURLCreationDateKey",
"getattrlist",
"getattrlistbulk",
"fgetattrlist",
"stat",
"fstat",
"fstatat",
"lstat",
"getattrlistat",
"creationDate",
"modificationDate",
"fileModificationDate",
"contentModificationDateKey",
"creationDateKey",
]
case .systemBootTime:
return [
"systemUptime",
"mach_absolute_time",
]
case .diskSpace:
return [
"NSURLVolumeAvailableCapacityKey",
"NSURLVolumeAvailableCapacityForImportantUsageKey",
"NSURLVolumeAvailableCapacityForOpportunisticUsageKey",
"NSURLVolumeTotalCapacityKey",
"NSFileSystemFreeSize",
"NSFileSystemSize",
"statfs",
"statvfs",
"fstatfs",
"fstatvfs",
"getattrlist",
"fgetattrlist",
"getattrlistat",
"volumeAvailableCapacityKey",
"volumeAvailableCapacityForImportantUsageKey",
"volumeAvailableCapacityForOpportunisticUsageKey",
"volumeTotalCapacityKey",
"systemFreeSize",
"systemSize",
]
case .activeKeyboards:
return ["activeInputModes"]
case .userDefaults:
return ["NSUserDefaults", "UserDefaults"]
}
}
}

public enum Reason: String, Encodable {
// File timestamp APIs
case _DDA9_1 = "DDA9.1"
case _C617_1 = "C617.1"
case _3B52_1 = "3B52.1"
ncooke3 marked this conversation as resolved.
Show resolved Hide resolved
// System boot time APIs
case _35F9_1 = "35F9.1"
// Disk space APIs
case _85F4_1 = "85F4.1"
case _E174_1 = "E174.1"
// Active keyboard APIs
case _3EC4_1 = "3EC4.1"
case _54BD_1 = "54BD.1"
// User defaults APIs
case _CA92_1 = "CA92.1"

public var description: String {
switch self {
case ._DDA9_1:
return "Declare this reason to display file timestamps to the person " +
"using the device. Information accessed for this reason, or any " +
"derived information, may not be sent off-device."
case ._C617_1:
return "Declare this reason to access the timestamps of files inside " +
"the app container, app group container, or the app’s CloudKit " +
"container."
case ._3B52_1:
return "Declare this reason to access the timestamps of files or " +
"directories that the user specifically granted access to, such " +
"as using a document picker view controller."
case ._35F9_1:
return "Declare this reason to access the system boot time in order " +
"to measure the amount of time that has elapsed between events " +
"that occurred within the app or to perform calculations to enable " +
"timers. Information accessed for this reason, or any derived " +
"information, may not be sent off-device. There is an exception " +
"for information about the amount of time that has elapsed " +
"between events that occurred within the app, which may be sent " +
"off-device."
case ._85F4_1:
return "Declare this reason to display disk space information to the " +
"person using the device. Disk space may be displayed in units of " +
"information (such as bytes) or units of time combined with a " +
"media type (such as minutes of HD video). Information accessed " +
"for this reason, or any derived information, may not be sent " +
"off-device."
case ._E174_1:
return "Declare this reason to check whether there is sufficient " +
"disk space to write files, or to check whether the disk space is " +
"low so that the app can delete files when the disk space is low. " +
"The app must behave differently based on disk space in a way that " +
"is observable to users. Information accessed for this reason, or " +
"any derived information, may not be sent off-device. There is an " +
"exception that allows the app to avoid downloading files from a " +
"server when disk space is insufficient."
case ._3EC4_1:
return "Declare this reason if your app is a custom keyboard app, " +
"and you access this API category to determine the keyboards that " +
"are active on the device. Providing a systemwide custom keyboard " +
"to the user must be the primary functionality of the app. " +
"Information accessed for this reason, or any derived " +
"information, may not be sent off-device."
case ._54BD_1:
return "Declare this reason to access active keyboard information to " +
"present the correct customized user interface to the person " +
"using the device. The app must have text fields for entering or " +
"editing text and must behave differently based on active " +
"keyboards in a way that is observable to users. Information " +
"accessed for this reason, or any derived information, may not be " +
"sent off-device."
case ._CA92_1:
return "Declare this reason to access user defaults to read and " +
"write information that is only accessible to the app itself. This " +
"reason does not permit reading information that was written by " +
"other apps or the system, or writing information that can be " +
"accessed by other apps."
}
}
}

public let type: Category
public let reasons: [Reason]

public init(type: Category, reasons: [Reason]) {
self.type = type
self.reasons = reasons
}

private enum CodingKeys: String, CodingKey {
case type = "NSPrivacyAccessedAPIType"
case reasons = "NSPrivacyAccessedAPITypeReasons"
}
}
Loading
Loading