module Fastlane module Actions class DownloadAppPrivacyDetailsFromAppStoreAction < Action DEFAULT_PATH = Fastlane::Helper.fastlane_enabled_folder_path DEFAULT_FILE_NAME = "app_privacy_details.json" def self.run(params) require 'spaceship' # Prompts select team if multiple teams and none specified UI.message("Login to App Store Connect (#{params[:username]})") Spaceship::ConnectAPI.login(params[:username], use_portal: false, use_tunes: true, tunes_team_id: params[:team_id], team_name: params[:team_name]) UI.message("Login successful") # Get App app = Spaceship::ConnectAPI::App.find(params[:app_identifier]) unless app UI.user_error!("Could not find app with bundle identifier '#{params[:app_identifier]}' on account #{params[:username]}") end # Download usages and return a config raw_usages = download_app_data_usages(params, app) usages_config = [] if raw_usages.count == 1 && raw_usages.first.data_protection.id == Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_NOT_COLLECTED usages_config << { "data_protections" => [Spaceship::ConnectAPI::AppDataUsageDataProtection::ID::DATA_NOT_COLLECTED] } else grouped_usages = raw_usages.group_by do |usage| usage.category.id end grouped_usages.sort_by(&:first).each do |key, usage_group| purposes = usage_group.map(&:purpose).compact || [] data_protections = usage_group.map(&:data_protection).compact || [] usages_config << { "category" => key, "purposes" => purposes.map(&:id).sort.uniq, "data_protections" => data_protections.map(&:id).sort.uniq } end end # Save to JSON file json = JSON.pretty_generate(usages_config) path = output_path(params) UI.message("Writing file to #{path}") File.write(path, json) end def self.output_path(params) path = params[:output_json_path] return File.absolute_path(path) end def self.download_app_data_usages(params, app) UI.message("Downloading App Data Usage") # Delete all existing usages for new ones Spaceship::ConnectAPI::AppDataUsage.all(app_id: app.id, includes: "category,grouping,purpose,dataProtection", limit: 500) end def self.description "Download App Privacy Details from an app in App Store Connect" end def self.available_options user = CredentialsManager::AppfileConfig.try_fetch_value(:itunes_connect_id) user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id) [ FastlaneCore::ConfigItem.new(key: :username, env_name: "FASTLANE_USER", description: "Your Apple ID Username for App Store Connect", default_value: user, default_value_dynamic: true), FastlaneCore::ConfigItem.new(key: :app_identifier, env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_APP_IDENTIFIER", description: "The bundle identifier of your app", code_gen_sensitive: true, default_value: CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier), default_value_dynamic: true), FastlaneCore::ConfigItem.new(key: :team_id, env_name: "FASTLANE_ITC_TEAM_ID", description: "The ID of your App Store Connect team if you're in multiple teams", optional: true, skip_type_validation: true, # as we also allow integers, which we convert to strings anyway code_gen_sensitive: true, default_value: CredentialsManager::AppfileConfig.try_fetch_value(:itc_team_id), default_value_dynamic: true), FastlaneCore::ConfigItem.new(key: :team_name, env_name: "FASTLANE_ITC_TEAM_NAME", description: "The name of your App Store Connect team if you're in multiple teams", optional: true, code_gen_sensitive: true, default_value: CredentialsManager::AppfileConfig.try_fetch_value(:itc_team_name), default_value_dynamic: true), # JSON paths FastlaneCore::ConfigItem.new(key: :output_json_path, env_name: "UPLOAD_APP_PRIVACY_DETAILS_TO_APP_STORE_OUTPUT_JSON_PATH", description: "Path to the app usage data JSON file generated by interactive questions", conflicting_options: [:skip_json_file_saving], default_value: File.join(DEFAULT_PATH, DEFAULT_FILE_NAME)) ] end def self.author "igor-makarov" end def self.is_supported?(platform) [:ios, :mac, :tvos].include?(platform) end def self.details "Download App Privacy Details from an app in App Store Connect. For more detail information, view https://docs.fastlane.tools/uploading-app-privacy-details" end def self.example_code [ 'download_app_privacy_details_from_app_store( username: "your@email.com", team_name: "Your Team", app_identifier: "com.your.bundle" )', 'download_app_privacy_details_from_app_store( username: "your@email.com", team_name: "Your Team", app_identifier: "com.your.bundle", output_json_path: "fastlane/app_data_usages.json" )' ] end def self.category :production end end end end