headdesk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Headdesk
4
+ module Checks
5
+ #
6
+ # Check the version of the Facebook SDK
7
+ #
8
+ # Facebook deprecates SDKs 2 years after they are released.
9
+ #
10
+ class FacebookSDK
11
+ include Check::APK
12
+
13
+ describe 'Facebook SDK version'
14
+ def call
15
+ skip_check unless: -> { apk.class?('com/facebook/FacebookSdk') }
16
+ facebook_sdk = apk.find_class('com/facebook/FacebookSdk')
17
+
18
+ # TODO: Parse https://developers.facebook.com/docs/android/change-log-4x
19
+ # and fail if > 2 years old, warn if < 3 months remaining
20
+ get_sdk_version = facebook_sdk.method('getSdkVersion').code
21
+ describe 'com.facebook.FacebookSdk contains getSdkVersion method'
22
+ fail_check if: -> { !get_sdk_version }
23
+
24
+ facebook_sdks = YAML.load_file(Headdesk::FACEBOOK_SDK_VERSIONS_YAML)
25
+ major, minor, patch = get_sdk_version.match(/const-string v0, "(\d+)\.(\d+)\.(\d+)"/).captures.map(&:to_i)
26
+
27
+ sdk_in_use = (facebook_sdks.select do |sdk|
28
+ sdk[:major] == major && sdk[:minor] == minor && sdk[:patch] == patch
29
+ end).first
30
+
31
+ describe "Found Facebook SDK version #{sdk_in_use[:version]}"
32
+ fail_check if: -> { !sdk_in_use }
33
+
34
+ export facebook_sdk: sdk_in_use
35
+
36
+ describe "Facebook SDK was released in the last 2 years (using #{sdk_in_use[:version]}, released #{sdk_in_use[:date]})"
37
+ fail_check if: -> { sdk_in_use[:date] < (Date.today - (365 * 2)) }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Headdesk
4
+ module Checks
5
+ #
6
+ # Make sure all <reciever> blocks in AndroidManifest.xml point to a Java class
7
+ # that exists in the APK.
8
+ #
9
+ class Receiver
10
+ include Check::APK
11
+
12
+ describe 'All <receiver> blocks in AndroidManifest.xml point to valid Java classes'
13
+ def call
14
+ receivers = []
15
+ apk.android_manifest.xpath('//receiver').each do |receiver|
16
+ receiver_name = receiver.attributes['name'].to_s
17
+ fail_check unless: -> { apk.class?(receiver_name) }
18
+ klass = apk.find_class(receiver_name)
19
+
20
+ describe "#{receiver_name} has onReceive method"
21
+ fail_check unless: -> { klass.method?('onReceive') }
22
+
23
+ receivers << {
24
+ name: receiver_name
25
+ }
26
+ end
27
+ export receivers: receivers
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Headdesk
4
+ module Checks
5
+ #
6
+ # Module for Teak checks
7
+ #
8
+ module Teak
9
+ #
10
+ # Module for Teak APK checks
11
+ #
12
+ module APK
13
+ def self.included(klass)
14
+ klass.include(Check::APK)
15
+ klass.extend(Preconditions)
16
+ klass.include(Utility)
17
+ end
18
+
19
+ #
20
+ # Precondition tests for presence of Teak class SDK in APK
21
+ #
22
+ module Preconditions
23
+ def preconditions?
24
+ false unless apk.class?('io.teak.sdk.Teak')
25
+ end
26
+ end
27
+
28
+ #
29
+ # Utility methods for Teak based checks
30
+ #
31
+ module Utility
32
+ def teak_sdk
33
+ return @teak_sdk if @teak_sdk
34
+
35
+ major, minor, revision = apk.find_class('io.teak.sdk.Teak')
36
+ .field('SDKVersion')
37
+ .value
38
+ .to_version
39
+ @teak_sdk = OpenStruct.new(
40
+ version: "#{major}.#{minor}.#{revision}",
41
+ major: major, minor: minor, revision: revision
42
+ )
43
+
44
+ @teak_sdk
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ Dir[File.dirname(__FILE__) + '/teak/*.rb'].each { |file| require file }
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Headdesk
4
+ module Checks
5
+ module Teak
6
+ #
7
+ # Check to make sure that an APK, which uses Teak, has an API 21+ icon.
8
+ #
9
+ class Api21Icon
10
+ include Checks::Teak::APK
11
+
12
+ describe 'Check for API 21+ Teak icons'
13
+ # :reek:UncommunicativeVariableName { accept: ['icon_v21'] }
14
+ def call
15
+ skip_check if: -> { apk.min_sdk 21 }
16
+
17
+ icon = apk.resources
18
+ .values(v: 20)
19
+ .drawable.io_teak_small_notification_icon
20
+ icon_v21 = apk.resources
21
+ .values(v: 21)
22
+ .drawable.io_teak_small_notification_icon
23
+ export icon: icon, icon_v21: icon_v21
24
+
25
+ describe "APK contains a drawable for 'io_teak_small_notification_icon'"
26
+ fail_check unless: -> { icon && icon_v21 }
27
+
28
+ describe "'io_teak_small_notification_icon' for v21 is different from < v21"
29
+ fail_check unless: -> { icon != icon_v21 }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Headdesk
4
+ module Checks
5
+ module Teak
6
+ #
7
+ # Check to make sure that an APK, which uses Teak, has caching enabled.
8
+ #
9
+ class Caching
10
+ include Checks::Teak::APK
11
+
12
+ describe 'Check for io_teak_enable_caching'
13
+ def call
14
+ describe 'Teak SDK version is lower than 2.0.0'
15
+ major, = apk.find_class('io.teak.sdk.Teak')
16
+ .field('SDKVersion')
17
+ .value
18
+ .to_version
19
+ skip_check if: major.to_i >= 2
20
+
21
+ describe "APK enables caching of Teak notification content (via 'io_teak_enable_caching')"
22
+ fail_check unless: apk.resources
23
+ .values(v: 21)
24
+ .bool
25
+ .io_teak_enable_caching
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Headdesk
4
+ module Checks
5
+ module Teak
6
+ #
7
+ # Check to make sure that an APK, which uses Teak, has caching enabled.
8
+ #
9
+ class Configuration
10
+ include Checks::Teak::APK
11
+
12
+ describe 'Check Teak configuration'
13
+ # :reek:UncommunicativeVariableName { accept: ['gcm_defaultSenderId'] }
14
+ def call
15
+ # App Id
16
+ teak_app_id = apk.resources
17
+ .values
18
+ .string
19
+ .io_teak_app_id
20
+ # TODO: Manifest meta-data
21
+ describe 'Teak App Id configured'
22
+ fail_check if: !teak_app_id
23
+ export teak_app_id: teak_app_id
24
+
25
+ # Api Key
26
+ teak_api_key = apk.resources
27
+ .values
28
+ .string
29
+ .io_teak_api_key
30
+ # TODO: Manifest meta-data
31
+ describe 'Teak API Key configured'
32
+ fail_check if: !teak_api_key
33
+ export teak_api_key: teak_api_key
34
+
35
+ # GCM Sender Id
36
+ io_teak_gcm_sender_id = apk.resources
37
+ .values
38
+ .string
39
+ .io_teak_gcm_sender_id
40
+ # TODO: Manifest meta-data
41
+ gcm_defaultSenderId ||= apk.resources
42
+ .values
43
+ .string
44
+ .gcm_defaultSenderId
45
+ describe "'gcm_defaultSenderId' is specified, and different from 'io_teak_gcm_sender_id'"
46
+ fail_check if: gcm_defaultSenderId != io_teak_gcm_sender_id if gcm_defaultSenderId
47
+
48
+ gcm_sender_id = io_teak_gcm_sender_id || gcm_defaultSenderId
49
+ describe "Either 'io_teak_gcm_sender_id' or 'gcm_defaultSenderId' configured"
50
+ fail_check if: !gcm_sender_id
51
+ export gcm_sender_id: gcm_sender_id
52
+
53
+ # Firebase App Id
54
+ io_teak_firebase_app_id = apk.resources
55
+ .values
56
+ .string
57
+ .io_teak_firebase_app_id
58
+ # TODO: Manifest meta-data
59
+ google_app_id = apk.resources
60
+ .values
61
+ .string
62
+ .google_app_id
63
+ describe "'google_app_id' is specified, and different from 'io_teak_firebase_app_id'"
64
+ fail_check if: google_app_id != io_teak_firebase_app_id if google_app_id
65
+
66
+ firebase_app_id = google_app_id || io_teak_firebase_app_id
67
+ describe "Either 'io_teak_firebase_app_id' or 'google_app_id' configured"
68
+ fail_check if: !firebase_app_id, skip_if: teak_sdk.major < 2
69
+ export gcm_sender_id: gcm_sender_id
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tmpdir'
4
+ require 'thor'
5
+ require 'headdesk'
6
+ require 'awesome_print'
7
+
8
+ module Headdesk
9
+ #
10
+ # headdesk CLI
11
+ #
12
+ # :reek:TooManyStatements
13
+ class CLI < Thor
14
+ desc 'unpack FILE [DESTINATION]', 'Unpack an APK or IPA to [DESTINATION] or to the current directory'
15
+ method_option :analize, type: :boolean, aliases: '-a'
16
+ def unpack(file, destination = nil)
17
+ # Make sure the input file exists
18
+ unless File.exist?(file)
19
+ STDERR.puts "Could not find: #{file}"
20
+ CLI.command_help(Thor::Base.shell.new, 'unpack')
21
+ return 1
22
+ end
23
+
24
+ # Make sure destination exists, if specified
25
+ unless !destination || Dir.exist?(destination)
26
+ STDERR.puts "Could not find destination path: #{destination}"
27
+ CLI.command_help(Thor::Base.shell.new, 'unpack')
28
+ return 1
29
+ end
30
+
31
+ begin
32
+ stdout = nil
33
+ output_path = destination
34
+
35
+ if !destination
36
+ # Output to tempdir, then copy to cwd if no destination specified
37
+ Dir.mktmpdir do |tmp_dir|
38
+ output_path = tmp_dir
39
+ stdout = Headdesk::ApkTool.unpack_to(file, tmp_dir)
40
+ FileUtils.cp_r("#{tmp_dir}/.", Dir.pwd)
41
+ end
42
+ else
43
+ stdout = Headdesk::ApkTool.unpack_to(file, destination)
44
+ end
45
+
46
+ # Analize if requested
47
+ Headdesk::Analize.at(output_path) if options[:analize]
48
+ rescue CliError => cli_err
49
+ STDERR.puts cli_err.message
50
+ CLI.command_help(Thor::Base.shell.new, 'unpack')
51
+ return 1
52
+ rescue StandardError => rb_err
53
+ STDERR.puts err.message.red
54
+ STDERR.puts err.backtrace.ai
55
+ return 1
56
+ end
57
+ end
58
+
59
+ desc 'analize [FILE]', 'Analize an APK or IPA'
60
+ method_option :path, type: :string
61
+ method_option :json, type: :boolean
62
+ def analize(file = nil)
63
+ # Make sure input file exsts, if specified
64
+ unless !file || File.exist?(file)
65
+ STDERR.puts "Could not find input file: #{file}"
66
+ CLI.command_help(Thor::Base.shell.new, 'analize')
67
+ return 1
68
+ end
69
+
70
+ # Unpack APK if needed
71
+ path = options[:path]
72
+ tmp_dir = nil
73
+ if file
74
+ path = tmp_dir = Dir.mktmpdir
75
+ Headdesk::ApkTool.unpack_to(file, tmp_dir)
76
+ end
77
+
78
+ # Make sure path exists
79
+ unless Dir.exist?(path)
80
+ STDERR.puts "Could not find path: #{path}"
81
+ CLI.command_help(Thor::Base.shell.new, 'analize')
82
+ return 1
83
+ end
84
+
85
+ # Analize
86
+ begin
87
+ report = Headdesk::Analize.at(path)
88
+
89
+ if options[:json]
90
+ STDOUT.puts report.to_json
91
+ else
92
+ STDOUT.puts report.to_s
93
+ end
94
+ rescue CliError => cli_err
95
+ STDERR.puts cli_err.message
96
+ CLI.command_help(Thor::Base.shell.new, 'analize')
97
+ return 1
98
+ rescue StandardError => err
99
+ STDERR.puts err.message.red
100
+ STDERR.puts err.backtrace.ai
101
+ return 1
102
+ end
103
+ ensure
104
+ FileUtils.remove_entry(tmp_dir) if tmp_dir
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,281 @@
1
+ ---
2
+ - :major: 4
3
+ :minor: 38
4
+ :patch: 1
5
+ :version: 4.38.1
6
+ :date: 2018-11-01
7
+ - :major: 4
8
+ :minor: 38
9
+ :patch: 0
10
+ :version: 4.38.0
11
+ :date: 2018-10-23
12
+ - :major: 4
13
+ :minor: 37
14
+ :patch: 0
15
+ :version: 4.37.0
16
+ :date: 2018-09-27
17
+ - :major: 4
18
+ :minor: 36
19
+ :patch: 1
20
+ :version: 4.36.1
21
+ :date: 2018-09-17
22
+ - :major: 4
23
+ :minor: 36
24
+ :patch: 0
25
+ :version: 4.36.0
26
+ :date: 2018-08-29
27
+ - :major: 4
28
+ :minor: 35
29
+ :patch: 0
30
+ :version: 4.35.0
31
+ :date: 2018-07-26
32
+ - :major: 4
33
+ :minor: 34
34
+ :patch: 0
35
+ :version: 4.34.0
36
+ :date: 2018-06-18
37
+ - :major: 4
38
+ :minor: 33
39
+ :patch: 0
40
+ :version: 4.33.0
41
+ :date: 2018-05-01
42
+ - :major: 4
43
+ :minor: 32
44
+ :patch: 0
45
+ :version: 4.32.0
46
+ :date: 2018-04-11
47
+ - :major: 4
48
+ :minor: 31
49
+ :patch: 0
50
+ :version: 4.31.0
51
+ :date: 2018-02-28
52
+ - :major: 4
53
+ :minor: 30
54
+ :patch: 0
55
+ :version: 4.30.0
56
+ :date: 2018-01-24
57
+ - :major: 4
58
+ :minor: 29
59
+ :patch: 0
60
+ :version: 4.29.0
61
+ :date: 2017-12-05
62
+ - :major: 4
63
+ :minor: 28
64
+ :patch: 0
65
+ :version: 4.28.0
66
+ :date: 2017-11-07
67
+ - :major: 4
68
+ :minor: 27
69
+ :patch: 0
70
+ :version: 4.27.0
71
+ :date: 2017-09-26
72
+ - :major: 4
73
+ :minor: 26
74
+ :patch: 0
75
+ :version: 4.26.0
76
+ :date: 2017-08-24
77
+ - :major: 4
78
+ :minor: 25
79
+ :patch: 0
80
+ :version: 4.25.0
81
+ :date: 2017-07-26
82
+ - :major: 4
83
+ :minor: 24
84
+ :patch: 0
85
+ :version: 4.24.0
86
+ :date: 2017-06-26
87
+ - :major: 4
88
+ :minor: 23
89
+ :patch: 0
90
+ :version: 4.23.0
91
+ :date: 2017-05-25
92
+ - :major: 4
93
+ :minor: 22
94
+ :patch: 1
95
+ :version: 4.22.1
96
+ :date: 2017-05-11
97
+ - :major: 4
98
+ :minor: 22
99
+ :patch: 0
100
+ :version: 4.22.0
101
+ :date: 2017-04-18
102
+ - :major: 4
103
+ :minor: 21
104
+ :patch: 1
105
+ :version: 4.21.1
106
+ :date: 2017-04-06
107
+ - :major: 4
108
+ :minor: 21
109
+ :patch: 0
110
+ :version: 4.21.0
111
+ :date: 2017-04-04
112
+ - :major: 4
113
+ :minor: 20
114
+ :patch: 0
115
+ :version: 4.20.0
116
+ :date: 2017-03-01
117
+ - :major: 4
118
+ :minor: 19
119
+ :patch: 0
120
+ :version: 4.19.0
121
+ :date: 2017-01-25
122
+ - :major: 4
123
+ :minor: 18
124
+ :patch: 0
125
+ :version: 4.18.0
126
+ :date: 2016-11-30
127
+ - :major: 4
128
+ :minor: 17
129
+ :patch: 0
130
+ :version: 4.17.0
131
+ :date: 2016-10-26
132
+ - :major: 4
133
+ :minor: 16
134
+ :patch: 1
135
+ :version: 4.16.1
136
+ :date: 2016-10-07
137
+ - :major: 4
138
+ :minor: 16
139
+ :patch: 0
140
+ :version: 4.16.0
141
+ :date: 2016-09-27
142
+ - :major: 4
143
+ :minor: 15
144
+ :patch: 0
145
+ :version: 4.15.0
146
+ :date: 2016-08-23
147
+ - :major: 4
148
+ :minor: 14
149
+ :patch: 1
150
+ :version: 4.14.1
151
+ :date: 2016-08-04
152
+ - :major: 4
153
+ :minor: 14
154
+ :patch: 0
155
+ :version: 4.14.0
156
+ :date: 2016-07-13
157
+ - :major: 4
158
+ :minor: 13
159
+ :patch: 2
160
+ :version: 4.13.2
161
+ :date: 2016-07-01
162
+ - :major: 4
163
+ :minor: 13
164
+ :patch: 1
165
+ :version: 4.13.1
166
+ :date: 2016-06-17
167
+ - :major: 4
168
+ :minor: 13
169
+ :patch: 0
170
+ :version: 4.13.0
171
+ :date: 2016-06-15
172
+ - :major: 4
173
+ :minor: 12
174
+ :patch: 1
175
+ :version: 4.12.1
176
+ :date: 2016-05-26
177
+ - :major: 4
178
+ :minor: 12
179
+ :patch: 0
180
+ :version: 4.12.0
181
+ :date: 2016-05-20
182
+ - :major: 4
183
+ :minor: 11
184
+ :patch: 0
185
+ :version: 4.11.0
186
+ :date: 2016-04-12
187
+ - :major: 4
188
+ :minor: 10
189
+ :patch: 1
190
+ :version: 4.10.1
191
+ :date: 2016-03-18
192
+ - :major: 4
193
+ :minor: 10
194
+ :patch: 0
195
+ :version: 4.10.0
196
+ :date: 2016-02-10
197
+ - :major: 4
198
+ :minor: 9
199
+ :patch: 0
200
+ :version: 4.9.0
201
+ :date: 2016-01-13
202
+ - :major: 4
203
+ :minor: 8
204
+ :patch: 2
205
+ :version: 4.8.2
206
+ :date: 2015-11-23
207
+ - :major: 4
208
+ :minor: 8
209
+ :patch: 1
210
+ :version: 4.8.1
211
+ :date: 2015-11-11
212
+ - :major: 4
213
+ :minor: 8
214
+ :patch: 0
215
+ :version: 4.8.0
216
+ :date: 2015-11-11
217
+ - :major: 4
218
+ :minor: 7
219
+ :patch: 0
220
+ :version: 4.7.0
221
+ :date: 2015-10-07
222
+ - :major: 4
223
+ :minor: 6
224
+ :patch: 0
225
+ :version: 4.6.0
226
+ :date: 2015-09-10
227
+ - :major: 4
228
+ :minor: 5
229
+ :patch: 1
230
+ :version: 4.5.1
231
+ :date: 2015-08-13
232
+ - :major: 4
233
+ :minor: 5
234
+ :patch: 0
235
+ :version: 4.5.0
236
+ :date: 2015-08-10
237
+ - :major: 4
238
+ :minor: 4
239
+ :patch: 1
240
+ :version: 4.4.1
241
+ :date: 2015-07-13
242
+ - :major: 4
243
+ :minor: 4
244
+ :patch: 0
245
+ :version: 4.4.0
246
+ :date: 2015-07-08
247
+ - :major: 4
248
+ :minor: 3
249
+ :patch: 0
250
+ :version: 4.3.0
251
+ :date: 2015-06-25
252
+ - :major: 4
253
+ :minor: 2
254
+ :patch: 0
255
+ :version: 4.2.0
256
+ :date: 2015-05-28
257
+ - :major: 4
258
+ :minor: 1
259
+ :patch: 2
260
+ :version: 4.1.2
261
+ :date: 2015-05-14
262
+ - :major: 4
263
+ :minor: 1
264
+ :patch: 1
265
+ :version: 4.1.1
266
+ :date: 2015-05-06
267
+ - :major: 4
268
+ :minor: 1
269
+ :patch: 0
270
+ :version: 4.1.0
271
+ :date: 2015-04-30
272
+ - :major: 4
273
+ :minor: 0
274
+ :patch: 1
275
+ :version: 4.0.1
276
+ :date: 2015-04-02
277
+ - :major: 4
278
+ :minor: 0
279
+ :patch: 0
280
+ :version: 4.0.0
281
+ :date: 2015-03-25