app_permission_statistics 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,135 @@
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 AppPermissionStatistics
12
+
13
+ class Extracter
14
+ include Helper::Archive
15
+ include Helper::Sigflat
16
+ include Helper::Utils
17
+ extend Forwardable
18
+ attr_reader :file
19
+ attr_reader :store_path
20
+
21
+ def initialize(file, store_path = nil)
22
+ @file = file
23
+ @store_path = store_path
24
+ end
25
+
26
+ def extract_update
27
+ capabilities_summary = Hash.new
28
+ capabilities = extract_capabilities_info
29
+ capabilities.each do |key,value|
30
+ capabilities_summary[key] = createsig(value)
31
+ end
32
+
33
+ usage_desc_summary = Hash.new
34
+ usage_desc = plistInfo.permis_usagedescription
35
+ usage_desc.each do |key,value|
36
+ usage_desc_summary[key] = createsig(value)
37
+ end
38
+
39
+ yaml_content = {
40
+ 'Capabilities_Summary' => capabilities_summary,
41
+ 'Capabilities' => capabilities,
42
+ 'PermissionsUsageDescription_Summary' => usage_desc_summary,
43
+ 'PermissionsUsageDescription' => usage_desc
44
+ }
45
+ yaml_name = entitlements_yaml_name(plistInfo.version, plistInfo.identifier, path: @store_path)
46
+
47
+ File.open(yaml_name, "w") { |file| file.write(yaml_content.to_yaml) }
48
+ update_versions
49
+ yaml_content
50
+ end
51
+
52
+
53
+ def update_versions
54
+ yaml_name = versions_yaml_name(plistInfo.identifier, path: @store_path)
55
+ yaml_content = [ ]
56
+ if File.exist?(yaml_name)
57
+ yaml_content = YAML.load_file(yaml_name)
58
+ end
59
+ version = plistInfo.version
60
+ if !yaml_content.include?(version)
61
+ yaml_content.unshift(version)
62
+ File.open(yaml_name, "w") { |file| file.write(yaml_content.to_yaml) }
63
+ end
64
+ end
65
+
66
+ def extract_capabilities_info
67
+ capabilities_info = {}
68
+ capabilities_info["In-App Purchase"] = true
69
+ capabilities_info = capabilities_info.merge(entitlementsInfo.enabled_capabilities)
70
+ capabilities_info = capabilities_info.merge(plistInfo.backgroundModes)
71
+ capabilities_info = capabilities_info.merge(plistInfo.enabled_capabilities)
72
+ capabilities_info
73
+ end
74
+
75
+ def mobileprovision
76
+ return unless mobileprovision?
77
+ return @mobileprovision if @mobileprovision
78
+ @mobileprovision = MobileProvision.new(mobileprovision_path)
79
+ end
80
+
81
+ def plistInfo
82
+ @plistInfo ||= InfoPlist.new(info_path)
83
+ end
84
+
85
+ def entitlementsInfo
86
+ @entitlementsInfo ||= EntitlementsPlist.new(entitlements_path)
87
+ end
88
+
89
+
90
+ def mobileprovision?
91
+ File.exist?(mobileprovision_path)
92
+ end
93
+
94
+ def mobileprovision_path
95
+ filename = 'embedded.mobileprovision'
96
+ @mobileprovision_path ||= File.join(@file, filename)
97
+ unless File.exist?(@mobileprovision_path)
98
+ @mobileprovision_path = File.join(app_path, filename)
99
+ end
100
+
101
+ @mobileprovision_path
102
+ end
103
+
104
+
105
+ def contents
106
+ @contents ||= unarchive(@file, path: 'ios')
107
+ end
108
+
109
+ def info_path
110
+ @info_path ||= File.join(app_path, 'Info.plist')
111
+ end
112
+
113
+ def entitlements_path
114
+ @entitlements_path ||= File.join(app_path, 'Runner.entitlements')
115
+ end
116
+
117
+ def app_path
118
+ @app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
119
+ end
120
+
121
+ def clear!
122
+ return unless @contents
123
+
124
+ FileUtils.rm_rf(@contents)
125
+
126
+ @contents = nil
127
+ @app_path = nil
128
+ @info_path = nil
129
+ @info = nil
130
+ @pre_version = nil
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,90 @@
1
+ require 'tmpdir'
2
+ require 'crimp'
3
+
4
+ module AppPermissionStatistics
5
+
6
+ module Helper
7
+
8
+ module Sigflat
9
+ def createsig(body)
10
+ return Crimp.signature(body)
11
+ end
12
+ end
13
+
14
+ module Utils
15
+
16
+ def defalut_store_path(app_identifier)
17
+ root_path = "#{Dir.home}/appInfo-#{app_identifier}"
18
+ FileUtils.mkdir_p(root_path) unless File.exists?(root_path)
19
+ root_path
20
+ end
21
+
22
+ def entitlements_yaml_name(v,app_identifier,path: nil)
23
+ dir_path = path ? "#{path}-#{app_identifier}" : defalut_store_path(app_identifier)
24
+ yaml_name = "#{dir_path}/entitlements_#{v}.yml";
25
+ yaml_name
26
+ end
27
+
28
+ def versions_yaml_name(app_identifier,path: nil)
29
+ dir_path = path ? "#{path}-#{app_identifier}" : defalut_store_path(app_identifier)
30
+ yaml_name = "#{dir_path}/entitlements_versions.yml";
31
+ yaml_name
32
+ end
33
+
34
+ def report_file_name(path: nil)
35
+ path = path ? "#{path}/entitlements" : nil
36
+ if !path.nil?
37
+ FileUtils.mkdir_p(path) unless File.exists?(path)
38
+ end
39
+ file_name = path ? "#{path}/analyze_report" : "analyze_report";
40
+ file_name
41
+ end
42
+
43
+ end
44
+
45
+ module Archive
46
+ require 'fileutils'
47
+ require 'securerandom'
48
+ require 'zip'
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 AppPermissionStatistics
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
+ if !value.nil?
41
+ return {
42
+ "BackgroundModes" => {
43
+ "UIBackgroundModes" => value
44
+ }
45
+ }
46
+ end
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