fastlane 2.219.0 → 2.221.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +86 -86
- data/bin/console +11 -0
- data/bin/match_file +60 -0
- data/deliver/lib/deliver/download_screenshots.rb +2 -1
- data/deliver/lib/deliver/generate_summary.rb +1 -1
- data/deliver/lib/deliver/options.rb +14 -0
- data/deliver/lib/deliver/runner.rb +4 -4
- data/deliver/lib/deliver/submit_for_review.rb +2 -2
- data/deliver/lib/deliver/upload_metadata.rb +43 -28
- data/deliver/lib/deliver/upload_screenshots.rb +15 -8
- data/fastlane/lib/fastlane/actions/app_store_connect_api_key.rb +2 -1
- data/fastlane/lib/fastlane/actions/appetize.rb +4 -0
- data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +6 -2
- data/fastlane/lib/fastlane/actions/git_add.rb +17 -2
- data/fastlane/lib/fastlane/actions/mailgun.rb +30 -8
- data/fastlane/lib/fastlane/actions/onesignal.rb +14 -2
- data/fastlane/lib/fastlane/actions/spm.rb +7 -0
- data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +2 -1
- data/fastlane/lib/fastlane/actions/upload_symbols_to_sentry.rb +4 -2
- data/fastlane/lib/fastlane/commands_generator.rb +9 -0
- data/fastlane/lib/fastlane/console.rb +24 -0
- data/fastlane/lib/fastlane/helper/sh_helper.rb +1 -1
- data/fastlane/lib/fastlane/lane_manager_base.rb +16 -8
- data/fastlane/lib/fastlane/plugins/plugin_fetcher.rb +2 -1
- data/fastlane/lib/fastlane/plugins/plugin_info_collector.rb +2 -1
- data/fastlane/lib/fastlane/plugins/plugin_manager.rb +2 -1
- data/fastlane/lib/fastlane/runner.rb +2 -2
- data/fastlane/lib/fastlane/version.rb +2 -1
- data/fastlane/swift/Deliverfile.swift +1 -1
- data/fastlane/swift/DeliverfileProtocol.swift +9 -1
- data/fastlane/swift/Fastlane.swift +48 -11
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/GymfileProtocol.swift +1 -1
- data/fastlane/swift/LaneFileProtocol.swift +1 -1
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +1 -1
- data/fastlane/swift/OptionalConfigValue.swift +2 -2
- data/fastlane/swift/Precheckfile.swift +1 -1
- data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
- data/fastlane/swift/Scanfile.swift +1 -1
- data/fastlane/swift/ScanfileProtocol.swift +1 -1
- data/fastlane/swift/Screengrabfile.swift +1 -1
- data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
- data/fastlane/swift/Snapshotfile.swift +1 -1
- data/fastlane/swift/SnapshotfileProtocol.swift +3 -3
- data/fastlane/swift/formatting/Brewfile.lock.json +19 -19
- data/fastlane_core/lib/fastlane_core/cert_checker.rb +11 -8
- data/fastlane_core/lib/fastlane_core/device_manager.rb +1 -1
- data/fastlane_core/lib/fastlane_core/helper.rb +0 -15
- data/fastlane_core/lib/fastlane_core/print_table.rb +16 -0
- data/fastlane_core/lib/fastlane_core/project.rb +5 -0
- data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +0 -4
- data/fastlane_core/lib/fastlane_core/ui/help_formatter.rb +1 -5
- data/fastlane_core/lib/fastlane_core/ui/implementations/shell.rb +2 -0
- data/frameit/lib/frameit/device_types.rb +4 -0
- data/frameit/lib/frameit/editor.rb +20 -0
- data/gym/lib/gym/detect_values.rb +2 -0
- data/gym/lib/gym/module.rb +2 -2
- data/match/lib/assets/READMETemplate.md +3 -5
- data/match/lib/match/encryption/encryption.rb +154 -0
- data/match/lib/match/encryption/openssl.rb +7 -38
- data/match/lib/match/encryption.rb +1 -0
- data/match/lib/match/runner.rb +44 -6
- data/match/lib/match/storage/git_storage.rb +4 -3
- data/match/lib/match/storage/interface.rb +9 -5
- data/pilot/lib/pilot/build_manager.rb +14 -6
- data/pilot/lib/pilot/manager.rb +2 -2
- data/pilot/lib/pilot/options.rb +1 -1
- data/snapshot/lib/snapshot/options.rb +2 -2
- data/snapshot/lib/snapshot/setup.rb +1 -1
- data/spaceship/lib/spaceship/connect_api/api_client.rb +2 -2
- data/spaceship/lib/spaceship/connect_api/models/app.rb +28 -33
- data/spaceship/lib/spaceship/connect_api/models/app_info.rb +17 -0
- data/spaceship/lib/spaceship/connect_api/models/app_store_version.rb +44 -9
- data/spaceship/lib/spaceship/connect_api/models/beta_tester.rb +30 -2
- data/spaceship/lib/spaceship/connect_api/models/certificate.rb +2 -2
- data/spaceship/lib/spaceship/connect_api/models/device.rb +11 -6
- data/spaceship/lib/spaceship/connect_api/models/profile.rb +8 -1
- data/spaceship/lib/spaceship/connect_api/provisioning/client.rb +1 -1
- data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +31 -22
- data/spaceship/lib/spaceship/connect_api/testflight/client.rb +1 -1
- data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +47 -43
- data/spaceship/lib/spaceship/connect_api/token.rb +9 -3
- data/spaceship/lib/spaceship/connect_api/tunes/client.rb +1 -1
- data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +96 -90
- data/spaceship/lib/spaceship/connect_api/users/client.rb +1 -1
- data/spaceship/lib/spaceship/connect_api/users/users.rb +15 -11
- data/spaceship/lib/spaceship/connect_api.rb +5 -2
- data/spaceship/lib/spaceship/portal/certificate.rb +2 -2
- data/spaceship/lib/spaceship/portal/provisioning_profile.rb +8 -1
- data/spaceship/lib/spaceship/stats_middleware.rb +2 -2
- data/trainer/lib/trainer/xcresult.rb +6 -10
- metadata +50 -33
@@ -1,6 +1,22 @@
|
|
1
1
|
require_relative 'configuration/configuration'
|
2
2
|
require_relative 'helper'
|
3
3
|
|
4
|
+
# Monkey patch Terminal::Table until this is merged
|
5
|
+
# https://github.com/tj/terminal-table/pull/131
|
6
|
+
# solves https://github.com/fastlane/fastlane/issues/21852
|
7
|
+
# loads Terminal::Table first to be able to monkey patch it.
|
8
|
+
require 'terminal-table'
|
9
|
+
module Terminal
|
10
|
+
class Table
|
11
|
+
class Cell
|
12
|
+
def lines
|
13
|
+
# @value.to_s.split(/\n/)
|
14
|
+
@value.to_s.encode("utf-8", invalid: :replace).split(/\n/)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
4
20
|
module FastlaneCore
|
5
21
|
class PrintTable
|
6
22
|
class << self
|
@@ -307,6 +307,10 @@ module FastlaneCore
|
|
307
307
|
supported_platforms.include?(:watchOS)
|
308
308
|
end
|
309
309
|
|
310
|
+
def visionos?
|
311
|
+
supported_platforms.include?(:visionOS)
|
312
|
+
end
|
313
|
+
|
310
314
|
def multiplatform?
|
311
315
|
supported_platforms.count > 1
|
312
316
|
end
|
@@ -323,6 +327,7 @@ module FastlaneCore
|
|
323
327
|
when "iphonesimulator", "iphoneos" then :iOS
|
324
328
|
when "watchsimulator", "watchos" then :watchOS
|
325
329
|
when "appletvsimulator", "appletvos" then :tvOS
|
330
|
+
when "xros", "xrsimulator" then :visionOS
|
326
331
|
end
|
327
332
|
end.uniq.compact
|
328
333
|
end
|
@@ -243,10 +243,6 @@ module Commander
|
|
243
243
|
ui.error(e.to_s)
|
244
244
|
ui.error("")
|
245
245
|
ui.error("SSL errors can be caused by various components on your local machine.")
|
246
|
-
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.1')
|
247
|
-
ui.error("Apple has recently changed their servers to require TLS 1.2, which may")
|
248
|
-
ui.error("not be available to your system installed Ruby (#{RUBY_VERSION})")
|
249
|
-
end
|
250
246
|
ui.error("")
|
251
247
|
ui.error("The best solution is to use the self-contained fastlane version.")
|
252
248
|
ui.error("Which ships with a bundled OpenSSL,ruby and all gems - so you don't depend on system libraries")
|
@@ -6,11 +6,7 @@ module FastlaneCore
|
|
6
6
|
# fastlane only customizes the global command help
|
7
7
|
return super unless name == :help
|
8
8
|
|
9
|
-
|
10
|
-
ERB.new(File.read(File.join(File.dirname(__FILE__), "help.erb")), nil, '-')
|
11
|
-
else
|
12
|
-
ERB.new(File.read(File.join(File.dirname(__FILE__), "help.erb")), trim_mode: '-')
|
13
|
-
end
|
9
|
+
ERB.new(File.read(File.join(File.dirname(__FILE__), "help.erb")), trim_mode: '-')
|
14
10
|
end
|
15
11
|
end
|
16
12
|
end
|
@@ -108,12 +108,14 @@ module FastlaneCore
|
|
108
108
|
start_line = error_line - 2 < 1 ? 1 : error_line - 2
|
109
109
|
end_line = error_line + 2 < contents.length ? error_line + 2 : contents.length
|
110
110
|
|
111
|
+
error('```')
|
111
112
|
Range.new(start_line, end_line).each do |line|
|
112
113
|
str = line == error_line ? " => " : " "
|
113
114
|
str << line.to_s.rjust(Math.log10(end_line) + 1)
|
114
115
|
str << ":\t#{contents[line - 1]}"
|
115
116
|
error(str)
|
116
117
|
end
|
118
|
+
error('```')
|
117
119
|
end
|
118
120
|
|
119
121
|
#####################################################
|
@@ -133,6 +133,10 @@ module Frameit
|
|
133
133
|
IPHONE_13_PRO ||= Frameit::Device.new("iphone-13-pro", "Apple iPhone 13 Pro", 11, [[1170, 2532], [2532, 1170]], 460, Color::GRAPHITE, Platform::IOS)
|
134
134
|
IPHONE_13_PRO_MAX ||= Frameit::Device.new("iphone13-pro-max", "Apple iPhone 13 Pro Max", 11, [[1284, 2778], [2778, 1284]], 458, Color::GRAPHITE, Platform::IOS)
|
135
135
|
IPHONE_13_MINI ||= Frameit::Device.new("iphone-13-mini", "Apple iPhone 13 Mini", 11, [[1080, 2340], [2340, 1080]], 476, Color::MIDNIGHT, Platform::IOS)
|
136
|
+
IPHONE_14 ||= Frameit::Device.new("iphone-14", "Apple iPhone 14", 12, [[1170, 2532], [2532, 1170]], 460, Color::MIDNIGHT, Platform::IOS)
|
137
|
+
IPHONE_14_PLUS ||= Frameit::Device.new("iphone-14-plus", "Apple iPhone 14 Plus", 12, [[1284, 2778], [2778, 1284]], 458, Color::MIDNIGHT, Platform::IOS)
|
138
|
+
IPHONE_14_PRO ||= Frameit::Device.new("iphone-14-pro", "Apple iPhone 14 Pro", 12, [[1178, 2556], [2556, 1178]], 460, Color::PURPLE, Platform::IOS)
|
139
|
+
IPHONE_14_PRO_MAX ||= Frameit::Device.new("iphone14-pro-max", "Apple iPhone 14 Pro Max", 12, [[1290, 2796], [2796, 1290]], 458, Color::PURPLE, Platform::IOS)
|
136
140
|
IPAD_10_2 ||= Frameit::Device.new("ipad-10-2", "Apple iPad 10.2", 1, [[1620, 2160], [2160, 1620]], 264, Color::SPACE_GRAY, Platform::IOS)
|
137
141
|
IPAD_AIR_2 ||= Frameit::Device.new("ipad-air-2", "Apple iPad Air 2", 1, [[1536, 2048], [2048, 1536]], 264, Color::SPACE_GRAY, Platform::IOS, Deliver::AppScreenshot::ScreenSize::IOS_IPAD)
|
138
142
|
IPAD_AIR_2019 ||= Frameit::Device.new("ipad-air-2019", "Apple iPad Air (2019)", 2, [[1668, 2224], [2224, 1668]], 265, Color::SPACE_GRAY, Platform::IOS)
|
@@ -109,6 +109,26 @@ module Frameit
|
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
112
|
+
# Apply rounded corners for all iPhone 14 devices
|
113
|
+
if screenshot.device.id.to_s.include?("iphone-14") || screenshot.device.id.to_s.include?("iphone14")
|
114
|
+
|
115
|
+
maskData = MiniMagick::Tool::Convert.new do |img|
|
116
|
+
img.size("#{screenshot.size[0]}x#{screenshot.size[1]}")
|
117
|
+
img.canvas('none')
|
118
|
+
img.draw("roundrectangle 0,0,#{screenshot.size[0]},#{screenshot.size[1]},100,100")
|
119
|
+
img << 'png:-'
|
120
|
+
end
|
121
|
+
|
122
|
+
# Create a mask
|
123
|
+
mask = MiniMagick::Image.read(maskData)
|
124
|
+
|
125
|
+
@image = @image.composite(mask, "png") do |c|
|
126
|
+
c.channel("A")
|
127
|
+
c.compose("DstIn")
|
128
|
+
c.alpha("on")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
112
132
|
@image = frame.composite(image, "png") do |c|
|
113
133
|
c.compose("DstOver")
|
114
134
|
c.geometry(offset['offset'])
|
data/gym/lib/gym/module.rb
CHANGED
@@ -33,8 +33,8 @@ module Gym
|
|
33
33
|
# Can be iOS project and build for mac if catalyst
|
34
34
|
return false if building_mac_catalyst_for_mac?
|
35
35
|
|
36
|
-
# Can be iOS project if iOS, tvOS, or
|
37
|
-
return Gym.project.ios? || Gym.project.tvos? || Gym.project.watchos?
|
36
|
+
# Can be iOS project if iOS, tvOS, watchOS, or visionOS
|
37
|
+
return Gym.project.ios? || Gym.project.tvos? || Gym.project.watchos? || Gym.project.visionos?
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -14,13 +14,11 @@ Make sure you have the latest version of the Xcode command line tools installed:
|
|
14
14
|
xcode-select --install
|
15
15
|
```
|
16
16
|
|
17
|
-
Install _fastlane_ using
|
17
|
+
Install _fastlane_ using bundler by following instructions here on [fastlane docs](https://docs.fastlane.tools).
|
18
18
|
|
19
|
-
|
20
|
-
[sudo] gem install fastlane -NV
|
21
|
-
```
|
19
|
+
or alternatively using
|
22
20
|
|
23
|
-
|
21
|
+
`brew install fastlane`
|
24
22
|
|
25
23
|
### Usage
|
26
24
|
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Match
|
6
|
+
module Encryption
|
7
|
+
# This is to keep backwards compatibility with the old fastlane version which used the local openssl installation.
|
8
|
+
# The encryption parameters in this implementation reflect the old behavior which was the most common default value in those versions.
|
9
|
+
# As for decryption, 1.0.x OpenSSL and earlier versions use MD5, 1.1.0c and newer uses SHA256, we try both before giving an error
|
10
|
+
class EncryptionV1
|
11
|
+
ALGORITHM = 'aes-256-cbc'
|
12
|
+
|
13
|
+
def encrypt(data:, password:, salt:, hash_algorithm: "MD5")
|
14
|
+
cipher = ::OpenSSL::Cipher.new(ALGORITHM)
|
15
|
+
cipher.encrypt
|
16
|
+
|
17
|
+
keyivgen(cipher, password, salt, hash_algorithm)
|
18
|
+
|
19
|
+
encrypted_data = cipher.update(data)
|
20
|
+
encrypted_data << cipher.final
|
21
|
+
{ encrypted_data: encrypted_data }
|
22
|
+
end
|
23
|
+
|
24
|
+
def decrypt(encrypted_data:, password:, salt:, hash_algorithm: "MD5")
|
25
|
+
cipher = ::OpenSSL::Cipher.new(ALGORITHM)
|
26
|
+
cipher.decrypt
|
27
|
+
|
28
|
+
keyivgen(cipher, password, salt, hash_algorithm)
|
29
|
+
|
30
|
+
data = cipher.update(encrypted_data)
|
31
|
+
data << cipher.final
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def keyivgen(cipher, password, salt, hash_algorithm)
|
37
|
+
cipher.pkcs5_keyivgen(password, salt, 1, hash_algorithm)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# The newer encryption mechanism, which features a more secure key and IV generation.
|
42
|
+
#
|
43
|
+
# The IV is randomly generated and provided unencrypted.
|
44
|
+
# The salt should be randomly generated and provided unencrypted (like in the current implementation).
|
45
|
+
# The key is generated with OpenSSL::KDF::pbkdf2_hmac with properly chosen parameters.
|
46
|
+
#
|
47
|
+
# Short explanation about salt and IV: https://stackoverflow.com/a/1950674/6324550
|
48
|
+
class EncryptionV2
|
49
|
+
ALGORITHM = 'aes-256-gcm'
|
50
|
+
|
51
|
+
def encrypt(data:, password:, salt:)
|
52
|
+
cipher = ::OpenSSL::Cipher.new(ALGORITHM)
|
53
|
+
cipher.encrypt
|
54
|
+
|
55
|
+
keyivgen(cipher, password, salt)
|
56
|
+
|
57
|
+
encrypted_data = cipher.update(data)
|
58
|
+
encrypted_data << cipher.final
|
59
|
+
|
60
|
+
auth_tag = cipher.auth_tag
|
61
|
+
|
62
|
+
{ encrypted_data: encrypted_data, auth_tag: auth_tag }
|
63
|
+
end
|
64
|
+
|
65
|
+
def decrypt(encrypted_data:, password:, salt:, auth_tag:)
|
66
|
+
cipher = ::OpenSSL::Cipher.new(ALGORITHM)
|
67
|
+
cipher.decrypt
|
68
|
+
|
69
|
+
keyivgen(cipher, password, salt)
|
70
|
+
|
71
|
+
cipher.auth_tag = auth_tag
|
72
|
+
|
73
|
+
data = cipher.update(encrypted_data)
|
74
|
+
data << cipher.final
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def keyivgen(cipher, password, salt)
|
80
|
+
keyIv = ::OpenSSL::KDF.pbkdf2_hmac(password, salt: salt, iterations: 10_000, length: 32 + 12 + 24, hash: "sha256")
|
81
|
+
key = keyIv[0..31]
|
82
|
+
iv = keyIv[32..43]
|
83
|
+
auth_data = keyIv[44..-1]
|
84
|
+
cipher.key = key
|
85
|
+
cipher.iv = iv
|
86
|
+
cipher.auth_data = auth_data
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class MatchDataEncryption
|
91
|
+
V1_PREFIX = "Salted__"
|
92
|
+
V2_PREFIX = "match_encrypted_v2__"
|
93
|
+
|
94
|
+
def encrypt(data:, password:, version: 2)
|
95
|
+
salt = SecureRandom.random_bytes(8)
|
96
|
+
if version == 2
|
97
|
+
e = EncryptionV2.new
|
98
|
+
encryption = e.encrypt(data: data, password: password, salt: salt)
|
99
|
+
encrypted_data = V2_PREFIX + salt + encryption[:auth_tag] + encryption[:encrypted_data]
|
100
|
+
else
|
101
|
+
e = EncryptionV1.new
|
102
|
+
encryption = e.encrypt(data: data, password: password, salt: salt)
|
103
|
+
encrypted_data = V1_PREFIX + salt + encryption[:encrypted_data]
|
104
|
+
end
|
105
|
+
Base64.encode64(encrypted_data)
|
106
|
+
end
|
107
|
+
|
108
|
+
def decrypt(base64encoded_encrypted:, password:)
|
109
|
+
stored_data = Base64.decode64(base64encoded_encrypted)
|
110
|
+
if stored_data.start_with?(V2_PREFIX)
|
111
|
+
salt = stored_data[20..27]
|
112
|
+
auth_tag = stored_data[28..43]
|
113
|
+
data_to_decrypt = stored_data[44..-1]
|
114
|
+
e = EncryptionV2.new
|
115
|
+
e.decrypt(encrypted_data: data_to_decrypt, password: password, salt: salt, auth_tag: auth_tag)
|
116
|
+
else
|
117
|
+
salt = stored_data[8..15]
|
118
|
+
data_to_decrypt = stored_data[16..-1]
|
119
|
+
e = EncryptionV1.new
|
120
|
+
begin
|
121
|
+
# Note that we are not guaranteed to catch the decryption errors here if the password or the hash is wrong
|
122
|
+
# as there's no integrity checks.
|
123
|
+
# see https://github.com/fastlane/fastlane/issues/21663
|
124
|
+
e.decrypt(encrypted_data: data_to_decrypt, password: password, salt: salt)
|
125
|
+
# With the wrong hash_algorithm, there's here 0.4% chance that the decryption failure will go undetected
|
126
|
+
rescue => _ex
|
127
|
+
# With a wrong password, there's a 0.4% chance it will decrypt garbage and not fail
|
128
|
+
fallback_hash_algorithm = "SHA256"
|
129
|
+
e.decrypt(encrypted_data: data_to_decrypt, password: password, salt: salt, hash_algorithm: fallback_hash_algorithm)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# The methods of this class will encrypt or decrypt files in place, by default.
|
136
|
+
class MatchFileEncryption
|
137
|
+
def encrypt(file_path:, password:, output_path: nil)
|
138
|
+
output_path = file_path unless output_path
|
139
|
+
data_to_encrypt = File.binread(file_path)
|
140
|
+
e = MatchDataEncryption.new
|
141
|
+
data = e.encrypt(data: data_to_encrypt, password: password)
|
142
|
+
File.write(output_path, data)
|
143
|
+
end
|
144
|
+
|
145
|
+
def decrypt(file_path:, password:, output_path: nil)
|
146
|
+
output_path = file_path unless output_path
|
147
|
+
content = File.read(file_path)
|
148
|
+
e = MatchDataEncryption.new
|
149
|
+
decrypted_data = e.decrypt(base64encoded_encrypted: content, password: password)
|
150
|
+
File.binwrite(output_path, decrypted_data)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -109,25 +109,10 @@ module Match
|
|
109
109
|
return password
|
110
110
|
end
|
111
111
|
|
112
|
-
# We encrypt with MD5 because that was the most common default value in older fastlane versions which used the local OpenSSL installation
|
113
|
-
# A more secure key and IV generation is needed in the future
|
114
|
-
# IV should be randomly generated and provided unencrypted
|
115
|
-
# salt should be randomly generated and provided unencrypted (like in the current implementation)
|
116
|
-
# key should be generated with OpenSSL::KDF::pbkdf2_hmac with properly chosen parameters
|
117
|
-
# Short explanation about salt and IV: https://stackoverflow.com/a/1950674/6324550
|
118
112
|
def encrypt_specific_file(path: nil, password: nil)
|
119
113
|
UI.user_error!("No password supplied") if password.to_s.strip.length == 0
|
120
|
-
|
121
|
-
|
122
|
-
salt = SecureRandom.random_bytes(8)
|
123
|
-
|
124
|
-
# The :: is important, as there is a name clash
|
125
|
-
cipher = ::OpenSSL::Cipher.new('AES-256-CBC')
|
126
|
-
cipher.encrypt
|
127
|
-
cipher.pkcs5_keyivgen(password, salt, 1, "MD5")
|
128
|
-
encrypted_data = "Salted__" + salt + cipher.update(data_to_encrypt) + cipher.final
|
129
|
-
|
130
|
-
File.write(path, Base64.encode64(encrypted_data))
|
114
|
+
e = MatchFileEncryption.new
|
115
|
+
e.encrypt(file_path: path, password: password)
|
131
116
|
rescue FastlaneCore::Interface::FastlaneError
|
132
117
|
raise
|
133
118
|
rescue => error
|
@@ -135,28 +120,12 @@ module Match
|
|
135
120
|
UI.crash!("Error encrypting '#{path}'")
|
136
121
|
end
|
137
122
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
stored_data = Base64.decode64(File.read(path))
|
142
|
-
salt = stored_data[8..15]
|
143
|
-
data_to_decrypt = stored_data[16..-1]
|
144
|
-
|
145
|
-
decipher = ::OpenSSL::Cipher.new('AES-256-CBC')
|
146
|
-
decipher.decrypt
|
147
|
-
decipher.pkcs5_keyivgen(password, salt, 1, hash_algorithm)
|
148
|
-
|
149
|
-
decrypted_data = decipher.update(data_to_decrypt) + decipher.final
|
150
|
-
|
151
|
-
File.binwrite(path, decrypted_data)
|
123
|
+
def decrypt_specific_file(path: nil, password: nil)
|
124
|
+
e = MatchFileEncryption.new
|
125
|
+
e.decrypt(file_path: path, password: password)
|
152
126
|
rescue => error
|
153
|
-
|
154
|
-
|
155
|
-
decrypt_specific_file(path: path, password: password, hash_algorithm: fallback_hash_algorithm)
|
156
|
-
else
|
157
|
-
UI.error(error.to_s)
|
158
|
-
UI.crash!("Error decrypting '#{path}'")
|
159
|
-
end
|
127
|
+
UI.error(error.to_s)
|
128
|
+
UI.crash!("Error decrypting '#{path}'")
|
160
129
|
end
|
161
130
|
end
|
162
131
|
end
|
data/match/lib/match/runner.rb
CHANGED
@@ -19,6 +19,7 @@ module Match
|
|
19
19
|
# rubocop:disable Metrics/ClassLength
|
20
20
|
class Runner
|
21
21
|
attr_accessor :files_to_commit
|
22
|
+
attr_accessor :files_to_delete
|
22
23
|
attr_accessor :spaceship
|
23
24
|
|
24
25
|
attr_accessor :storage
|
@@ -28,6 +29,7 @@ module Match
|
|
28
29
|
# rubocop:disable Metrics/PerceivedComplexity
|
29
30
|
def run(params)
|
30
31
|
self.files_to_commit = []
|
32
|
+
self.files_to_delete = []
|
31
33
|
|
32
34
|
FileUtils.mkdir_p(params[:output_path]) if params[:output_path]
|
33
35
|
|
@@ -82,12 +84,12 @@ module Match
|
|
82
84
|
end
|
83
85
|
|
84
86
|
# Certificate
|
85
|
-
cert_id = fetch_certificate(params: params,
|
87
|
+
cert_id = fetch_certificate(params: params, renew_expired_certs: false)
|
86
88
|
|
87
89
|
# Mac Installer Distribution Certificate
|
88
90
|
additional_cert_types = params[:additional_cert_types] || []
|
89
91
|
cert_ids = additional_cert_types.map do |additional_cert_type|
|
90
|
-
fetch_certificate(params: params,
|
92
|
+
fetch_certificate(params: params, renew_expired_certs: false, specific_cert_type: additional_cert_type)
|
91
93
|
end
|
92
94
|
|
93
95
|
profile_type = Sigh.profile_type_for_distribution_type(
|
@@ -112,9 +114,10 @@ module Match
|
|
112
114
|
end
|
113
115
|
end
|
114
116
|
|
115
|
-
|
117
|
+
has_file_changes = self.files_to_commit.count > 0 || self.files_to_delete.count > 0
|
118
|
+
if has_file_changes && !params[:readonly]
|
116
119
|
encryption.encrypt_files if encryption
|
117
|
-
storage.save_changes!(files_to_commit: self.files_to_commit)
|
120
|
+
storage.save_changes!(files_to_commit: self.files_to_commit, files_to_delete: self.files_to_delete)
|
118
121
|
end
|
119
122
|
|
120
123
|
# Print a summary table for each app_identifier
|
@@ -152,13 +155,48 @@ module Match
|
|
152
155
|
end
|
153
156
|
end
|
154
157
|
|
155
|
-
|
158
|
+
RENEWABLE_CERT_TYPES_VIA_API = [:mac_installer_distribution, :development, :distribution, :enterprise]
|
159
|
+
|
160
|
+
def fetch_certificate(params: nil, renew_expired_certs: false, specific_cert_type: nil)
|
156
161
|
cert_type = Match.cert_type_sym(specific_cert_type || params[:type])
|
157
162
|
|
158
163
|
certs = Dir[File.join(prefixed_working_directory, "certs", cert_type.to_s, "*.cer")]
|
159
164
|
keys = Dir[File.join(prefixed_working_directory, "certs", cert_type.to_s, "*.p12")]
|
160
165
|
|
161
|
-
|
166
|
+
storage_has_certs = certs.count != 0 && keys.count != 0
|
167
|
+
|
168
|
+
# Determine if cert is renewable.
|
169
|
+
# Can't renew developer_id certs with Connect API token. Account holder access is required.
|
170
|
+
is_authenticated_with_login = Spaceship::ConnectAPI.token.nil?
|
171
|
+
is_cert_renewable_via_api = RENEWABLE_CERT_TYPES_VIA_API.include?(cert_type)
|
172
|
+
is_cert_renewable = is_authenticated_with_login || is_cert_renewable_via_api
|
173
|
+
|
174
|
+
# Validate existing certificate first.
|
175
|
+
if renew_expired_certs && is_cert_renewable && storage_has_certs && !params[:readonly]
|
176
|
+
cert_path = select_cert_or_key(paths: certs)
|
177
|
+
|
178
|
+
unless Utils.is_cert_valid?(cert_path)
|
179
|
+
UI.important("Removing invalid certificate '#{File.basename(cert_path)}'")
|
180
|
+
|
181
|
+
# Remove expired cert.
|
182
|
+
self.files_to_delete << cert_path
|
183
|
+
File.delete(cert_path)
|
184
|
+
|
185
|
+
# Key filename is the same as cert but with .p12 extension.
|
186
|
+
key_path = cert_path.gsub(/\.cer$/, ".p12")
|
187
|
+
# Remove expired key .p12 file.
|
188
|
+
if File.exist?(key_path)
|
189
|
+
self.files_to_delete << key_path
|
190
|
+
File.delete(key_path)
|
191
|
+
end
|
192
|
+
|
193
|
+
certs = []
|
194
|
+
keys = []
|
195
|
+
storage_has_certs = false
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
if !storage_has_certs
|
162
200
|
UI.important("Couldn't find a valid code signing identity for #{cert_type}... creating one for you now")
|
163
201
|
UI.crash!("No code signing identity found and cannot create a new one because you enabled `readonly`") if params[:readonly]
|
164
202
|
cert_path = Generator.generate_certificate(params, cert_type, prefixed_working_directory, specific_cert_type: specific_cert_type)
|
@@ -140,9 +140,10 @@ module Match
|
|
140
140
|
end
|
141
141
|
|
142
142
|
def delete_files(files_to_delete: [], custom_message: nil)
|
143
|
-
|
144
|
-
|
145
|
-
|
143
|
+
if files_to_delete.count > 0
|
144
|
+
commands = files_to_delete.map { |filename| "git rm #{filename.shellescape}" }
|
145
|
+
git_push(commands: commands, commit_message: custom_message)
|
146
|
+
end
|
146
147
|
end
|
147
148
|
|
148
149
|
def upload_files(files_to_upload: [], custom_message: nil)
|
@@ -55,11 +55,14 @@ module Match
|
|
55
55
|
# Custom init to `[]` in case `nil` is passed
|
56
56
|
files_to_commit ||= []
|
57
57
|
files_to_delete ||= []
|
58
|
+
files_to_delete -= files_to_commit # Make sure we are not removing added files.
|
59
|
+
|
60
|
+
if files_to_commit.count == 0 && files_to_delete.count == 0
|
61
|
+
UI.user_error!("Neither `files_to_commit` nor `files_to_delete` were provided to the `save_changes!` method call")
|
62
|
+
end
|
58
63
|
|
59
64
|
Dir.chdir(File.expand_path(self.working_directory)) do
|
60
65
|
if files_to_commit.count > 0 # everything that isn't `match nuke`
|
61
|
-
UI.user_error!("You can't provide both `files_to_delete` and `files_to_commit` right now") if files_to_delete.count > 0
|
62
|
-
|
63
66
|
if !File.exist?(MATCH_VERSION_FILE_NAME) || File.read(MATCH_VERSION_FILE_NAME) != Fastlane::VERSION.to_s
|
64
67
|
files_to_commit << MATCH_VERSION_FILE_NAME
|
65
68
|
File.write(MATCH_VERSION_FILE_NAME, Fastlane::VERSION) # stored unencrypted
|
@@ -74,13 +77,14 @@ module Match
|
|
74
77
|
|
75
78
|
self.upload_files(files_to_upload: files_to_commit, custom_message: custom_message)
|
76
79
|
UI.message("Finished uploading files to #{self.human_readable_description}")
|
77
|
-
|
80
|
+
end
|
81
|
+
|
82
|
+
if files_to_delete.count > 0
|
78
83
|
self.delete_files(files_to_delete: files_to_delete, custom_message: custom_message)
|
79
84
|
UI.message("Finished deleting files from #{self.human_readable_description}")
|
80
|
-
else
|
81
|
-
UI.user_error!("Neither `files_to_commit` nor `files_to_delete` were provided to the `save_changes!` method call")
|
82
85
|
end
|
83
86
|
end
|
87
|
+
ensure # Always clear working_directory after save
|
84
88
|
self.clear_changes
|
85
89
|
end
|
86
90
|
|
@@ -65,6 +65,7 @@ module Pilot
|
|
65
65
|
UI.important("`skip_waiting_for_build_processing` used and no `changelog` supplied - skipping waiting for build processing")
|
66
66
|
return
|
67
67
|
else
|
68
|
+
UI.important("`skip_waiting_for_build_processing` used and `changelog` supplied - will wait until build appears on App Store Connect, update the changelog and then skip the rest of the remaining of the processing steps.")
|
68
69
|
return_when_build_appears = true
|
69
70
|
end
|
70
71
|
end
|
@@ -72,8 +73,10 @@ module Pilot
|
|
72
73
|
# Calling login again here is needed if login was not called during 'start'
|
73
74
|
login unless should_login_in_start
|
74
75
|
|
75
|
-
|
76
|
-
|
76
|
+
if config[:skip_waiting_for_build_processing].nil?
|
77
|
+
UI.message("If you want to skip waiting for the processing to be finished, use the `skip_waiting_for_build_processing` option")
|
78
|
+
UI.message("Note that if `skip_waiting_for_build_processing` is used but a `changelog` is supplied, this process will wait for the build to appear on App Store Connect, update the changelog and then skip the remaining of the processing steps.")
|
79
|
+
end
|
77
80
|
|
78
81
|
latest_build = wait_for_build_processing_to_be_complete(return_when_build_appears)
|
79
82
|
distribute(options, build: latest_build)
|
@@ -365,12 +368,17 @@ module Pilot
|
|
365
368
|
end
|
366
369
|
|
367
370
|
def reject_build_waiting_for_review(build)
|
368
|
-
waiting_for_review_build = build.app.get_builds(
|
371
|
+
waiting_for_review_build = build.app.get_builds(
|
372
|
+
filter: { "betaAppReviewSubmission.betaReviewState" => "WAITING_FOR_REVIEW,IN_REVIEW",
|
373
|
+
"expired" => false,
|
374
|
+
"preReleaseVersion.version" => build.pre_release_version.version },
|
375
|
+
includes: "betaAppReviewSubmission,preReleaseVersion"
|
376
|
+
).first
|
369
377
|
unless waiting_for_review_build.nil?
|
370
378
|
UI.important("Another build is already in review. Going to remove that build and submit the new one.")
|
371
|
-
UI.important("
|
372
|
-
waiting_for_review_build.
|
373
|
-
UI.success("
|
379
|
+
UI.important("Canceling beta app review submission for build: #{waiting_for_review_build.app_version} - #{waiting_for_review_build.version}")
|
380
|
+
waiting_for_review_build.expire!
|
381
|
+
UI.success("Canceled beta app review submission for previous build: #{waiting_for_review_build.app_version} - #{waiting_for_review_build.version}")
|
374
382
|
end
|
375
383
|
end
|
376
384
|
|
data/pilot/lib/pilot/manager.rb
CHANGED
@@ -85,8 +85,8 @@ module Pilot
|
|
85
85
|
result ||= FastlaneCore::IpaFileAnalyser.fetch_app_platform(config[:ipa]) if config[:ipa]
|
86
86
|
result ||= FastlaneCore::PkgFileAnalyser.fetch_app_platform(config[:pkg]) if config[:pkg]
|
87
87
|
if required
|
88
|
-
result ||= UI.input("Please enter the app's platform (appletvos, ios, osx): ")
|
89
|
-
UI.user_error!("App Platform must be ios, appletvos, or
|
88
|
+
result ||= UI.input("Please enter the app's platform (appletvos, ios, osx, xros): ")
|
89
|
+
UI.user_error!("App Platform must be ios, appletvos, osx, or xros") unless ['ios', 'appletvos', 'osx', 'xros'].include?(result)
|
90
90
|
UI.verbose("App Platform (#{result})")
|
91
91
|
end
|
92
92
|
return result
|
data/pilot/lib/pilot/options.rb
CHANGED
@@ -49,7 +49,7 @@ module Pilot
|
|
49
49
|
description: "The platform to use (optional)",
|
50
50
|
optional: true,
|
51
51
|
verify_block: proc do |value|
|
52
|
-
UI.user_error!("The platform can only be ios, appletvos, or
|
52
|
+
UI.user_error!("The platform can only be ios, appletvos, osx, or xros") unless ['ios', 'appletvos', 'osx', 'xros'].include?(value)
|
53
53
|
end),
|
54
54
|
FastlaneCore::ConfigItem.new(key: :apple_id,
|
55
55
|
short_option: "-p",
|
@@ -23,7 +23,7 @@ module Snapshot
|
|
23
23
|
short_option: "-w",
|
24
24
|
env_name: "SNAPSHOT_WORKSPACE",
|
25
25
|
optional: true,
|
26
|
-
description: "Path the workspace file",
|
26
|
+
description: "Path to the workspace file",
|
27
27
|
verify_block: proc do |value|
|
28
28
|
v = File.expand_path(value.to_s)
|
29
29
|
UI.user_error!("Workspace file not found at path '#{v}'") unless File.exist?(v)
|
@@ -34,7 +34,7 @@ module Snapshot
|
|
34
34
|
short_option: "-p",
|
35
35
|
optional: true,
|
36
36
|
env_name: "SNAPSHOT_PROJECT",
|
37
|
-
description: "Path the project file",
|
37
|
+
description: "Path to the project file",
|
38
38
|
verify_block: proc do |value|
|
39
39
|
v = File.expand_path(value.to_s)
|
40
40
|
UI.user_error!("Project file not found at path '#{v}'") unless File.exist?(v)
|
@@ -34,7 +34,7 @@ module Snapshot
|
|
34
34
|
puts("✅ Successfully created #{snapshot_helper_filename} '#{File.join(path, snapshot_helper_filename)}'".green)
|
35
35
|
puts("✅ Successfully created new Snapfile at '#{snapfile_path}'".green)
|
36
36
|
puts("-------------------------------------------------------".yellow)
|
37
|
-
print_instructions(snapshot_helper_filename: snapshot_helper_filename
|
37
|
+
print_instructions(snapshot_helper_filename: snapshot_helper_filename)
|
38
38
|
end
|
39
39
|
|
40
40
|
def self.print_instructions(snapshot_helper_filename: nil)
|
@@ -69,7 +69,7 @@ module Spaceship
|
|
69
69
|
# Forwarding to class level if using web session.
|
70
70
|
def hostname
|
71
71
|
if @token
|
72
|
-
return "https://api.appstoreconnect.apple.com/
|
72
|
+
return "https://api.appstoreconnect.apple.com/"
|
73
73
|
end
|
74
74
|
return self.class.hostname
|
75
75
|
end
|
@@ -184,7 +184,7 @@ module Spaceship
|
|
184
184
|
if tries.zero?
|
185
185
|
raise error
|
186
186
|
else
|
187
|
-
msg = "Token has expired or has been revoked! Trying to refresh..."
|
187
|
+
msg = "Token has expired, issued-at-time is in the future, or has been revoked! Trying to refresh..."
|
188
188
|
puts(msg) if Spaceship::Globals.verbose?
|
189
189
|
@token.refresh!
|
190
190
|
retry
|