fastlane 2.115.0.beta.20190119200019 → 2.115.0.beta.20190120200101

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 405a7ac48ad02e28a6b26a3dda7458028411060b
4
- data.tar.gz: c1df6b742f5a232012d94e42d2e04c935a3ec201
3
+ metadata.gz: ca7d2255218f2b7f48d1b95b3d48abee6957abd2
4
+ data.tar.gz: a61e4ceef9c859240a85d52a4046c8aab11ad10b
5
5
  SHA512:
6
- metadata.gz: e072c405cda86946bac1343f0a8d9de9ac72ee822385443633ed550022f6fdfd331d78d9d50c632fb7afdc30d8d5d348d837e8df632ca5645b79ce1769e87ced
7
- data.tar.gz: 8a939edd7c093b7c4a291ac5651fcaafa77910279fdf13bb65a1c6d629458bd5f98b42701bb2ffc34698f7d201a3990043ace5cf8c25865e95b9264f4d54adaf
6
+ metadata.gz: 3e69aacb4427d3d84b4f348ecf78cb0eb83e3944e770f9d17673e25765b0ae4cfebb71ef258efd9cee63f79057fb0a88bb1de1aadc5efd5e3470606f03791226
7
+ data.tar.gz: 3a43f626ddbf582c1b5666c544a103d2ffead5b9879db53194de284033323d75a08f082ae51afc264968754ceb282a146a82238610a995caad73d92ceb739a88
@@ -254,7 +254,9 @@ module Fastlane
254
254
  branch_option = "--branch #{branch}" if branch != 'HEAD'
255
255
 
256
256
  UI.message("Cloning remote git repo...")
257
- Actions.sh("GIT_TERMINAL_PROMPT=0 git clone '#{url}' '#{clone_folder}' --depth 1 -n #{branch_option}")
257
+ Helper.with_env_values('GIT_TERMINAL_PROMPT' => '0') do
258
+ Actions.sh("git clone #{url.shellescape} #{clone_folder.shellescape} --depth 1 -n #{branch_option}")
259
+ end
258
260
 
259
261
  unless version.nil?
260
262
  req = Gem::Requirement.new(version)
@@ -263,14 +265,14 @@ module Fastlane
263
265
  UI.user_error!("No tag found matching #{version.inspect}") if checkout_param.nil?
264
266
  end
265
267
 
266
- Actions.sh("cd '#{clone_folder}' && git checkout #{checkout_param} '#{path}'")
268
+ Actions.sh("cd #{clone_folder.shellescape} && git checkout #{checkout_param.shellescape} #{path.shellescape}")
267
269
 
268
270
  # We also want to check out all the local actions of this fastlane setup
269
271
  containing = path.split(File::SEPARATOR)[0..-2]
270
272
  containing = "." if containing.count == 0
271
273
  actions_folder = File.join(containing, "actions")
272
274
  begin
273
- Actions.sh("cd '#{clone_folder}' && git checkout #{checkout_param} '#{actions_folder}'")
275
+ Actions.sh("cd #{clone_folder.shellescape} && git checkout #{checkout_param.shellescape} #{actions_folder.shellescape}")
274
276
  rescue
275
277
  # We don't care about a failure here, as local actions are optional
276
278
  end
@@ -290,10 +292,12 @@ module Fastlane
290
292
 
291
293
  def fetch_remote_tags(folder: nil)
292
294
  UI.message("Fetching remote git tags...")
293
- Actions.sh("cd '#{folder}' && GIT_TERMINAL_PROMPT=0 git fetch --all --tags -q")
295
+ Helper.with_env_values('GIT_TERMINAL_PROMPT' => '0') do
296
+ Actions.sh("cd #{folder.shellescape} && git fetch --all --tags -q")
297
+ end
294
298
 
295
299
  # Fetch all possible tags
296
- git_tags_string = Actions.sh("cd '#{folder}' && git tag -l")
300
+ git_tags_string = Actions.sh("cd #{folder.shellescape} && git tag -l")
297
301
  git_tags = git_tags_string.split("\n")
298
302
 
299
303
  # Sort tags based on their version number
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
- VERSION = '2.115.0.beta.20190119200019'.freeze
2
+ VERSION = '2.115.0.beta.20190120200101'.freeze
3
3
  DESCRIPTION = "The easiest way to automate beta deployments and releases for your iOS and Android apps".freeze
4
4
  MINIMUM_XCODE_RELEASE = "7.0".freeze
5
5
  RUBOCOP_REQUIREMENT = '0.49.1'.freeze
@@ -299,6 +299,22 @@ module FastlaneCore
299
299
  Helper.backticks(command, print: print)
300
300
  end
301
301
 
302
+ # Executes the provided block after adjusting the ENV to have the
303
+ # provided keys and values set as defined in hash. After the block
304
+ # completes, restores the ENV to its previous state.
305
+ def self.with_env_values(hash, &block)
306
+ old_vals = ENV.select { |k, v| hash.include?(k) }
307
+ hash.each do |k, v|
308
+ ENV[k] = hash[k]
309
+ end
310
+ yield
311
+ ensure
312
+ hash.each do |k, v|
313
+ ENV.delete(k) unless old_vals.include?(k)
314
+ ENV[k] = old_vals[k]
315
+ end
316
+ end
317
+
302
318
  # loading indicator
303
319
  #
304
320
 
@@ -91,10 +91,16 @@ module FastlaneCore
91
91
  err = "#{dir}/cms.err"
92
92
  # we want to prevent the error output to mix up with the standard output because of
93
93
  # /dev/null: https://github.com/fastlane/fastlane/issues/6387
94
- if keychain_path.nil?
95
- decoded = `security cms -D -i "#{path}" 2> #{err}`
94
+ if Helper.mac?
95
+ if keychain_path.nil?
96
+ decoded = `security cms -D -i "#{path}" 2> #{err}`
97
+ else
98
+ decoded = `security cms -D -i "#{path}" -k "#{keychain_path.shellescape}" 2> #{err}`
99
+ end
96
100
  else
97
- decoded = `security cms -D -i "#{path}" -k "#{keychain_path.shellescape}" 2> #{err}`
101
+ # `security` only works on Mac, fallback to `openssl`
102
+ # via https://stackoverflow.com/a/14379814/252627
103
+ decoded = `openssl smime -inform der -verify -noverify -in #{path} 2> #{err}`
98
104
  end
99
105
  UI.error("Failure to decode #{path}. Exit: #{$?.exitstatus}: #{File.read(err)}") if $?.exitstatus != 0
100
106
  decoded
@@ -116,7 +116,7 @@ module Match
116
116
  def encrypt_specific_file(path: nil, password: nil)
117
117
  UI.user_error!("No password supplied") if password.to_s.strip.length == 0
118
118
 
119
- data_to_encrypt = File.read(path)
119
+ data_to_encrypt = File.binread(path)
120
120
  salt = SecureRandom.random_bytes(8)
121
121
 
122
122
  # The :: is important, as there is a name clash
@@ -174,6 +174,10 @@ module Match
174
174
  description: "The name of provisioning profile template. If the developer account has provisioning profile templates (aka: custom entitlements), the template name can be found by inspecting the Entitlements drop-down while creating/editing a provisioning profile (e.g. \"Apple Pay Pass Suppression Development\")",
175
175
  optional: true,
176
176
  default_value: nil),
177
+ FastlaneCore::ConfigItem.new(key: :output_path,
178
+ env_name: "MATCH_OUTPUT_PATH",
179
+ description: "Path in which to export certificates, key and profile",
180
+ optional: true),
177
181
 
178
182
  # other
179
183
  FastlaneCore::ConfigItem.new(key: :verbose,
@@ -25,6 +25,8 @@ module Match
25
25
  def run(params)
26
26
  self.files_to_commit = []
27
27
 
28
+ FileUtils.mkdir_p(params[:output_path]) if params[:output_path]
29
+
28
30
  FastlaneCore::PrintTable.print_values(config: params,
29
31
  title: "Summary for match #{Fastlane::VERSION}")
30
32
 
@@ -166,21 +168,31 @@ module Match
166
168
  self.files_to_commit << private_key_path
167
169
  else
168
170
  cert_path = certs.last
169
- UI.message("Installing certificate...")
170
171
 
171
- # Only looking for cert in "custom" (non login.keychain) keychain
172
- # Doing this for backwards compatability
173
- keychain_name = params[:keychain_name] == "login.keychain" ? nil : params[:keychain_name]
172
+ if Helper.mac?
173
+ UI.message("Installing certificate...")
174
+
175
+ # Only looking for cert in "custom" (non login.keychain) keychain
176
+ # Doing this for backwards compatability
177
+ keychain_name = params[:keychain_name] == "login.keychain" ? nil : params[:keychain_name]
178
+
179
+ if FastlaneCore::CertChecker.installed?(cert_path, in_keychain: keychain_name)
180
+ UI.verbose("Certificate '#{File.basename(cert_path)}' is already installed on this machine")
181
+ else
182
+ Utils.import(cert_path, params[:keychain_name], password: params[:keychain_password])
183
+ end
174
184
 
175
- if FastlaneCore::CertChecker.installed?(cert_path, in_keychain: keychain_name)
176
- UI.verbose("Certificate '#{File.basename(cert_path)}' is already installed on this machine")
185
+ # Import the private key
186
+ # there seems to be no good way to check if it's already installed - so just install it
187
+ Utils.import(keys.last, params[:keychain_name], password: params[:keychain_password])
177
188
  else
178
- Utils.import(cert_path, params[:keychain_name], password: params[:keychain_password])
189
+ UI.message("Skipping installation of certificate as it would not work on this operating system.")
179
190
  end
180
191
 
181
- # Import the private key
182
- # there seems to be no good way to check if it's already installed - so just install it
183
- Utils.import(keys.last, params[:keychain_name], password: params[:keychain_password])
192
+ if params[:output_path]
193
+ FileUtils.cp(cert_path, params[:output_path])
194
+ FileUtils.cp(keys.last, params[:output_path])
195
+ end
184
196
 
185
197
  # Get and print info of certificate
186
198
  info = Utils.get_cert_info(cert_path)
@@ -202,7 +214,9 @@ module Match
202
214
  profile_name = names.join("_").gsub("*", '\*') # this is important, as it shouldn't be a wildcard
203
215
  base_dir = File.join(prefixed_working_directory(working_directory), "profiles", prov_type.to_s)
204
216
  profiles = Dir[File.join(base_dir, "#{profile_name}.mobileprovision")]
205
- keychain_path = FastlaneCore::Helper.keychain_path(params[:keychain_name]) unless params[:keychain_name].nil?
217
+ if Helper.mac?
218
+ keychain_path = FastlaneCore::Helper.keychain_path(params[:keychain_name]) unless params[:keychain_name].nil?
219
+ end
206
220
 
207
221
  # Install the provisioning profiles
208
222
  profile = profiles.last
@@ -220,11 +234,13 @@ module Match
220
234
 
221
235
  if profile.nil? || params[:force]
222
236
  if params[:readonly]
223
- all_profiles = Dir.entries(base_dir).reject { |f| f.start_with?(".") }
224
237
  UI.error("No matching provisioning profiles found for '#{profile_name}'")
225
238
  UI.error("A new one cannot be created because you enabled `readonly`")
226
- UI.error("Provisioning profiles in your repo for type `#{prov_type}`:")
227
- all_profiles.each { |p| UI.error("- '#{p}'") }
239
+ if Dir.exist?(base_dir) # folder for `prov_type` does not exist on first match use for that type
240
+ all_profiles = Dir.entries(base_dir).reject { |f| f.start_with?(".") }
241
+ UI.error("Provisioning profiles in your repo for type `#{prov_type}`:")
242
+ all_profiles.each { |p| UI.error("- '#{p}'") }
243
+ end
228
244
  UI.error("If you are certain that a profile should exist, double-check the recent changes to your match repository")
229
245
  UI.user_error!("No matching provisioning profiles found and can not create a new one because you enabled `readonly`. Check the output above for more information.")
230
246
  end
@@ -236,10 +252,16 @@ module Match
236
252
  self.files_to_commit << profile
237
253
  end
238
254
 
239
- installed_profile = FastlaneCore::ProvisioningProfile.install(profile, keychain_path)
255
+ if Helper.mac?
256
+ installed_profile = FastlaneCore::ProvisioningProfile.install(profile, keychain_path)
257
+ end
240
258
  parsed = FastlaneCore::ProvisioningProfile.parse(profile, keychain_path)
241
259
  uuid = parsed["UUID"]
242
260
 
261
+ if params[:output_path]
262
+ FileUtils.cp(profile, params[:output_path])
263
+ end
264
+
243
265
  if spaceship && !spaceship.profile_exists(username: params[:username], uuid: uuid)
244
266
  # This profile is invalid, let's remove the local file and generate a new one
245
267
  File.delete(profile)
@@ -60,7 +60,7 @@ module Match
60
60
  # No existing working directory, creating a new one now
61
61
  self.working_directory = Dir.mktmpdir
62
62
 
63
- command = "git clone '#{self.git_url}' '#{self.working_directory}'"
63
+ command = "git clone #{self.git_url.shellescape} #{self.working_directory.shellescape}"
64
64
  if self.shallow_clone
65
65
  command << " --depth 1 --no-single-branch"
66
66
  elsif self.clone_branch_directly
@@ -74,9 +74,11 @@ module Match
74
74
 
75
75
  begin
76
76
  # GIT_TERMINAL_PROMPT will fail the `git clone` command if user credentials are missing
77
- FastlaneCore::CommandExecutor.execute(command: "GIT_TERMINAL_PROMPT=0 #{command}",
78
- print_all: FastlaneCore::Globals.verbose?,
79
- print_command: FastlaneCore::Globals.verbose?)
77
+ Helper.with_env_values('GIT_TERMINAL_PROMPT' => '0') do
78
+ FastlaneCore::CommandExecutor.execute(command: command,
79
+ print_all: FastlaneCore::Globals.verbose?,
80
+ print_command: FastlaneCore::Globals.verbose?)
81
+ end
80
82
  rescue
81
83
  UI.error("Error cloning certificates repo, please make sure you have read access to the repository you want to use")
82
84
  if self.branch && self.clone_branch_directly
@@ -193,13 +195,15 @@ module Match
193
195
  def git_push(commands: [], commit_message: nil)
194
196
  commit_message ||= generate_commit_message
195
197
  commands << "git commit -m #{commit_message.shellescape}"
196
- commands << "GIT_TERMINAL_PROMPT=0 git push origin #{self.branch.shellescape}"
198
+ commands << "git push origin #{self.branch.shellescape}"
197
199
 
198
200
  UI.message("Pushing changes to remote git repo...")
199
- commands.each do |command|
200
- FastlaneCore::CommandExecutor.execute(command: command,
201
+ Helper.with_env_values('GIT_TERMINAL_PROMPT' => '0') do
202
+ commands.each do |command|
203
+ FastlaneCore::CommandExecutor.execute(command: command,
201
204
  print_all: FastlaneCore::Globals.verbose?,
202
- print_command: FastlaneCore::Globals.verbose?)
205
+ print_command: FastlaneCore::Globals.verbose?)
206
+ end
203
207
  end
204
208
 
205
209
  self.clear_changes
@@ -32,7 +32,7 @@ module Match
32
32
  end
33
33
 
34
34
  def self.get_cert_info(cer_certificate_path)
35
- cert = OpenSSL::X509::Certificate.new(File.read(cer_certificate_path))
35
+ cert = OpenSSL::X509::Certificate.new(File.binread(cer_certificate_path))
36
36
 
37
37
  # openssl output:
38
38
  # subject= /UID={User ID}/CN={Certificate Name}/OU={Certificate User}/O={Organisation}/C={Country}
@@ -54,7 +54,7 @@ module Match
54
54
  .push([openssl_keys_to_readable_keys.fetch("notBefore"), cert.not_before])
55
55
  .push([openssl_keys_to_readable_keys.fetch("notAfter"), cert.not_after])
56
56
  rescue => ex
57
- UI.error(ex)
57
+ UI.error("get_cert_info: #{ex}")
58
58
  return {}
59
59
  end
60
60
 
@@ -3,6 +3,7 @@ require 'credentials_manager/appfile_config'
3
3
  require_relative 'module'
4
4
 
5
5
  module Scan
6
+ # rubocop:disable Metrics/ClassLength
6
7
  class Options
7
8
  def self.verify_type(item_name, acceptable_types, value)
8
9
  type_ok = [Array, String].any? { |type| value.kind_of?(type) }
@@ -322,6 +323,24 @@ module Scan
322
323
  env_name: "SCAN_SLACK_MESSAGE",
323
324
  description: "The message included with each message posted to slack",
324
325
  optional: true),
326
+ FastlaneCore::ConfigItem.new(key: :slack_use_webhook_configured_username_and_icon,
327
+ env_name: "SCAN_SLACK_USE_WEBHOOK_CONFIGURED_USERNAME_AND_ICON",
328
+ description: "Use webhook's default username and icon settings? (true/false)",
329
+ default_value: false,
330
+ type: Boolean,
331
+ optional: true),
332
+ FastlaneCore::ConfigItem.new(key: :slack_username,
333
+ env_name: "SCAN_SLACK_USERNAME",
334
+ description: "Overrides the webhook's username property if slack_use_webhook_configured_username_and_icon is false",
335
+ default_value: "fastlane",
336
+ is_string: true,
337
+ optional: true),
338
+ FastlaneCore::ConfigItem.new(key: :slack_icon_url,
339
+ env_name: "SCAN_SLACK_ICON_URL",
340
+ description: "Overrides the webhook's image property if slack_use_webhook_configured_username_and_icon is false",
341
+ default_value: "https://s3-eu-west-1.amazonaws.com/fastlane.tools/fastlane.png",
342
+ is_string: true,
343
+ optional: true),
325
344
  FastlaneCore::ConfigItem.new(key: :skip_slack,
326
345
  description: "Don't publish to slack, even when an URL is given",
327
346
  is_string: false,
@@ -349,4 +368,5 @@ module Scan
349
368
  ]
350
369
  end
351
370
  end
371
+ # rubocop:enable Metrics/ClassLength
352
372
  end
@@ -15,6 +15,8 @@ module Scan
15
15
  channel = ('#' + channel) unless ['#', '@'].include?(channel[0]) # send message to channel by default
16
16
  end
17
17
 
18
+ username = Scan.config[:slack_use_webhook_configured_username_and_icon] ? nil : Scan.config[:slack_username]
19
+ icon_url = Scan.config[:slack_use_webhook_configured_username_and_icon] ? nil : Scan.config[:slack_icon_url]
18
20
  fields = []
19
21
 
20
22
  if results[:build_errors]
@@ -46,8 +48,8 @@ module Scan
46
48
  channel: channel,
47
49
  slack_url: Scan.config[:slack_url].to_s,
48
50
  success: results[:build_errors].to_i == 0 && results[:failures].to_i == 0,
49
- username: 'fastlane',
50
- icon_url: 'https://s3-eu-west-1.amazonaws.com/fastlane.tools/fastlane.png',
51
+ username: username,
52
+ icon_url: icon_url,
51
53
  payload: {},
52
54
  attachment_properties: {
53
55
  fields: fields
@@ -199,13 +199,16 @@ module Sigh
199
199
  true
200
200
  end
201
201
 
202
- unless Sigh.config[:skip_certificate_verification]
203
- certificates = certificates.find_all do |c|
204
- file = Tempfile.new('cert')
205
- file.write(c.download_raw)
206
- file.close
207
-
208
- FastlaneCore::CertChecker.installed?(file.path)
202
+ # verify certificates
203
+ if Helper.mac?
204
+ unless Sigh.config[:skip_certificate_verification]
205
+ certificates = certificates.find_all do |c|
206
+ file = Tempfile.new('cert')
207
+ file.write(c.download_raw)
208
+ file.close
209
+
210
+ FastlaneCore::CertChecker.installed?(file.path)
211
+ end
209
212
  end
210
213
  end
211
214
 
@@ -221,7 +221,7 @@ module Spaceship
221
221
  end
222
222
 
223
223
  if ENV["DEBUG"]
224
- puts("To run _spaceship_ through a local proxy, use SPACESHIP_DEBUG")
224
+ puts("To run spaceship through a local proxy, use SPACESHIP_DEBUG")
225
225
  end
226
226
  end
227
227
  end
@@ -394,7 +394,7 @@ module Spaceship
394
394
  end
395
395
 
396
396
  # This method is used for both the Apple Dev Portal and App Store Connect
397
- # This will also handle 2 step verification
397
+ # This will also handle 2 step verification and 2 factor authentication
398
398
  #
399
399
  # It is called in `send_login_request` of sub classes (which the method `login`, above, transferred over to via `do_login`)
400
400
  def send_shared_login_request(user, password)
@@ -25,7 +25,6 @@ require_relative 'app_trailer'
25
25
  require_relative 'sandbox_tester'
26
26
  require_relative 'app_details'
27
27
  require_relative 'pricing_tier'
28
- require_relative 'recovery_device'
29
28
  require_relative 'territory'
30
29
  require_relative 'availability'
31
30
  require_relative 'members'
@@ -163,14 +163,13 @@ module Spaceship
163
163
  return unless raw.kind_of?(Hash)
164
164
 
165
165
  data = raw['data'] || raw # sometimes it's with data, sometimes it isn't
166
- error_keys_to_check = [
167
- "sectionErrorKeys",
168
- "sectionInfoKeys",
169
- "sectionWarningKeys",
170
- "validationErrors"
171
- ]
172
- errors_in_data = fetch_errors_in_data(data_section: data, keys: error_keys_to_check)
173
- errors_in_version_info = fetch_errors_in_data(data_section: data, sub_section_name: "versionInfo", keys: error_keys_to_check)
166
+
167
+ error_keys = ["sectionErrorKeys", "validationErrors", "serviceErrors"]
168
+ info_keys = ["sectionInfoKeys", "sectionWarningKeys"]
169
+ error_and_info_keys_to_check = error_keys + info_keys
170
+
171
+ errors_in_data = fetch_errors_in_data(data_section: data, keys: error_and_info_keys_to_check)
172
+ errors_in_version_info = fetch_errors_in_data(data_section: data, sub_section_name: "versionInfo", keys: error_and_info_keys_to_check)
174
173
 
175
174
  # If we have any errors or "info" we need to treat them as warnings or errors
176
175
  if errors_in_data.count == 0 && errors_in_version_info.count == 0
@@ -205,7 +204,6 @@ module Spaceship
205
204
  errors = handle_response_hash.call(data)
206
205
 
207
206
  # Search at data level, as well as "versionInfo" level for errors
208
- error_keys = ["sectionErrorKeys", "validationErrors"]
209
207
  errors_in_data = fetch_errors_in_data(data_section: data, keys: error_keys)
210
208
  errors_in_version_info = fetch_errors_in_data(data_section: data, sub_section_name: "versionInfo", keys: error_keys)
211
209
 
@@ -234,7 +232,6 @@ module Spaceship
234
232
  end
235
233
 
236
234
  # Search at data level, as well as "versionInfo" level for info and warnings
237
- info_keys = ["sectionInfoKeys", "sectionWarningKeys"]
238
235
  info_in_data = fetch_errors_in_data(data_section: data, keys: info_keys)
239
236
  info_in_version_info = fetch_errors_in_data(data_section: data, sub_section_name: "versionInfo", keys: info_keys)
240
237
 
@@ -1,19 +1,14 @@
1
1
  require_relative 'globals'
2
2
  require_relative 'tunes/tunes_client'
3
- require_relative 'tunes/recovery_device'
4
3
 
5
4
  module Spaceship
6
5
  class Client
7
6
  def handle_two_step_or_factor(response)
7
+ # extract `x-apple-id-session-id` and `scnt` from response, to be used by `update_request_headers`
8
8
  @x_apple_id_session_id = response["x-apple-id-session-id"]
9
9
  @scnt = response["scnt"]
10
10
 
11
- puts("")
12
- puts("Two-step Verification (4 digits code) or Two-factor Authentication (6 digits code) is enabled for account '#{self.user}'")
13
- puts("More information about Two-step Verification (4 digits code): https://support.apple.com/en-us/HT204152")
14
- puts("More information about Two-factor Authentication (6 digits code): https://support.apple.com/en-us/HT204915")
15
- puts("")
16
-
11
+ # get authentication options
17
12
  r = request(:get) do |req|
18
13
  req.url("https://idmsa.apple.com/appleauth/auth")
19
14
  update_request_headers(req)
@@ -28,35 +23,28 @@ module Spaceship
28
23
  end
29
24
  end
30
25
 
31
- def handle_two_step(r)
32
- if r.body.fetch("securityCode", {})["tooManyCodesLock"].to_s.length > 0
26
+ def handle_two_step(response)
27
+ if response.body.fetch("securityCode", {})["tooManyCodesLock"].to_s.length > 0
33
28
  raise Tunes::Error.new, "Too many verification codes have been sent. Enter the last code you received, use one of your devices, or try again later."
34
29
  end
35
30
 
36
- old_client = (begin
37
- Tunes::RecoveryDevice.client
38
- rescue
39
- nil # since client might be nil, which raises an exception
40
- end)
41
- Tunes::RecoveryDevice.client = self # temporary set it as it's required by the factory method
42
- devices = r.body["trustedDevices"].collect do |current|
43
- Tunes::RecoveryDevice.factory(current)
44
- end
45
- Tunes::RecoveryDevice.client = old_client
46
-
47
31
  puts("Two-step Verification (4 digits code) is enabled for account '#{self.user}'")
48
- puts("Please select a device to verify your identity")
49
- available = devices.collect do |c|
50
- "#{c.name}\t#{c.model_name || 'SMS'}\t(#{c.device_id})"
32
+ puts("More information about Two-step Verification: https://support.apple.com/en-us/HT204152")
33
+ puts("")
34
+
35
+ puts("Please select a trusted device to verify your identity")
36
+ available = response.body["trustedDevices"].collect do |current|
37
+ "#{current['name']}\t#{current['modelName'] || 'SMS'}\t(#{current['id']})"
51
38
  end
52
39
  result = choose(*available)
40
+
53
41
  device_id = result.match(/.*\t.*\t\((.*)\)/)[1]
54
- select_device(r, device_id)
42
+ handle_two_step_for_device(device_id)
55
43
  end
56
44
 
57
45
  # this is extracted into its own method so it can be called multiple times (see end)
58
- def select_device(r, device_id)
59
- # Request Token
46
+ def handle_two_step_for_device(device_id)
47
+ # Request token to device
60
48
  r = request(:put) do |req|
61
49
  req.url("https://idmsa.apple.com/appleauth/auth/verify/device/#{device_id}/securitycode")
62
50
  update_request_headers(req)
@@ -70,7 +58,7 @@ module Spaceship
70
58
  code = ask("Please enter the 4 digit code: ")
71
59
  puts("Requesting session...")
72
60
 
73
- # Send token back to server to get a valid session
61
+ # Send token to server to get a valid session
74
62
  r = request(:post) do |req|
75
63
  req.url("https://idmsa.apple.com/appleauth/auth/verify/device/#{device_id}/securitycode")
76
64
  req.headers['Content-Type'] = 'application/json'
@@ -101,7 +89,7 @@ module Spaceship
101
89
  # }
102
90
  if ex.to_s.include?("verification code") # to have a nicer output
103
91
  puts("Error: Incorrect verification code")
104
- return select_device(r, device_id)
92
+ return handle_two_step_for_device(device_id)
105
93
  end
106
94
 
107
95
  raise ex
@@ -112,29 +100,100 @@ module Spaceship
112
100
  return true
113
101
  end
114
102
 
115
- def handle_two_factor(response)
116
- two_factor_url = "https://github.com/fastlane/fastlane/tree/master/spaceship#2-step-verification"
117
- puts("Two-factor Authentication (6 digits code) is enabled for account '#{self.user}'")
103
+ def handle_two_factor(response, depth = 0)
104
+ if depth == 0
105
+ puts("Two-factor Authentication (6 digits code) is enabled for account '#{self.user}'")
106
+ puts("More information about Two-factor Authentication: https://support.apple.com/en-us/HT204915")
107
+ puts("")
108
+
109
+ two_factor_url = "https://github.com/fastlane/fastlane/tree/master/spaceship#2-step-verification"
110
+ puts("If you're running this in a non-interactive session (e.g. server or CI)")
111
+ puts("check out #{two_factor_url}")
112
+ end
118
113
 
119
- puts("If you're running this in a non-interactive session (e.g. server or CI)")
120
- puts("check out #{two_factor_url}")
114
+ # "verification code" has already be pushed to devices
121
115
 
122
116
  security_code = response.body["securityCode"]
123
- # securityCode =
124
- # {"length"=>6,
125
- # "tooManyCodesSent"=>false,
126
- # "tooManyCodesValidated"=>false,
127
- # "securityCodeLocked"=>false}
117
+ # "securityCode": {
118
+ # "length": 6,
119
+ # "tooManyCodesSent": false,
120
+ # "tooManyCodesValidated": false,
121
+ # "securityCodeLocked": false
122
+ # },
128
123
  code_length = security_code["length"]
129
- code = ask("Please enter the #{code_length} digit code: ")
124
+
125
+ puts("")
126
+ puts("(Input `sms` to escape this prompt and select a trusted phone number to send the code as a text message)")
127
+ code_type = 'trusteddevice'
128
+ code = ask("Please enter the #{code_length} digit code:")
129
+ body = { "securityCode" => { "code" => code.to_s } }.to_json
130
+
131
+ if code == 'sms'
132
+ code_type = 'phone'
133
+ body = request_two_factor_code_from_phone(response.body["trustedPhoneNumbers"], code_length)
134
+ end
135
+
130
136
  puts("Requesting session...")
131
137
 
132
- # Send securityCode back to server to get a valid session
138
+ # Send "verification code" back to server to get a valid session
133
139
  r = request(:post) do |req|
134
- req.url("https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode")
140
+ req.url("https://idmsa.apple.com/appleauth/auth/verify/#{code_type}/securitycode")
135
141
  req.headers['Content-Type'] = 'application/json'
136
- req.body = { "securityCode" => { "code" => code.to_s } }.to_json
142
+ req.body = body
143
+ update_request_headers(req)
144
+ end
145
+
146
+ begin
147
+ # we use `Spaceship::TunesClient.new.handle_itc_response`
148
+ # since this might be from the Dev Portal, but for 2 factor
149
+ Spaceship::TunesClient.new.handle_itc_response(r.body) # this will fail if the code is invalid
150
+ rescue => ex
151
+ # If the code was entered wrong
152
+ # {
153
+ # "service_errors": [{
154
+ # "code": "-21669",
155
+ # "title": "Incorrect Verification Code",
156
+ # "message": "Incorrect verification code."
157
+ # }],
158
+ # "hasError": true
159
+ # }
137
160
 
161
+ if ex.to_s.include?("verification code") # to have a nicer output
162
+ puts("Error: Incorrect verification code")
163
+ depth += 1
164
+ return handle_two_factor(response, depth)
165
+ end
166
+
167
+ raise ex
168
+ end
169
+
170
+ store_session
171
+
172
+ return true
173
+ end
174
+
175
+ def get_id_for_number(phone_numbers, result)
176
+ phone_numbers.each do |phone|
177
+ phone_id = phone['id']
178
+ return phone_id if phone['numberWithDialCode'] == result
179
+ end
180
+ end
181
+
182
+ def request_two_factor_code_from_phone(phone_numbers, code_length)
183
+ puts("Please select a trusted phone number to send code to:")
184
+
185
+ available = phone_numbers.collect do |current|
186
+ current['numberWithDialCode']
187
+ end
188
+ result = choose(*available)
189
+
190
+ phone_id = get_id_for_number(phone_numbers, result)
191
+
192
+ # Request code
193
+ r = request(:put) do |req|
194
+ req.url("https://idmsa.apple.com/appleauth/auth/verify/phone")
195
+ req.headers['Content-Type'] = 'application/json'
196
+ req.body = { "phoneNumber" => { "id" => phone_id }, "mode" => "sms" }.to_json
138
197
  update_request_headers(req)
139
198
  end
140
199
 
@@ -142,9 +201,10 @@ module Spaceship
142
201
  # since this might be from the Dev Portal, but for 2 step
143
202
  Spaceship::TunesClient.new.handle_itc_response(r.body)
144
203
 
145
- store_session
204
+ puts("Successfully requested text message")
146
205
 
147
- return true
206
+ code = ask("Please enter the #{code_length} digit code you received at #{result}:")
207
+ { "securityCode" => { "code" => code.to_s }, "phoneNumber" => { "id" => phone_id }, "mode" => "sms" }.to_json
148
208
  end
149
209
 
150
210
  def store_session
@@ -162,7 +222,6 @@ module Spaceship
162
222
 
163
223
  request(:get) do |req|
164
224
  req.url("https://idmsa.apple.com/appleauth/auth/2sv/trust")
165
-
166
225
  update_request_headers(req)
167
226
  end
168
227
  # This request will fail if the user isn't added to a team on iTC
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.115.0.beta.20190119200019
4
+ version: 2.115.0.beta.20190120200101
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danielle Tomlinson
@@ -27,7 +27,7 @@ authors:
27
27
  autorequire:
28
28
  bindir: bin
29
29
  cert_chain: []
30
- date: 2019-01-19 00:00:00.000000000 Z
30
+ date: 2019-01-20 00:00:00.000000000 Z
31
31
  dependencies:
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: slack-notifier
@@ -1657,7 +1657,6 @@ files:
1657
1657
  - spaceship/lib/spaceship/tunes/members.rb
1658
1658
  - spaceship/lib/spaceship/tunes/pricing_info.rb
1659
1659
  - spaceship/lib/spaceship/tunes/pricing_tier.rb
1660
- - spaceship/lib/spaceship/tunes/recovery_device.rb
1661
1660
  - spaceship/lib/spaceship/tunes/sandbox_tester.rb
1662
1661
  - spaceship/lib/spaceship/tunes/spaceship.rb
1663
1662
  - spaceship/lib/spaceship/tunes/territory.rb
@@ -1688,24 +1687,24 @@ metadata:
1688
1687
  post_install_message:
1689
1688
  rdoc_options: []
1690
1689
  require_paths:
1691
- - cert/lib
1692
- - spaceship/lib
1693
- - scan/lib
1690
+ - supply/lib
1691
+ - precheck/lib
1692
+ - pem/lib
1694
1693
  - fastlane/lib
1694
+ - spaceship/lib
1695
1695
  - snapshot/lib
1696
- - pem/lib
1697
- - gym/lib
1698
- - credentials_manager/lib
1699
- - screengrab/lib
1700
- - pilot/lib
1701
1696
  - match/lib
1697
+ - pilot/lib
1702
1698
  - sigh/lib
1703
- - fastlane_core/lib
1704
- - supply/lib
1705
- - deliver/lib
1706
- - precheck/lib
1699
+ - screengrab/lib
1707
1700
  - frameit/lib
1701
+ - fastlane_core/lib
1702
+ - cert/lib
1703
+ - credentials_manager/lib
1708
1704
  - produce/lib
1705
+ - gym/lib
1706
+ - deliver/lib
1707
+ - scan/lib
1709
1708
  required_ruby_version: !ruby/object:Gem::Requirement
1710
1709
  requirements:
1711
1710
  - - ">="
@@ -1,65 +0,0 @@
1
- require_relative 'tunes_base'
2
-
3
- module Spaceship
4
- module Tunes
5
- class RecoveryDevice < TunesBase
6
- # @return (String) ID provided by Apple
7
- # @example
8
- # "1801231651"
9
- attr_accessor :device_id
10
-
11
- # @return (String) The name of the device
12
- # @example
13
- # "Felix Krause's iPhone 6"
14
- attr_accessor :name
15
-
16
- # @return (Bool) This device looks suspicious [add emoji here]
17
- # this will probably always be true, otherwise the device
18
- # doesn't show up
19
- # @example
20
- # true
21
- attr_accessor :trusted
22
-
23
- # @return (Bool)
24
- # @example
25
- # true
26
- attr_accessor :status
27
-
28
- # @return (String) Remote URL to an image representing this device
29
- # This shows the attention to detail by Apple <3
30
- # @example
31
- # "https://appleid.cdn-apple.com/static/deviceImages-5.0/iPhone/iPhone8,1-e4e7e8-dadcdb/online-sourcelist__3x.png"
32
- # @example
33
- # "https://appleid.cdn-apple.com/appleauth/static/bin/cb2613252489/images/sms@3x.png"
34
- attr_accessor :device_image
35
-
36
- # @return (String)
37
- # @example
38
- # "iPad Air"
39
- # @example
40
- # nil # e.g. when it's a phone number
41
- attr_accessor :model_name
42
-
43
- # @return (String)
44
- # @example
45
- # "79"
46
- attr_accessor :last_two_digits
47
-
48
- # @return (Number)
49
- # @example
50
- # 1446488271926
51
- attr_accessor :update_date
52
-
53
- attr_mapping(
54
- 'id' => :device_id,
55
- 'name' => :name,
56
- 'trusted' => :trusted,
57
- 'status' => :status,
58
- 'imageLocation3x' => :device_image,
59
- 'modelName' => :model_name,
60
- 'lastTwoDigits' => :last_two_digits,
61
- 'updateDate' => :update_date
62
- )
63
- end
64
- end
65
- end