app_permission_statistics 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|