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.
@@ -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