cocoapods-entitlements-statistics 0.0.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.
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'cfpropertylist'
5
+
6
+ module AppEntitlementsStatistics
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
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "mobile_provision"
4
+ require_relative "info_plist"
5
+ require_relative "entitlements"
6
+ require_relative "helper"
7
+
8
+ require 'fileutils'
9
+ require 'forwardable'
10
+
11
+ module AppEntitlementsStatistics
12
+
13
+ class Extracter
14
+ include Helper::Sigflat
15
+ include Helper::Utils
16
+ extend Forwardable
17
+
18
+ attr_reader :system_capabilities
19
+ attr_reader :info_plist_path
20
+ attr_reader :entitlements_path
21
+ attr_reader :identifier
22
+ attr_reader :cur_version
23
+
24
+ def initialize(args)
25
+ args.each do |key ,value|
26
+ case key
27
+ when "cur_version"
28
+ @cur_version = value
29
+ when "product_bundle_identifier"
30
+ @identifier = value
31
+ when "info_plist_path"
32
+ @info_plist_path = value
33
+ when "entitlements_path"
34
+ @entitlements_path = value
35
+ when "system_capabilities"
36
+ @system_capabilities = value
37
+ end
38
+ end
39
+ end
40
+
41
+ def extract_update
42
+ capabilities_summary = Hash.new
43
+ capabilities = extract_capabilities_info
44
+ capabilities.each do |key,value|
45
+ capabilities_summary[key] = createsig(value)
46
+ end
47
+
48
+ usage_desc_summary = Hash.new
49
+ usage_desc = plistInfo.permis_usagedescription
50
+ usage_desc.each do |key,value|
51
+ usage_desc_summary[key] = createsig(value)
52
+ end
53
+
54
+ yaml_content = {
55
+ 'Capabilities_Summary' => capabilities_summary,
56
+ 'Capabilities' => capabilities,
57
+ 'PermissionsUsageDescription_Summary' => usage_desc_summary,
58
+ 'PermissionsUsageDescription' => usage_desc
59
+ }
60
+ yaml_name = entitlements_yaml_name(@cur_version, @identifier , path: @store_path)
61
+
62
+ File.open(yaml_name, "w") { |file| file.write(yaml_content.to_yaml) }
63
+ update_versions
64
+ yaml_content
65
+ end
66
+
67
+
68
+ def update_versions
69
+ yaml_name = versions_yaml_name(@identifier , path: @store_path)
70
+ yaml_content = [ ]
71
+ if File.exist?(yaml_name)
72
+ yaml_content = YAML.load_file(yaml_name)
73
+ end
74
+ version = @cur_version
75
+ if !yaml_content.include?(version)
76
+ yaml_content.unshift(version)
77
+ File.open(yaml_name, "w") { |file| file.write(yaml_content.to_yaml) }
78
+ end
79
+ end
80
+
81
+ def extract_capabilities_info
82
+ #{"com.apple.BackgroundModes"=>{"enabled"=>"1"}, "com.apple.InAppPurchase"=>{"enabled"=>"1"}, "com.apple.Push"=>{"enabled"=>"1"}}
83
+ capabilities_info = {}
84
+
85
+ @system_capabilities.each do |key,value|
86
+ case key
87
+ when "com.apple.InAppPurchase"
88
+ capabilities_info["In-App Purchase"] = { key => value }
89
+ when "com.apple.BackgroundModes"
90
+ h1 = {key => value}
91
+ h2 = plistInfo.backgroundModes["Background Modes"]
92
+ capabilities_info["Background Modes"] = h1.merge(h2)
93
+ when "com.apple.Push"
94
+ capabilities_info["Push Notifications"] = { key => value }
95
+ end
96
+ end
97
+ capabilities_info = capabilities_info.merge(entitlementsInfo.enabled_capabilities)
98
+ capabilities_info = capabilities_info.merge(plistInfo.enabled_capabilities)
99
+ capabilities_info
100
+ end
101
+
102
+ def plistInfo
103
+ @plistInfo ||= InfoPlist.new(@info_plist_path)
104
+ end
105
+
106
+ def entitlementsInfo
107
+ @entitlementsInfo ||= EntitlementsPlist.new(@entitlements_path)
108
+ end
109
+
110
+ end
111
+
112
+ end
@@ -0,0 +1,90 @@
1
+ require 'tmpdir'
2
+ require 'crimp'
3
+ require 'cocoapods'
4
+
5
+
6
+ module AppEntitlementsStatistics
7
+
8
+ module Helper
9
+
10
+ module Sigflat
11
+ def createsig(body)
12
+ return Crimp.signature(body)
13
+ end
14
+ end
15
+
16
+ module Utils
17
+
18
+ def defalut_store_path(app_identifier)
19
+ root_path = "#{Dir.home}/appInfo-#{app_identifier}"
20
+ FileUtils.mkdir_p(root_path) unless File.exists?(root_path)
21
+ root_path
22
+ end
23
+
24
+ def entitlements_yaml_name(v,app_identifier,path: nil)
25
+ dir_path = path ? "#{path}-#{app_identifier}" : defalut_store_path(app_identifier)
26
+ yaml_name = "#{dir_path}/entitlements_#{v}.yml";
27
+ yaml_name
28
+ end
29
+
30
+ def versions_yaml_name(app_identifier,path: nil)
31
+ dir_path = path ? "#{path}-#{app_identifier}" : defalut_store_path(app_identifier)
32
+ yaml_name = "#{dir_path}/entitlements_versions.yml";
33
+ yaml_name
34
+ end
35
+
36
+ def report_file_name(path: nil)
37
+ path = path ? "#{path}/entitlements_statistics" : "entitlements_statistics"
38
+ FileUtils.mkdir_p(path) unless File.exists?(path)
39
+ file_name = "#{path}/analyze_report"
40
+ file_name
41
+ end
42
+
43
+ end
44
+
45
+ module Archive
46
+ require 'zip'
47
+ require 'fileutils'
48
+ require 'securerandom'
49
+
50
+ # Unarchive zip file
51
+ #
52
+ # source: https://github.com/soffes/lagunitas/blob/master/lib/lagunitas/ipa.rb
53
+ def unarchive(file, path: nil)
54
+ path = path ? "#{path}-" : ''
55
+ root_path = "#{Dir.mktmpdir}/AppInfo-#{path}#{SecureRandom.hex}"
56
+ # puts root_path
57
+ Zip::File.open(file) do |zip_file|
58
+ if block_given?
59
+ yield root_path, zip_file
60
+ else
61
+ zip_file.each do |f|
62
+ f_path = File.join(root_path, f.name)
63
+ FileUtils.mkdir_p(File.dirname(f_path))
64
+ zip_file.extract(f, f_path) unless File.exist?(f_path)
65
+ end
66
+ end
67
+ end
68
+
69
+ root_path
70
+ end
71
+
72
+
73
+ def tempdir(file, prefix:, system: false)
74
+ dest_path = if system
75
+ Dir.mktmpdir("appinfo-#{prefix}-#{File.basename(file, '.*')}-", '/tmp')
76
+ else
77
+ File.join(File.dirname(file), prefix)
78
+ end
79
+
80
+ dest_file = File.join(dest_path, File.basename(file))
81
+ FileUtils.mkdir_p(dest_path, mode: 0_700) unless system
82
+ dest_file
83
+ end
84
+
85
+ end
86
+
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'cfpropertylist'
5
+
6
+ module AppEntitlementsStatistics
7
+
8
+ # Apple Device Type
9
+ module Device
10
+ MACOS = 'macOS'
11
+ IPHONE = 'iPhone'
12
+ IPAD = 'iPad'
13
+ UNIVERSAL = 'Universal'
14
+ end
15
+
16
+ # iOS Info.plist parser
17
+ class InfoPlist
18
+ extend Forwardable
19
+
20
+ def initialize(file)
21
+ @file = file
22
+ end
23
+
24
+ #
25
+ # Extract the permissions UsageDescription from the Info.plist
26
+ # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html
27
+ #
28
+ def permis_usagedescription
29
+ desc = Hash.new
30
+ info.each do |key, value|
31
+ if key.include?("UsageDescription")
32
+ desc[key] = value
33
+ end
34
+ end
35
+ desc
36
+ end
37
+
38
+ def backgroundModes
39
+ value = info.try(:[], 'UIBackgroundModes')
40
+ h = Hash.new
41
+ if !value.nil?
42
+ h["Background Modes"] = {
43
+ "UIBackgroundModes" => value
44
+ }
45
+ end
46
+ h
47
+ end
48
+
49
+ #
50
+ # Extract the capabilities from the Info.plist
51
+ # https://mercury.tencent.com/introduction/show?item=ios
52
+ #
53
+ def enabled_capabilities
54
+ capabilities = Hash.new
55
+ info.each do |key, value|
56
+ case key
57
+ when 'UIRequiredDeviceCapabilities'
58
+ capabilities['RequiredDeviceCapabilities'] = {
59
+ key => value
60
+ }
61
+ when 'UIFileSharingEnabled'
62
+ capabilities['FileSharingEnabled' ] = {
63
+ key => value
64
+ }
65
+ when 'CADisableMinimumFrameDurationOnPhone'
66
+ capabilities['DisableMinimumFrameDurationOnPhone'] = {
67
+ key => value
68
+ }
69
+ when 'GCSupportedGameControllers'
70
+ capabilities['GameControllers'] = {
71
+ key => value
72
+ }
73
+ when 'GCSupportsControllerUserInteraction'
74
+ capabilities['SupportsControllerUserInteraction' ] = {
75
+ key => value
76
+ }
77
+ when 'MKDirectionsApplicationSupportedModes'
78
+ capabilities['Maps'] = {
79
+ key => value
80
+ }
81
+ when 'NSAppTransportSecurity'
82
+ capabilities['AppTransportSecurity'] = {
83
+ key => value
84
+ }
85
+ when 'CFBundleDocumentTypes'
86
+ capabilities['BundleDocumentTypes'] = {
87
+ key => value
88
+ }
89
+ end
90
+ end
91
+ capabilities
92
+ end
93
+
94
+
95
+ def device_type
96
+ device_family = info.try(:[], 'UIDeviceFamily')
97
+ if device_family == [1]
98
+ Device::IPHONE
99
+ elsif device_family == [2]
100
+ Device::IPAD
101
+ elsif device_family == [1, 2]
102
+ Device::UNIVERSAL
103
+ elsif !info.try(:[], 'DTSDKName').nil? || !info.try(:[], 'DTPlatformName').nil?
104
+ Device::MACOS
105
+ end
106
+ end
107
+
108
+ def version
109
+ release_version || build_version
110
+ end
111
+
112
+ def build_version
113
+ info.try(:[], 'CFBundleVersion')
114
+ end
115
+
116
+ def release_version
117
+ info.try(:[], 'CFBundleShortVersionString')
118
+ end
119
+
120
+ def identifier
121
+ info.try(:[], 'CFBundleIdentifier')
122
+ end
123
+ alias bundle_id identifier
124
+
125
+ def name
126
+ display_name || bundle_name
127
+ end
128
+
129
+ def display_name
130
+ info.try(:[], 'CFBundleDisplayName')
131
+ end
132
+
133
+ def bundle_name
134
+ info.try(:[], 'CFBundleName')
135
+ end
136
+
137
+ def min_os_version
138
+ min_sdk_version || min_system_version
139
+ end
140
+
141
+ #
142
+ # Extract the Minimum OS Version from the Info.plist (iOS Only)
143
+ #
144
+ def min_sdk_version
145
+ info.try(:[], 'MinimumOSVersion')
146
+ end
147
+
148
+ #
149
+ # Extract the Minimum OS Version from the Info.plist (macOS Only)
150
+ #
151
+ def min_system_version
152
+ info.try(:[], 'LSMinimumSystemVersion')
153
+ end
154
+
155
+ def iphone?
156
+ device_type == Device::IPHONE
157
+ end
158
+
159
+ def ipad?
160
+ device_type == Device::IPAD
161
+ end
162
+
163
+ def universal?
164
+ device_type == Device::UNIVERSAL
165
+ end
166
+
167
+ def macos?
168
+ device_type == Device::MACOS
169
+ end
170
+
171
+ def device_family
172
+ info.try(:[], 'UIDeviceFamily') || []
173
+ end
174
+
175
+ def [](key)
176
+ info.try(:[], key.to_s)
177
+ end
178
+
179
+ def_delegators :info, :to_h
180
+
181
+ def method_missing(method_name, *args, &block)
182
+ info.try(:[], method_name.to_s.ai_camelcase) ||
183
+ info.send(method_name) ||
184
+ super
185
+ end
186
+
187
+ def respond_to_missing?(method_name, *args)
188
+ info.key?(method_name.to_s.ai_camelcase) ||
189
+ info.respond_to?(method_name) ||
190
+ super
191
+ end
192
+
193
+ private
194
+
195
+ def info
196
+ return unless File.file?(@file)
197
+
198
+ @info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: @file).value)
199
+ end
200
+
201
+ end
202
+ end