app_permission_statistics 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +8 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +33 -0
- data/README.md +124 -0
- data/Rakefile +4 -0
- data/analyze_report +481 -0
- data/app_permission_statistics.gemspec +39 -0
- data/bin/app_permission_statistics +41 -0
- data/bin/setup +8 -0
- data/lib/app_permission_statistics/analyze.rb +220 -0
- data/lib/app_permission_statistics/core_ext/inflector.rb +35 -0
- data/lib/app_permission_statistics/core_ext/try.rb +112 -0
- data/lib/app_permission_statistics/entitlements.rb +185 -0
- data/lib/app_permission_statistics/extracter.rb +135 -0
- data/lib/app_permission_statistics/helper.rb +90 -0
- data/lib/app_permission_statistics/info_plist.rb +202 -0
- data/lib/app_permission_statistics/mobile_provision.rb +317 -0
- data/lib/app_permission_statistics/version.rb +5 -0
- data/lib/app_permission_statistics.rb +49 -0
- metadata +127 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/app_permission_statistics/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "app_permission_statistics"
|
7
|
+
spec.version = AppPermissionStatistics::VERSION
|
8
|
+
spec.authors = ["bin"]
|
9
|
+
spec.email = ["tang.bin@olaola.chat"]
|
10
|
+
|
11
|
+
spec.summary = "app permission statistics"
|
12
|
+
spec.description = "app permission statistics from ipas"
|
13
|
+
spec.homepage = "https://github.com/olaola-chat/cli-app_permission_statistics.git"
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
15
|
+
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/olaola-chat/cli-app_permission_statistics.git"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
24
|
+
end
|
25
|
+
spec.bindir = "bin"
|
26
|
+
spec.executables = ['app_permission_statistics']
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_runtime_dependency "yaml"
|
30
|
+
spec.add_runtime_dependency "crimp"
|
31
|
+
spec.add_dependency 'CFPropertyList', '< 3.1.0', '>= 2.3.4'
|
32
|
+
spec.add_development_dependency 'bundler', '>= 1.12'
|
33
|
+
|
34
|
+
# Uncomment to register a new dependency of your gem
|
35
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
36
|
+
|
37
|
+
# For more information and examples about making a new gem, checkout our
|
38
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "app_permission_statistics"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
if ARGV.length < 2
|
15
|
+
puts "Error: need two ipa path !!"
|
16
|
+
abort
|
17
|
+
end
|
18
|
+
|
19
|
+
if !File.exist?(ARGV.first)
|
20
|
+
puts "Error: #{ARGV.first} not found"
|
21
|
+
abort
|
22
|
+
end
|
23
|
+
|
24
|
+
if !File.exist?(ARGV[1])
|
25
|
+
puts "Error: #{ARGV[1]} not found"
|
26
|
+
abort
|
27
|
+
end
|
28
|
+
|
29
|
+
if ARGV.length == 1
|
30
|
+
actuator = AppPermissionStatistics::Actuator.new(ipaPath)
|
31
|
+
actuator.run
|
32
|
+
elsif ARGV.length == 2
|
33
|
+
actuator = AppPermissionStatistics::Actuator.new(ARGV[0],ARGV[1],report_path: ARGV[2])
|
34
|
+
actuator.run
|
35
|
+
elsif ARGV.length == 3
|
36
|
+
actuator = AppPermissionStatistics::Actuator.new(ARGV[0],ARGV[1],report_path:ARGV[2])
|
37
|
+
actuator.run
|
38
|
+
elsif ARGV.length >= 4
|
39
|
+
actuator = AppPermissionStatistics::Actuator.new(ARGV[0],ARGV[1],report_path:ARGV[2],store_path:ARGV[3])
|
40
|
+
actuator.run
|
41
|
+
end
|
data/bin/setup
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "helper"
|
4
|
+
require 'fileutils'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
module AppPermissionStatistics
|
8
|
+
|
9
|
+
class Analyzer
|
10
|
+
include Helper::Utils
|
11
|
+
|
12
|
+
attr_reader :store_path
|
13
|
+
attr_reader :report_path
|
14
|
+
attr_reader :identifier
|
15
|
+
|
16
|
+
def initialize(identifier,report_path = nil,store_path = nil)
|
17
|
+
@identifier = identifier
|
18
|
+
@report_path = report_path
|
19
|
+
@store_path = store_path
|
20
|
+
end
|
21
|
+
|
22
|
+
def analyze
|
23
|
+
load_version
|
24
|
+
return singleGenerate unless @versions.length > 1
|
25
|
+
pre_version = @versions[1]
|
26
|
+
cur_version = @versions[0]
|
27
|
+
pre_entitlements = load_entitlements_yaml(pre_version)
|
28
|
+
cur_entitlements = load_entitlements_yaml(cur_version)
|
29
|
+
return "" unless pre_entitlements.any?
|
30
|
+
return "" unless cur_entitlements.any?
|
31
|
+
|
32
|
+
capabilities_diff = analyze_capabilities_diff(pre_entitlements,cur_entitlements)
|
33
|
+
usage_descs_diff = analyze_usage_descs_diff(pre_entitlements,cur_entitlements);
|
34
|
+
report_path = generate(capabilities_diff,usage_descs_diff)
|
35
|
+
report_path
|
36
|
+
end
|
37
|
+
|
38
|
+
def singleGenerate
|
39
|
+
return "" unless @versions.length > 0
|
40
|
+
output = "no more versions to compare !! "
|
41
|
+
output += "\n\n\n#{@versions.first} entitlements list: "
|
42
|
+
output += "\n" + read_each_line(@versions.first)
|
43
|
+
output += "\n" + "----------------------------------------"
|
44
|
+
report_path = report_file_name(path: @report_path)
|
45
|
+
File.open(report_path, 'w') { |file|
|
46
|
+
file.write(output)
|
47
|
+
}
|
48
|
+
report_path
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def generate(capabilities_diff,usage_descs_diff)
|
53
|
+
return unless @versions.length > 1
|
54
|
+
cur_version = @versions[0]
|
55
|
+
pre_version = @versions[1]
|
56
|
+
|
57
|
+
output = "\n" + "compared #{cur_version} #{pre_version}"
|
58
|
+
output += "\n" + capabilities_diff unless capabilities_diff.empty?
|
59
|
+
output += "\n" + usage_descs_diff unless usage_descs_diff.empty?
|
60
|
+
output += "\n\nThere is no difference between the two versions \n\n" unless (!capabilities_diff.empty? || !usage_descs_diff.empty?)
|
61
|
+
puts output
|
62
|
+
puts "----------------------------------------"
|
63
|
+
output += "\n\n\n#{cur_version} entitlements list: "
|
64
|
+
output += "\n" + read_each_line(cur_version)
|
65
|
+
output += "\n" + "----------------------------------------"
|
66
|
+
output += "\n\n#{pre_version} entitlements list: "
|
67
|
+
output += "\n" + read_each_line(pre_version)
|
68
|
+
output += "\n" + "----------------------------------------"
|
69
|
+
|
70
|
+
report_path = report_file_name(path: @report_path)
|
71
|
+
File.open(report_path, 'w') { |file|
|
72
|
+
file.write(output)
|
73
|
+
}
|
74
|
+
puts "detail file: #{File.expand_path(report_path)}"
|
75
|
+
report_path
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_each_line(version)
|
79
|
+
file_name = entitlements_yaml_name(version,@identifier, path: @store_path)
|
80
|
+
file_content = ""
|
81
|
+
File.open(file_name,"r").each_line do |line|
|
82
|
+
file_content += "\n" + line
|
83
|
+
end
|
84
|
+
file_content
|
85
|
+
end
|
86
|
+
|
87
|
+
def load_version
|
88
|
+
yaml_name = versions_yaml_name(@identifier, path: @store_path)
|
89
|
+
if File.exist?(yaml_name)
|
90
|
+
@versions = YAML.load_file(yaml_name)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def load_entitlements_yaml(version)
|
95
|
+
yaml_name = entitlements_yaml_name(version,@identifier, path: @store_path)
|
96
|
+
yaml_content = [ ]
|
97
|
+
if File.exist?(yaml_name)
|
98
|
+
yaml_content = YAML.load_file(yaml_name)
|
99
|
+
end
|
100
|
+
puts "#{yaml_name} not found!!!" unless !yaml_content.empty?
|
101
|
+
yaml_content
|
102
|
+
end
|
103
|
+
|
104
|
+
def analyze_capabilities_diff(pre,cur)
|
105
|
+
output = ""
|
106
|
+
cur_capabilities_summary = cur['Capabilities_Summary']
|
107
|
+
pre_capabilities_summary = pre['Capabilities_Summary']
|
108
|
+
cur_capabilities = cur['Capabilities']
|
109
|
+
pre_capabilities = pre['Capabilities']
|
110
|
+
output = ""
|
111
|
+
# 修改
|
112
|
+
comm_capabilities = cur_capabilities_summary.keys & pre_capabilities_summary.keys
|
113
|
+
modifys = modifys_cur = modifys_pre = ""
|
114
|
+
comm_capabilities.each do |key|
|
115
|
+
if cur_capabilities_summary[key] != pre_capabilities_summary[key]
|
116
|
+
modifys_cur += "\n" + '- ' + key + ': ' + cur_capabilities[key].to_s
|
117
|
+
modifys_pre += "\n" + '- ' + key + ': ' + pre_capabilities[key].to_s
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
modifys += "\n" + @versions[0] unless modifys_cur.empty?
|
122
|
+
modifys += modifys_cur unless modifys_cur.empty?
|
123
|
+
modifys += "\n" + @versions[1] unless modifys_pre.empty?
|
124
|
+
modifys += modifys_pre unless modifys_pre.empty?
|
125
|
+
|
126
|
+
#新增
|
127
|
+
add_capabilities = cur_capabilities_summary.keys - pre_capabilities_summary.keys
|
128
|
+
adds = ""
|
129
|
+
add_capabilities.each do |key|
|
130
|
+
adds += "\n" + '- ' + key + ': ' + cur_capabilities[key].to_s
|
131
|
+
end
|
132
|
+
|
133
|
+
#移除
|
134
|
+
remove_capabilities = pre_capabilities_summary.keys - cur_capabilities_summary.keys
|
135
|
+
removes = ""
|
136
|
+
remove_capabilities.each do |key|
|
137
|
+
removes += "\n" + '- ' + key + ': ' + pre_capabilities[key].to_s
|
138
|
+
end
|
139
|
+
|
140
|
+
if !modifys.empty?
|
141
|
+
output += "\n" + "modify capabilitys : "
|
142
|
+
output += modifys
|
143
|
+
output += "\n------------------------------"
|
144
|
+
end
|
145
|
+
|
146
|
+
if !adds.empty?
|
147
|
+
output += "\n" + "add capabilitys : "
|
148
|
+
output += adds
|
149
|
+
output += "\n------------------------------"
|
150
|
+
end
|
151
|
+
|
152
|
+
if !removes.empty?
|
153
|
+
output += "\n" + "remove capabilitys : "
|
154
|
+
output += removes
|
155
|
+
output += "\n------------------------------"
|
156
|
+
end
|
157
|
+
|
158
|
+
output
|
159
|
+
end
|
160
|
+
|
161
|
+
def analyze_usage_descs_diff(pre,cur)
|
162
|
+
output = ""
|
163
|
+
cur_usage_descs_summary = cur['PermissionsUsageDescription_Summary']
|
164
|
+
pre_usage_descs_summary = pre['PermissionsUsageDescription_Summary']
|
165
|
+
|
166
|
+
cur_usage_descs = cur['PermissionsUsageDescription']
|
167
|
+
pre_usage_descs = pre['PermissionsUsageDescription']
|
168
|
+
|
169
|
+
# 修改
|
170
|
+
comm_usage_descs = cur_usage_descs_summary.keys & pre_usage_descs_summary.keys
|
171
|
+
modifys = modifys_cur = modifys_pre = ""
|
172
|
+
comm_usage_descs.each do |key|
|
173
|
+
if cur_usage_descs_summary[key] != pre_usage_descs_summary[key]
|
174
|
+
modifys_cur += "\n" + '- ' + key + ': ' + cur_usage_descs[key].to_s
|
175
|
+
modifys_pre += "\n" + '- ' + key + ': ' + pre_usage_descs[key].to_s
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
modifys += "\n" + @versions[0] unless modifys_cur.empty?
|
180
|
+
modifys += modifys_cur unless modifys_cur.empty?
|
181
|
+
modifys += "\n" + @versions[1] unless modifys_pre.empty?
|
182
|
+
modifys += modifys_pre unless modifys_pre.empty?
|
183
|
+
|
184
|
+
#新增
|
185
|
+
add_usage_descs = cur_usage_descs_summary.keys - pre_usage_descs_summary.keys
|
186
|
+
adds = ""
|
187
|
+
add_usage_descs.each do |key|
|
188
|
+
adds += "\n" + '- ' + key + ': ' + cur_usage_descs[key].to_s
|
189
|
+
end
|
190
|
+
|
191
|
+
#移除
|
192
|
+
remove_usage_descs = pre_usage_descs_summary.keys - cur_usage_descs_summary.keys
|
193
|
+
removes = ""
|
194
|
+
remove_usage_descs.each do |key|
|
195
|
+
removes += "\n" + '- ' + key + ': ' + pre_usage_descs[key].to_s
|
196
|
+
end
|
197
|
+
|
198
|
+
if !modifys.empty?
|
199
|
+
output += "\n" + "modify permissions usage description : "
|
200
|
+
output += modifys
|
201
|
+
output += "\n------------------------------"
|
202
|
+
end
|
203
|
+
|
204
|
+
if !adds.empty?
|
205
|
+
output += "\n" + "add permissions usage description : "
|
206
|
+
output += adds
|
207
|
+
output += "\n------------------------------"
|
208
|
+
end
|
209
|
+
|
210
|
+
if !removes.empty?
|
211
|
+
output += "\n" + "remove permissions usage description : "
|
212
|
+
output += removes
|
213
|
+
output += "\n------------------------------"
|
214
|
+
end
|
215
|
+
|
216
|
+
output
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AppPermissionStatistics
|
4
|
+
module Inflector
|
5
|
+
def ai_snakecase
|
6
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
7
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
8
|
+
.tr('-', '_')
|
9
|
+
.gsub(/\s/, '_')
|
10
|
+
.gsub(/__+/, '_')
|
11
|
+
.downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def ai_camelcase(first_letter: :upper, separators: ['-', '_', '\s'])
|
15
|
+
str = dup
|
16
|
+
|
17
|
+
separators.each do |s|
|
18
|
+
str = str.gsub(/(?:#{s}+)([a-z])/) { $1.upcase }
|
19
|
+
end
|
20
|
+
|
21
|
+
case first_letter
|
22
|
+
when :upper, true
|
23
|
+
str = str.gsub(/(\A|\s)([a-z])/) { $1 + $2.upcase }
|
24
|
+
when :lower, false
|
25
|
+
str = str.gsub(/(\A|\s)([A-Z])/) { $1 + $2.downcase }
|
26
|
+
end
|
27
|
+
|
28
|
+
str
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class String
|
34
|
+
include AppPermissionStatistics::Inflector
|
35
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyrights rails
|
4
|
+
# Copy from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/try.rb
|
5
|
+
|
6
|
+
module AppPermissionStatistics
|
7
|
+
module Tryable # :nodoc:
|
8
|
+
##
|
9
|
+
# :method: try
|
10
|
+
#
|
11
|
+
# :call-seq:
|
12
|
+
# try(*a, &b)
|
13
|
+
#
|
14
|
+
# Invokes the public method whose name goes as first argument just like
|
15
|
+
# +public_send+ does, except that if the receiver does not respond to it the
|
16
|
+
# call returns +nil+ rather than raising an exception.
|
17
|
+
#
|
18
|
+
# This method is defined to be able to write
|
19
|
+
#
|
20
|
+
# @person.try(:name)
|
21
|
+
#
|
22
|
+
# instead of
|
23
|
+
#
|
24
|
+
# @person.name if @person
|
25
|
+
#
|
26
|
+
# +try+ calls can be chained:
|
27
|
+
#
|
28
|
+
# @person.try(:spouse).try(:name)
|
29
|
+
#
|
30
|
+
# instead of
|
31
|
+
#
|
32
|
+
# @person.spouse.name if @person && @person.spouse
|
33
|
+
#
|
34
|
+
# +try+ will also return +nil+ if the receiver does not respond to the method:
|
35
|
+
#
|
36
|
+
# @person.try(:non_existing_method) # => nil
|
37
|
+
#
|
38
|
+
# instead of
|
39
|
+
#
|
40
|
+
# @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
|
41
|
+
#
|
42
|
+
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
|
43
|
+
# to the method:
|
44
|
+
#
|
45
|
+
# nil.try(:to_i) # => nil, rather than 0
|
46
|
+
#
|
47
|
+
# Arguments and blocks are forwarded to the method if invoked:
|
48
|
+
#
|
49
|
+
# @posts.try(:each_slice, 2) do |a, b|
|
50
|
+
# ...
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# The number of arguments in the signature must match. If the object responds
|
54
|
+
# to the method the call is attempted and +ArgumentError+ is still raised
|
55
|
+
# in case of argument mismatch.
|
56
|
+
#
|
57
|
+
# If +try+ is called without arguments it yields the receiver to a given
|
58
|
+
# block unless it is +nil+:
|
59
|
+
#
|
60
|
+
# @person.try do |p|
|
61
|
+
# ...
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# You can also call try with a block without accepting an argument, and the block
|
65
|
+
# will be instance_eval'ed instead:
|
66
|
+
#
|
67
|
+
# @person.try { upcase.truncate(50) }
|
68
|
+
#
|
69
|
+
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
|
70
|
+
# with instances of classes that do not have +Object+ among their ancestors,
|
71
|
+
# like direct subclasses of +BasicObject+.
|
72
|
+
def try(method_name = nil, *args, &block)
|
73
|
+
if method_name.nil? && block_given?
|
74
|
+
if block.arity.zero?
|
75
|
+
instance_eval(&b)
|
76
|
+
else
|
77
|
+
yield self
|
78
|
+
end
|
79
|
+
elsif respond_to?(method_name)
|
80
|
+
public_send(method_name, *args, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# :method: try!
|
86
|
+
#
|
87
|
+
# :call-seq:
|
88
|
+
# try!(*a, &b)
|
89
|
+
#
|
90
|
+
# Same as #try, but raises a +NoMethodError+ exception if the receiver is
|
91
|
+
# not +nil+ and does not implement the tried method.
|
92
|
+
#
|
93
|
+
# "a".try!(:upcase) # => "A"
|
94
|
+
# nil.try!(:upcase) # => nil
|
95
|
+
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
|
96
|
+
def try!(method_name = nil, *args, &block)
|
97
|
+
if method_name.nil? && block_given?
|
98
|
+
if block.arity.zero?
|
99
|
+
instance_eval(&block)
|
100
|
+
else
|
101
|
+
yield self
|
102
|
+
end
|
103
|
+
else
|
104
|
+
public_send(method_name, *args, &block)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class Object
|
111
|
+
include AppPermissionStatistics::Tryable
|
112
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'cfpropertylist'
|
5
|
+
|
6
|
+
module AppPermissionStatistics
|
7
|
+
|
8
|
+
# iOS app.entitlements parser
|
9
|
+
class EntitlementsPlist
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def initialize(file)
|
13
|
+
@file = file
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Extract the capabilities
|
18
|
+
# https://developer.apple.com/help/account/reference/supported-capabilities-ios
|
19
|
+
# https://developer.apple.com/documentation/bundleresources/entitlements
|
20
|
+
# https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AboutEntitlements.html
|
21
|
+
# a few entitlements are inherited from the iOS provisioning profile used to run the app.
|
22
|
+
#
|
23
|
+
def enabled_capabilities
|
24
|
+
capabilities = Hash.new
|
25
|
+
info.each do |key, value|
|
26
|
+
case key
|
27
|
+
when 'com.apple.developer.game-center'
|
28
|
+
capabilities['Game Center'] = {
|
29
|
+
key => value
|
30
|
+
}
|
31
|
+
when 'keychain-access-groups'
|
32
|
+
capabilities['Keychain sharing'] = {
|
33
|
+
key => value
|
34
|
+
}
|
35
|
+
when 'aps-environment'
|
36
|
+
capabilities['Push Notifications'] = {
|
37
|
+
key => value
|
38
|
+
}
|
39
|
+
when 'com.apple.developer.applesignin'
|
40
|
+
capabilities['Sign In with Apple'] = {
|
41
|
+
key => value
|
42
|
+
}
|
43
|
+
when 'com.apple.developer.siri'
|
44
|
+
capabilities['SiriKit'] = {
|
45
|
+
key => value
|
46
|
+
}
|
47
|
+
when 'com.apple.security.application-groups'
|
48
|
+
capabilities['App Groups'] = {
|
49
|
+
key => value
|
50
|
+
}
|
51
|
+
when 'com.apple.developer.associated-domains'
|
52
|
+
capabilities['Associated Domains'] = {
|
53
|
+
key => value
|
54
|
+
}
|
55
|
+
when 'com.apple.developer.default-data-protection'
|
56
|
+
capabilities['Data Protection'] = {
|
57
|
+
key => value
|
58
|
+
}
|
59
|
+
when 'com.apple.developer.networking.networkextension'
|
60
|
+
capabilities ['Network Extensions'] = {
|
61
|
+
key => value
|
62
|
+
}
|
63
|
+
when 'com.apple.developer.networking.vpn.api'
|
64
|
+
capabilities ['Personal VPN'] = {
|
65
|
+
key => value
|
66
|
+
}
|
67
|
+
when 'com.apple.developer.healthkit',
|
68
|
+
'com.apple.developer.healthkit.access'
|
69
|
+
capabilities['HealthKit'] = {
|
70
|
+
'com.apple.developer.healthkit' => info['com.apple.developer.healthkit'],
|
71
|
+
'com.apple.developer.healthkit.access' => info['com.apple.developer.healthkit.access'],
|
72
|
+
} unless capabilities.include?('HealthKit')
|
73
|
+
when 'com.apple.developer.icloud-services',
|
74
|
+
'com.apple.developer.icloud-container-identifiers'
|
75
|
+
capabilities['iCloud'] = {
|
76
|
+
'com.apple.developer.icloud-services' => info['com.apple.developer.icloud-services'],
|
77
|
+
'com.apple.developer.icloud-container-identifiers' => info['com.apple.developer.icloud-container-identifiers'],
|
78
|
+
} unless capabilities.include?('iCloud')
|
79
|
+
when 'com.apple.developer.in-app-payments'
|
80
|
+
capabilities['Apple Pay'] = {
|
81
|
+
key => value
|
82
|
+
}
|
83
|
+
when 'com.apple.developer.homekit'
|
84
|
+
capabilities['HomeKit'] = {
|
85
|
+
key => value
|
86
|
+
}
|
87
|
+
when 'com.apple.developer.user-fonts'
|
88
|
+
capabilities['Fonts'] = {
|
89
|
+
key => value
|
90
|
+
}
|
91
|
+
when 'com.apple.developer.pass-type-identifiers'
|
92
|
+
capabilities['Wallet'] = {
|
93
|
+
key => value
|
94
|
+
}
|
95
|
+
when 'inter-app-audio'
|
96
|
+
capabilities['Inter-App Audio'] = {
|
97
|
+
key => value
|
98
|
+
}
|
99
|
+
when 'com.apple.developer.networking.multipath'
|
100
|
+
capabilities['Multipath'] = {
|
101
|
+
key => value
|
102
|
+
}
|
103
|
+
when 'com.apple.developer.authentication-services.autofill-credential-provider'
|
104
|
+
capabilities['AutoFill Credential Provider'] = {
|
105
|
+
key => value
|
106
|
+
}
|
107
|
+
when 'com.apple.developer.networking.wifi-info'
|
108
|
+
capabilities['Access WiFi Information'] = {
|
109
|
+
key => value
|
110
|
+
}
|
111
|
+
when 'com.apple.external-accessory.wireless-configuration'
|
112
|
+
capabilities['Wireless Accessory Configuration'] = {
|
113
|
+
key => value
|
114
|
+
}
|
115
|
+
when 'com.apple.developer.kernel.extended-virtual-addressing'
|
116
|
+
capabilities['Extended Virtual Address Space'] = {
|
117
|
+
key => value
|
118
|
+
}
|
119
|
+
when 'com.apple.developer.nfc.readersession.formats'
|
120
|
+
capabilities['NFC Tag Reading'] = {
|
121
|
+
key => value
|
122
|
+
}
|
123
|
+
when 'com.apple.developer.ClassKit-environment'
|
124
|
+
capabilities['ClassKit'] = {
|
125
|
+
key => value
|
126
|
+
}
|
127
|
+
when 'com.apple.developer.networking.HotspotConfiguration'
|
128
|
+
capabilities['Hotspot'] = {
|
129
|
+
key => value
|
130
|
+
}
|
131
|
+
when 'com.apple.developer.devicecheck.appattest-environment'
|
132
|
+
capabilities['App Attest'] = {
|
133
|
+
key => value
|
134
|
+
}
|
135
|
+
when 'com.apple.developer.coremedia.hls.low-latency'
|
136
|
+
capabilities['Low Latency HLS'] = {
|
137
|
+
key => value
|
138
|
+
}
|
139
|
+
when 'com.apple.developer.associated-domains.mdm-managed'
|
140
|
+
capabilities['MDM Managed Associated Domains'] = {
|
141
|
+
key => value
|
142
|
+
}
|
143
|
+
end
|
144
|
+
end
|
145
|
+
capabilities
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def game_center
|
150
|
+
info.try(:[], 'com.apple.developer.game-center').nil?
|
151
|
+
end
|
152
|
+
|
153
|
+
def keychain_access_groups
|
154
|
+
info.try(:[], 'keychain-access-groups').nil?
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
def [](key)
|
159
|
+
info.try(:[], key.to_s)
|
160
|
+
end
|
161
|
+
|
162
|
+
def_delegators :info, :to_h
|
163
|
+
|
164
|
+
def method_missing(method_name, *args, &block)
|
165
|
+
info.try(:[], method_name.to_s.ai_camelcase) ||
|
166
|
+
info.send(method_name) ||
|
167
|
+
super
|
168
|
+
end
|
169
|
+
|
170
|
+
def respond_to_missing?(method_name, *args)
|
171
|
+
info.key?(method_name.to_s.ai_camelcase) ||
|
172
|
+
info.respond_to?(method_name) ||
|
173
|
+
super
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
def info
|
179
|
+
return unless File.file?(@file)
|
180
|
+
|
181
|
+
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: @file).value)
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|