fastlane 2.241.1 → 2.241.2

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -38
  3. data/fastlane/lib/fastlane/commands_generator.rb +7 -1
  4. data/fastlane/lib/fastlane/server/socket_server.rb +57 -7
  5. data/fastlane/lib/fastlane/swift_lane_manager.rb +32 -11
  6. data/fastlane/lib/fastlane/version.rb +1 -1
  7. data/fastlane/swift/ArgumentProcessor.swift +10 -2
  8. data/fastlane/swift/Deliverfile.swift +1 -1
  9. data/fastlane/swift/DeliverfileProtocol.swift +1 -1
  10. data/fastlane/swift/Fastlane.swift +2 -2
  11. data/fastlane/swift/Gymfile.swift +1 -1
  12. data/fastlane/swift/GymfileProtocol.swift +1 -1
  13. data/fastlane/swift/MainProcess.swift +27 -0
  14. data/fastlane/swift/Matchfile.swift +1 -1
  15. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  16. data/fastlane/swift/Precheckfile.swift +1 -1
  17. data/fastlane/swift/PrecheckfileProtocol.swift +1 -1
  18. data/fastlane/swift/RubyCommand.swift +1 -9
  19. data/fastlane/swift/RubyCommandable.swift +3 -2
  20. data/fastlane/swift/Scanfile.swift +1 -1
  21. data/fastlane/swift/ScanfileProtocol.swift +1 -1
  22. data/fastlane/swift/Screengrabfile.swift +1 -1
  23. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  24. data/fastlane/swift/Snapshotfile.swift +1 -1
  25. data/fastlane/swift/SnapshotfileProtocol.swift +1 -1
  26. data/fastlane/swift/main.swift +1 -1
  27. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +19 -5
  28. data/match/lib/match/runner.rb +2 -0
  29. data/match/lib/match/storage/gitlab/secure_file.rb +2 -1
  30. data/match/lib/match/storage/google_cloud_storage.rb +1 -1
  31. data/match/lib/match/storage/interface.rb +10 -0
  32. data/match/lib/match/storage/s3_storage.rb +1 -1
  33. data/sigh/lib/sigh/manager.rb +5 -1
  34. data/spaceship/lib/spaceship/client.rb +7 -1
  35. metadata +18 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41082316db6260b452820acb2f6254dd8666b30092363544b46f4bf7da8a372d
4
- data.tar.gz: 72020a428f0804273e1dbe44b2ae1dcac74b2eede448370932cbf63b94f3d5f4
3
+ metadata.gz: de93b89428a3de2726024a5c924723c7fcb3f3096402884e4139749e8d22cb39
4
+ data.tar.gz: 25aa9615e76bb0f0e503ebf6cad2170178938f249027994b8b463ce45bd94403
5
5
  SHA512:
6
- metadata.gz: da0f67a4715290e286ef7faf42aa12b73ccc2c3de580f96807b22dda347411b0fb138c21284a91f18f983825555161bd6072556fb980d1c14976a8f76a3a1b8a
7
- data.tar.gz: c22918d93563fd3181c3f4cf3fa21c48873e16b0130ac8a5453145311943dd34b822e250e85ddcbcaccfc2b843934a355e562654ce2144cfdea6f56f67011b62
6
+ metadata.gz: a3ae511471c7729e7854ac3aa91ea06d2ceb306e87b7e968aab9be77ce05d0ba6e3b127e3e720598380084686158c2b40b2e587cf78d402b5ec1a6aebcf4e42c
7
+ data.tar.gz: 84016a1687602afdde1ab12f6c6dacb1f22550c6ab3c3724e79638435dea908cdcf58735519523ba670074190caddb6bb4b0fb792c359b56b181751acb0347eb
data/README.md CHANGED
@@ -30,12 +30,6 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
30
30
  <!-- team:start -->
31
31
  <table id='team'>
32
32
  <tr>
33
- <td id='stefan-natchev'>
34
- <a href='https://github.com/snatchev'>
35
- <img src='https://github.com/snatchev.png?size=200' width='140'>
36
- </a>
37
- <h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
38
- </td>
39
33
  <td id='jan-piotrowski'>
40
34
  <a href='https://github.com/janpio'>
41
35
  <img src='https://github.com/janpio.png?size=200' width='140'>
@@ -48,43 +42,49 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
48
42
  </a>
49
43
  <h4 align='center'><a href='https://twitter.com/ibotpeaches'>Connor Tumbleson</a></h4>
50
44
  </td>
45
+ <td id='olivier-halligon'>
46
+ <a href='https://github.com/AliSoftware'>
47
+ <img src='https://github.com/AliSoftware.png?size=200' width='140'>
48
+ </a>
49
+ <h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
50
+ </td>
51
51
  <td id='felix-krause'>
52
52
  <a href='https://github.com/KrauseFx'>
53
53
  <img src='https://github.com/KrauseFx.png?size=200' width='140'>
54
54
  </a>
55
55
  <h4 align='center'><a href='https://twitter.com/KrauseFx'>Felix Krause</a></h4>
56
56
  </td>
57
- <td id='jérôme-lacoste'>
58
- <a href='https://github.com/lacostej'>
59
- <img src='https://github.com/lacostej.png?size=200' width='140'>
57
+ <td id='josh-holtz'>
58
+ <a href='https://github.com/joshdholtz'>
59
+ <img src='https://github.com/joshdholtz.png?size=200' width='140'>
60
60
  </a>
61
- <h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
61
+ <h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
62
62
  </td>
63
63
  </tr>
64
64
  <tr>
65
- <td id='roger-oba'>
66
- <a href='https://github.com/rogerluan'>
67
- <img src='https://github.com/rogerluan.png?size=200' width='140'>
68
- </a>
69
- <h4 align='center'><a href='https://twitter.com/rogerluan_'>Roger Oba</a></h4>
70
- </td>
71
65
  <td id='joshua-liebowitz'>
72
66
  <a href='https://github.com/taquitos'>
73
67
  <img src='https://github.com/taquitos.png?size=200' width='140'>
74
68
  </a>
75
69
  <h4 align='center'><a href='https://twitter.com/taquitos'>Joshua Liebowitz</a></h4>
76
70
  </td>
77
- <td id='olivier-halligon'>
78
- <a href='https://github.com/AliSoftware'>
79
- <img src='https://github.com/AliSoftware.png?size=200' width='140'>
71
+ <td id='jérôme-lacoste'>
72
+ <a href='https://github.com/lacostej'>
73
+ <img src='https://github.com/lacostej.png?size=200' width='140'>
80
74
  </a>
81
- <h4 align='center'><a href='https://twitter.com/aligatr'>Olivier Halligon</a></h4>
75
+ <h4 align='center'><a href='https://twitter.com/lacostej'>Jérôme Lacoste</a></h4>
82
76
  </td>
83
- <td id='josh-holtz'>
84
- <a href='https://github.com/joshdholtz'>
85
- <img src='https://github.com/joshdholtz.png?size=200' width='140'>
77
+ <td id='stefan-natchev'>
78
+ <a href='https://github.com/snatchev'>
79
+ <img src='https://github.com/snatchev.png?size=200' width='140'>
86
80
  </a>
87
- <h4 align='center'><a href='https://twitter.com/joshdholtz'>Josh Holtz</a></h4>
81
+ <h4 align='center'><a href='https://twitter.com/snatchev'>Stefan Natchev</a></h4>
82
+ </td>
83
+ <td id='roger-oba'>
84
+ <a href='https://github.com/rogerluan'>
85
+ <img src='https://github.com/rogerluan.png?size=200' width='140'>
86
+ </a>
87
+ <h4 align='center'><a href='https://twitter.com/rogerluan_'>Roger Oba</a></h4>
88
88
  </td>
89
89
  </tr>
90
90
  </table>
@@ -94,24 +94,24 @@ If the above doesn't help, please [submit an issue](https://github.com/fastlane/
94
94
  _fastlane_ wouldn't be what it is today without the countless hours these former team members have dedicated to the project ❤️
95
95
 
96
96
  <p id='alumni'>
97
- <a href='https://github.com/lmirosevic'>Luka Mirosevic</a>,
98
- <a href='https://github.com/minuscorp'>Jorge Revuelta H</a>,
99
- <a href='https://github.com/milch'>Manu Wallner</a>,
100
- <a href='https://github.com/nafu'>Fumiya Nakamura</a>,
101
- <a href='https://github.com/revolter'>Iulian Onofrei</a>,
102
- <a href='https://github.com/ainame'>Satoshi Namai</a>,
103
- <a href='https://github.com/getaaron'>Aaron Brager</a>,
104
- <a href='https://github.com/hjanuschka'>Helmut Januschka</a>,
97
+ <a href='https://github.com/endocrimes'>Danielle Tomlinson</a>,
105
98
  <a href='https://github.com/max-ott'>Max Ott</a>,
99
+ <a href='https://github.com/crazymanish'>Manish Rathi</a>,
100
+ <a href='https://github.com/lucgrabowski'>Łukasz Grabowski</a>,
106
101
  <a href='https://github.com/mollyIV'>Daniel Jankowski</a>,
107
- <a href='https://github.com/matthewellis'>Matthew Ellis</a>,
102
+ <a href='https://github.com/ainame'>Satoshi Namai</a>,
103
+ <a href='https://github.com/getaaron'>Aaron Brager</a>,
104
+ <a href='https://github.com/jdee'>Jimmy Dee</a>,
105
+ <a href='https://github.com/revolter'>Iulian Onofrei</a>,
108
106
  <a href='https://github.com/mgrebenets'>Maksym Grebenets</a>,
109
107
  <a href='https://github.com/armcburney'>Andrew McBurney</a>,
110
- <a href='https://github.com/giginet'>Kohki Miki</a>,
111
- <a href='https://github.com/endocrimes'>Danielle Tomlinson</a>,
112
- <a href='https://github.com/crazymanish'>Manish Rathi</a>,
113
- <a href='https://github.com/jdee'>Jimmy Dee</a>,
114
- <a href='https://github.com/lucgrabowski'>Łukasz Grabowski</a>
108
+ <a href='https://github.com/lmirosevic'>Luka Mirosevic</a>,
109
+ <a href='https://github.com/milch'>Manu Wallner</a>,
110
+ <a href='https://github.com/minuscorp'>Jorge Revuelta H</a>,
111
+ <a href='https://github.com/matthewellis'>Matthew Ellis</a>,
112
+ <a href='https://github.com/hjanuschka'>Helmut Januschka</a>,
113
+ <a href='https://github.com/nafu'>Fumiya Nakamura</a>,
114
+ <a href='https://github.com/giginet'>Kohki Miki</a>
115
115
  </p>
116
116
  <!-- team:end -->
117
117
 
@@ -144,11 +144,16 @@ module Fastlane
144
144
  c.option('-s', '--stay_alive', 'Keeps socket server up even after error or disconnects, requires CTRL-C to kill.')
145
145
  c.option('-c seconds', '--connection_timeout', 'Sets connection established timeout')
146
146
  c.option('-p port', '--port', "Sets the port on localhost for the socket connection")
147
+ c.option('--token_file path', "File holding the token every command must carry (default: FASTLANE_SOCKET_SERVER_TOKEN_FILE)")
147
148
  c.action do |args, options|
148
149
  default_connection_timeout = 5
149
150
  stay_alive = options.stay_alive || false
150
151
  connection_timeout = options.connection_timeout || default_connection_timeout
151
152
  port = options.port || 2000
153
+ token_file = options.token_file || ENV["FASTLANE_SOCKET_SERVER_TOKEN_FILE"]
154
+ token = File.read(token_file).strip if token_file
155
+ UI.user_error!("The token file #{token_file} is empty") if token_file && token.empty?
156
+ UI.important("No --token_file given: any local process that connects first can run actions") unless token
152
157
 
153
158
  if stay_alive && options.connection_timeout.nil?
154
159
  UI.important("stay_alive is set, but the connection timeout is not, this will give you #{default_connection_timeout} seconds to (re)connect")
@@ -162,7 +167,8 @@ module Fastlane
162
167
  command_executor: command_executor,
163
168
  connection_timeout: connection_timeout,
164
169
  stay_alive: stay_alive,
165
- port: port
170
+ port: port,
171
+ token: token
166
172
  )
167
173
  result = server.start
168
174
  UI.success("Result: #{result}") if result
@@ -3,6 +3,7 @@ require 'fastlane/server/command_parser.rb'
3
3
  require 'fastlane/server/json_return_value_processor.rb'
4
4
  require 'socket'
5
5
  require 'json'
6
+ require 'openssl'
6
7
 
7
8
  module Fastlane
8
9
  class SocketServer
@@ -20,7 +21,8 @@ module Fastlane
20
21
  return_value_processor: nil,
21
22
  connection_timeout: 5,
22
23
  stay_alive: false,
23
- port: 2000
24
+ port: 2000,
25
+ token: nil
24
26
  )
25
27
  if return_value_processor.nil?
26
28
  return_value_processor = JSONReturnValueProcessor.new
@@ -31,6 +33,8 @@ module Fastlane
31
33
  @connection_timeout = connection_timeout.to_i
32
34
  @stay_alive = stay_alive
33
35
  @port = port.to_i
36
+ # When set, every command must carry it: the runner reads it from a file only this user can read
37
+ @token = token
34
38
  end
35
39
 
36
40
  # this is the public API, don't call anything else
@@ -78,6 +82,11 @@ module Fastlane
78
82
  end
79
83
 
80
84
  def parse_and_execute_command(command_string: nil)
85
+ unless token_matches?(command_string)
86
+ UI.error("Refusing a command without this run's token: it does not come from the FastlaneRunner fastlane started")
87
+ return COMMAND_EXECUTION_STATE[:error]
88
+ end
89
+
81
90
  command = CommandParser.parse(json: command_string)
82
91
  case command
83
92
  when ControlCommand
@@ -137,15 +146,17 @@ module Fastlane
137
146
  end
138
147
 
139
148
  def listen
140
- @server = TCPServer.open('localhost', @port) # Socket to listen on port 2000
141
- UI.verbose("Waiting for #{@connection_timeout} seconds for a connection from FastlaneRunner")
149
+ @servers = listening_sockets
150
+ @port = @servers.first.local_address.ip_port
151
+ UI.verbose("Waiting for #{@connection_timeout} seconds for a connection from FastlaneRunner on port #{@port}")
142
152
 
143
- # set thread local to ready so we can check it
153
+ # set thread locals so the caller can tell the runner where to connect
154
+ Thread.current[:port] = @port
144
155
  Thread.current[:ready] = true
145
156
  @client = nil
146
157
  begin
147
158
  Timeout.timeout(@connection_timeout) do
148
- @client = @server.accept # Wait for a client to connect
159
+ @client = accept_first_connection
149
160
  end
150
161
  rescue Timeout::Error
151
162
  UI.user_error!("fastlane failed to receive a connection from the FastlaneRunner binary after #{@connection_timeout} seconds, shutting down")
@@ -166,8 +177,47 @@ module Fastlane
166
177
  @client.close
167
178
  @client = nil
168
179
 
169
- @server.close
170
- @server = nil
180
+ close_servers
181
+ end
182
+
183
+ # A socket on every address localhost resolves to. The runner connects to
184
+ # `localhost` and tries those addresses in turn, so another process listening
185
+ # on one this server did not take would receive its commands.
186
+ def listening_sockets
187
+ # Port 0: a free port, the same on every address
188
+ return Socket.tcp_server_sockets('localhost', 0) if @port.zero?
189
+
190
+ sockets = []
191
+ Addrinfo.getaddrinfo('localhost', @port, nil, :STREAM).uniq(&:ip_address).each do |address|
192
+ sockets << address.listen
193
+ rescue SystemCallError => e
194
+ sockets.each(&:close)
195
+ UI.user_error!("fastlane could not listen on #{address.inspect_sockaddr} for the FastlaneRunner binary: #{e.message}")
196
+ end
197
+ sockets
198
+ end
199
+
200
+ # The first connection on any of the sockets. They are closed once it is
201
+ # accepted, so nothing else can connect while the runner is talking to us.
202
+ def accept_first_connection
203
+ ready, = IO.select(@servers)
204
+ ready.first.accept.first
205
+ ensure
206
+ close_servers
207
+ end
208
+
209
+ def token_matches?(command_string)
210
+ return true if @token.nil?
211
+
212
+ token = JSON.parse(command_string)['token']
213
+ token.kind_of?(String) && OpenSSL.secure_compare(token, @token)
214
+ rescue JSON::ParserError, TypeError
215
+ false
216
+ end
217
+
218
+ def close_servers
219
+ (@servers || []).each { |server| server.close unless server.closed? }
220
+ @servers = nil
171
221
  end
172
222
 
173
223
  # record fastlane action command and then execute it
@@ -1,10 +1,12 @@
1
1
  require_relative 'lane_manager_base.rb'
2
2
  require_relative 'swift_runner_upgrader.rb'
3
+ require 'securerandom'
4
+ require 'tmpdir'
3
5
 
4
6
  module Fastlane
5
7
  class SwiftLaneManager < LaneManagerBase
6
8
  # The runner protocol version this fastlane ships, see main.swift
7
- RUNNER_PROTOCOL_VERSION = 1
9
+ RUNNER_PROTOCOL_VERSION = 2
8
10
  RUNNER_PROTOCOL_VERSION_REGEX = /FastlaneRunnerProtocolVersion \[(\d+)\]/
9
11
 
10
12
  # @param lane_name The name of the lane to execute
@@ -31,15 +33,25 @@ module Fastlane
31
33
 
32
34
  self.ensure_runner_built!
33
35
  self.warn_if_runner_outdated
34
- swift_server_port ||= 2000
35
- socket_thread = self.start_socket_thread(port: swift_server_port)
36
- sleep(0.250) while socket_thread[:ready].nil?
36
+ # A secret only this user can read, which the runner sends back with every command
37
+ token_dir = Dir.mktmpdir('fastlane-runner')
38
+ token_file = File.join(token_dir, 'token')
39
+ token = SecureRandom.hex(32)
40
+ File.write(token_file, token, perm: 0o600)
41
+ token = nil unless self.runner_sends_token?
42
+ # Port 0 lets the server pick a free port, which no other process can take beforehand
43
+ socket_thread = self.start_socket_thread(port: swift_server_port || 0, token: token)
44
+ sleep(0.250) while socket_thread[:ready].nil? && socket_thread.alive?
45
+ # The thread ended without listening: join raises its error rather than waiting forever
46
+ socket_thread.join unless socket_thread[:ready]
37
47
  # wait on socket_thread to be in ready state, then start the runner thread
38
- self.cruise_swift_lane_in_thread(lane, parameters, swift_server_port)
48
+ self.cruise_swift_lane_in_thread(lane, parameters, socket_thread[:port], token_file)
39
49
 
40
50
  socket_thread.value
41
51
  rescue Exception => ex # rubocop:disable Lint/RescueException
42
52
  e = ex
53
+ ensure
54
+ FileUtils.remove_entry(token_dir) if token_dir
43
55
  end
44
56
  # If we have a thread exception, drop that in the exception
45
57
  # won't ever have a situation where e is non-nil, and socket_thread[:exception] is also non-nil
@@ -84,14 +96,14 @@ module Fastlane
84
96
  end
85
97
  end
86
98
 
87
- def self.cruise_swift_lane_in_thread(lane, parameters = nil, swift_server_port)
99
+ def self.cruise_swift_lane_in_thread(lane, parameters = nil, swift_server_port, token_file)
88
100
  if parameters.nil?
89
101
  parameters = {}
90
102
  end
91
103
 
92
104
  parameter_string = ""
93
105
  parameters.each do |key, value|
94
- parameter_string += " #{key} #{value}"
106
+ parameter_string += " #{key.to_s.shellescape} #{value.to_s.shellescape}"
95
107
  end
96
108
 
97
109
  if FastlaneCore::Globals.verbose?
@@ -99,13 +111,16 @@ module Fastlane
99
111
  end
100
112
 
101
113
  parameter_string += " swiftServerPort #{swift_server_port}"
114
+ parameter_string += " swiftServerTokenFile #{token_file.shellescape}"
115
+
116
+ command = "#{FastlaneCore::FastlaneFolder.swift_runner_path.shellescape} lane #{lane.to_s.shellescape}#{parameter_string}"
102
117
 
103
118
  return Thread.new do
104
119
  if FastlaneCore::Globals.verbose?
105
- return_value = Actions.sh(%(#{FastlaneCore::FastlaneFolder.swift_runner_path} lane #{lane}#{parameter_string}))
120
+ return_value = Actions.sh(command)
106
121
  UI.message("runner output: ".yellow + return_value)
107
122
  else
108
- Actions.sh(%(#{FastlaneCore::FastlaneFolder.swift_runner_path} lane #{lane}#{parameter_string} > /dev/null))
123
+ Actions.sh("#{command} > /dev/null")
109
124
  end
110
125
  end
111
126
  end
@@ -223,13 +238,13 @@ module Fastlane
223
238
  return project_modified
224
239
  end
225
240
 
226
- def self.start_socket_thread(port: nil)
241
+ def self.start_socket_thread(port: nil, token: nil)
227
242
  require 'fastlane/server/socket_server'
228
243
  require 'fastlane/server/socket_server_action_command_executor'
229
244
 
230
245
  return Thread.new do
231
246
  command_executor = SocketServerActionCommandExecutor.new
232
- server = Fastlane::SocketServer.new(command_executor: command_executor, port: port)
247
+ server = Fastlane::SocketServer.new(command_executor: command_executor, port: port, token: token)
233
248
  server.start
234
249
  end
235
250
  end
@@ -245,6 +260,12 @@ module Fastlane
245
260
  UI.important("Your FastlaneRunner is outdated for this version of fastlane. Run a lane outside CI, without `disable_runner_upgrades`, to update it, then commit the changes.")
246
261
  end
247
262
 
263
+ # Runners from protocol version 2 send the run's token with every command; older ones get warn_if_runner_outdated.
264
+ # Version 1 was released before the token existed.
265
+ def self.runner_sends_token?
266
+ runner_protocol_version >= 2
267
+ end
268
+
248
269
  def self.ensure_runner_built!
249
270
  UI.verbose("Checking for new user-provided tool configuration files")
250
271
  # if self.link_user_configs_to_project returns true, that means we need to rebuild the runner
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
- VERSION = '2.241.1'.freeze
2
+ VERSION = '2.241.2'.freeze
3
3
  SUMMARY = "The easiest way to build and release mobile apps.".freeze
4
4
  DESCRIPTION = "The easiest way to automate beta deployments and releases for your iOS and Android apps".freeze
5
5
  MINIMUM_XCODE_RELEASE = "7.0".freeze
@@ -11,6 +11,10 @@
11
11
  import Foundation
12
12
 
13
13
  struct ArgumentProcessor {
14
+ /// The secret fastlane wrote to a file only this user can read: sent with every command so that
15
+ /// fastlane can tell this runner from another process that connected to its port
16
+ static var serverToken: String?
17
+
14
18
  let args: [RunnerArgument]
15
19
  let currentLane: String
16
20
  let commandTimeout: Int
@@ -33,6 +37,10 @@ struct ArgumentProcessor {
33
37
 
34
38
  port = UInt32(fastlaneArgsMinusLanes.first(where: { $0.name == "swiftServerPort" })?.value ?? "") ?? 2000
35
39
 
40
+ if let tokenFile = fastlaneArgsMinusLanes.first(where: { $0.name == "swiftServerTokenFile" })?.value {
41
+ ArgumentProcessor.serverToken = (try? String(contentsOfFile: tokenFile, encoding: .utf8))?.trimmingCharacters(in: .whitespacesAndNewlines)
42
+ }
43
+
36
44
  // Configure logMode since we might need to use it before we finish parsing
37
45
  if let logModeArg = potentialLogMode.first {
38
46
  let logModeString = logModeArg.value
@@ -74,7 +82,7 @@ struct ArgumentProcessor {
74
82
  func laneParameters() -> [String: String] {
75
83
  let laneParametersArgs = args.filter { arg in
76
84
  let lowercasedName = arg.name.lowercased()
77
- return lowercasedName != "timeoutseconds" && lowercasedName != "lane" && lowercasedName != "logmode"
85
+ return lowercasedName != "timeoutseconds" && lowercasedName != "lane" && lowercasedName != "logmode" && lowercasedName != "swiftservertokenfile"
78
86
  }
79
87
  var laneParameters = [String: String]()
80
88
  for arg in laneParametersArgs {
@@ -86,4 +94,4 @@ struct ArgumentProcessor {
86
94
 
87
95
  // Please don't remove the lines below
88
96
  // They are used to detect outdated files
89
- // FastlaneRunnerAPIVersion [0.9.2]
97
+ // FastlaneRunnerAPIVersion [0.9.3]
@@ -17,4 +17,4 @@ public class Deliverfile: DeliverfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.241.1
20
+ // Generated with fastlane 2.241.2
@@ -539,4 +539,4 @@ public extension DeliverfileProtocol {
539
539
 
540
540
  // Please don't remove the lines below
541
541
  // They are used to detect outdated files
542
- // FastlaneRunnerAPIVersion [0.9.157]
542
+ // FastlaneRunnerAPIVersion [0.9.158]
@@ -12605,7 +12605,7 @@ public func updateProjectProvisioning(xcodeproj: OptionalConfigValue<String?> =
12605
12605
  targetFilter: OptionalConfigValue<String?> = .fastlaneDefault(nil),
12606
12606
  buildConfigurationFilter: OptionalConfigValue<String?> = .fastlaneDefault(nil),
12607
12607
  buildConfiguration: OptionalConfigValue<String?> = .fastlaneDefault(nil),
12608
- certificate: String = "/var/folders/36/tjdph2t965j8snz9_vkdnw0r0000gn/T/AppleIncRootCertificate.cer",
12608
+ certificate: String = "/var/folders/n6/hn37q6pj7sj_9dmqqc2113800000gn/T/AppleIncRootCertificate.cer",
12609
12609
  codeSigningIdentity: OptionalConfigValue<String?> = .fastlaneDefault(nil))
12610
12610
  {
12611
12611
  let xcodeprojArg = xcodeproj.asRubyArgument(name: "xcodeproj", type: nil)
@@ -14245,4 +14245,4 @@ public let snapshotfile: Snapshotfile = .init()
14245
14245
 
14246
14246
  // Please don't remove the lines below
14247
14247
  // They are used to detect outdated files
14248
- // FastlaneRunnerAPIVersion [0.9.210]
14248
+ // FastlaneRunnerAPIVersion [0.9.211]
@@ -17,4 +17,4 @@ public class Gymfile: GymfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.241.1
20
+ // Generated with fastlane 2.241.2
@@ -399,4 +399,4 @@ public extension GymfileProtocol {
399
399
 
400
400
  // Please don't remove the lines below
401
401
  // They are used to detect outdated files
402
- // FastlaneRunnerAPIVersion [0.9.160]
402
+ // FastlaneRunnerAPIVersion [0.9.161]
@@ -42,12 +42,21 @@ class MainProcess {
42
42
  killExistingSocketServerProcesses(port: argumentProcessor.port)
43
43
  abortIfPortIsInUse(argumentProcessor.port)
44
44
 
45
+ let serverToken = createServerToken()
46
+ ArgumentProcessor.serverToken = serverToken.token
47
+
45
48
  rubySocketCommand = Process()
46
49
  rubySocketCommand.launchPath = "/usr/bin/env"
47
50
  rubySocketCommand.arguments = fastlaneLaunchArguments() + ["socket_server", "-c", String(timeout), "-p", String(argumentProcessor.port)]
51
+ // An environment variable rather than --token_file, so that an older fastlane still starts
52
+ var environment = ProcessInfo.processInfo.environment
53
+ environment["FASTLANE_SOCKET_SERVER_TOKEN_FILE"] = serverToken.file
54
+ rubySocketCommand.environment = environment
48
55
  rubySocketCommand.launch()
49
56
 
50
57
  waitUntilSocketServerIsListening(port: argumentProcessor.port, serverProcess: rubySocketCommand)
58
+ // socket_server read the token when it started
59
+ try? FileManager.default.removeItem(atPath: serverToken.directory)
51
60
  thread.start()
52
61
  #endif
53
62
  }
@@ -78,6 +87,24 @@ class MainProcess {
78
87
  return ["bundle", "exec", "fastlane"]
79
88
  }
80
89
 
90
+ /// A random token in a file only this user can read, which socket_server requires on every command and the runner sends
91
+ func createServerToken() -> (token: String, directory: String, file: String) {
92
+ var template = Array((NSTemporaryDirectory() as NSString).appendingPathComponent("fastlane-runner.XXXXXX").utf8CString)
93
+ guard let created = mkdtemp(&template), let random = FileHandle(forReadingAtPath: "/dev/urandom") else {
94
+ log(message: "Could not create the token for fastlane socket_server")
95
+ exit(1)
96
+ }
97
+ let directory = String(cString: created)
98
+ let token = random.readData(ofLength: 32).map { String(format: "%02x", $0) }.joined()
99
+ random.closeFile()
100
+ let file = directory + "/token"
101
+ guard token.count == 64, FileManager.default.createFile(atPath: file, contents: token.data(using: .utf8), attributes: [.posixPermissions: 0o600]) else {
102
+ log(message: "Could not create the token for fastlane socket_server")
103
+ exit(1)
104
+ }
105
+ return (token, directory, file)
106
+ }
107
+
81
108
  /// The directory of the Gemfile Bundler would use: BUNDLE_GEMFILE, else the
82
109
  /// nearest Gemfile or gems.rb from the working directory up.
83
110
  func bundleRootDirectory() -> String? {
@@ -17,4 +17,4 @@ public class Matchfile: MatchfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.241.1
20
+ // Generated with fastlane 2.241.2
@@ -420,4 +420,4 @@ public extension MatchfileProtocol {
420
420
 
421
421
  // Please don't remove the lines below
422
422
  // They are used to detect outdated files
423
- // FastlaneRunnerAPIVersion [0.9.154]
423
+ // FastlaneRunnerAPIVersion [0.9.155]
@@ -17,4 +17,4 @@ public class Precheckfile: PrecheckfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.241.1
20
+ // Generated with fastlane 2.241.2
@@ -84,4 +84,4 @@ public extension PrecheckfileProtocol {
84
84
 
85
85
  // Please don't remove the lines below
86
86
  // They are used to detect outdated files
87
- // FastlaneRunnerAPIVersion [0.9.153]
87
+ // FastlaneRunnerAPIVersion [0.9.154]
@@ -81,15 +81,7 @@ struct RubyCommand: RubyCommandable {
81
81
 
82
82
  var closure: ((String) -> Void)? {
83
83
  let callbacks = args.filter { ($0.type != nil) && $0.type == .stringClosure }
84
- guard let callback = callbacks.first else {
85
- return nil
86
- }
87
-
88
- guard let callbackArgValue = callback.value else {
89
- return nil
90
- }
91
-
92
- guard let callbackClosure = callbackArgValue as? ((String) -> Void) else {
84
+ guard let callback = callbacks.first, let callbackArgValue = callback.value, let callbackClosure = callbackArgValue as? ((String) -> Void) else {
93
85
  return nil
94
86
  }
95
87
  return callbackClosure
@@ -32,12 +32,13 @@ protocol RubyCommandable {
32
32
 
33
33
  extension RubyCommandable {
34
34
  var json: String {
35
+ let token = ArgumentProcessor.serverToken.map { ", \"token\": \"\($0)\"" } ?? ""
35
36
  return """
36
- { "commandType": "\(type.token)", "command": \(commandJson) }
37
+ { "commandType": "\(type.token)", "command": \(commandJson)\(token) }
37
38
  """
38
39
  }
39
40
  }
40
41
 
41
42
  // Please don't remove the lines below
42
43
  // They are used to detect outdated files
43
- // FastlaneRunnerAPIVersion [0.9.2]
44
+ // FastlaneRunnerAPIVersion [0.9.3]
@@ -17,4 +17,4 @@ public class Scanfile: ScanfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.241.1
20
+ // Generated with fastlane 2.241.2
@@ -595,4 +595,4 @@ public extension ScanfileProtocol {
595
595
 
596
596
  // Please don't remove the lines below
597
597
  // They are used to detect outdated files
598
- // FastlaneRunnerAPIVersion [0.9.165]
598
+ // FastlaneRunnerAPIVersion [0.9.166]
@@ -17,4 +17,4 @@ public class Screengrabfile: ScreengrabfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.241.1
20
+ // Generated with fastlane 2.241.2
@@ -161,4 +161,4 @@ public extension ScreengrabfileProtocol {
161
161
 
162
162
  // Please don't remove the lines below
163
163
  // They are used to detect outdated files
164
- // FastlaneRunnerAPIVersion [0.9.155]
164
+ // FastlaneRunnerAPIVersion [0.9.156]
@@ -17,4 +17,4 @@ public class Snapshotfile: SnapshotfileProtocol {
17
17
  // during the `init` process, and you won't see this message
18
18
  }
19
19
 
20
- // Generated with fastlane 2.241.1
20
+ // Generated with fastlane 2.241.2
@@ -385,4 +385,4 @@ public extension SnapshotfileProtocol {
385
385
 
386
386
  // Please don't remove the lines below
387
387
  // They are used to detect outdated files
388
- // FastlaneRunnerAPIVersion [0.9.149]
388
+ // FastlaneRunnerAPIVersion [0.9.150]
@@ -12,7 +12,7 @@ import Foundation
12
12
 
13
13
  let argumentProcessor = ArgumentProcessor(args: CommandLine.arguments)
14
14
  /// fastlane reads this from the built runner, to tell whether it is outdated. Increase it when the runner changes how it talks to fastlane.
15
- let runnerProtocolVersion = "FastlaneRunnerProtocolVersion [1]"
15
+ let runnerProtocolVersion = "FastlaneRunnerProtocolVersion [2]"
16
16
  verbose(message: runnerProtocolVersion)
17
17
  let timeout = argumentProcessor.commandTimeout
18
18
 
@@ -58,30 +58,44 @@ module FastlaneCore
58
58
  # Runs preparations before executing any command from the executor.
59
59
  #
60
60
  # @param original_api_key [Hash] api key containing the issuer id and private key
61
- # @return [Hash] copy of `api_key` which includes an extra `key_dir` with the location of the .p8 file on disk
61
+ # @return [Hash] copy of `api_key` which includes an extra `key_dir` with the location of the .p8 file on disk,
62
+ # and `cleanup_path`: what #cleanup removes afterwards
62
63
  def prepare(original_api_key:)
63
64
  return if original_api_key.nil?
64
65
  # Create .p8 file from api_key and provide api key info which contains .p8 file path
65
66
  api_key = original_api_key.dup
67
+ p8_name = "AuthKey_#{api_key[:key_id]}.p8"
66
68
  if self.kind_of?(ShellScriptTransporterExecutor)
67
69
  # as of Transporter v3.3.0, the app is unable to detect the private keys under the 'private_keys' folder in current directory
68
70
  # so we must rely on the other search paths in the Home dir:
69
71
  # https://help.apple.com/itc/transporteruserguide/en.lproj/static.html#itc803b7be80
70
72
  private_keys_dir = File.join(Dir.home, ".appstoreconnect/private_keys")
71
73
  unless Dir.exist?(private_keys_dir)
72
- FileUtils.mkdir_p(private_keys_dir)
74
+ FileUtils.mkdir_p(private_keys_dir, mode: 0o700)
73
75
  end
74
76
  api_key[:key_dir] = private_keys_dir
77
+ # Keys already there belong to the user: only remove the one written below
78
+ p8_path = File.join(private_keys_dir, p8_name)
79
+ api_key[:cleanup_path] = p8_path unless File.exist?(p8_path)
75
80
  else
76
81
  api_key[:key_dir] = Dir.mktmpdir("deliver-")
82
+ api_key[:cleanup_path] = api_key[:key_dir]
77
83
  end
78
84
  # Specified p8 needs to be generated to call altool or iTMSTransporter
79
- File.open(File.join(api_key[:key_dir], "AuthKey_#{api_key[:key_id]}.p8"), "wb") do |p8|
85
+ p8_path = File.join(api_key[:key_dir], p8_name)
86
+ File.open(p8_path, "wb", 0o600) do |p8|
80
87
  p8.write(api_key[:key])
81
88
  end
89
+ File.chmod(0o600, p8_path)
82
90
  api_key
83
91
  end
84
92
 
93
+ # Removes what #prepare wrote: its temporary directory, or the key file it added to ~/.appstoreconnect/private_keys
94
+ def cleanup(api_key)
95
+ return if api_key.nil? || api_key[:cleanup_path].nil?
96
+ FileUtils.rm_rf(api_key[:cleanup_path])
97
+ end
98
+
85
99
  def execute(command, hide_output)
86
100
  if Helper.test?
87
101
  yield(nil) if block_given?
@@ -869,7 +883,7 @@ module FastlaneCore
869
883
  return upload(app_id, dir, package_path: package_path, asset_path: asset_path)
870
884
  ensure
871
885
  if use_api_key
872
- FileUtils.rm_rf(api_key[:key_dir]) unless api_key.nil?
886
+ @transporter_executor.cleanup(api_key)
873
887
  end
874
888
  end
875
889
 
@@ -967,7 +981,7 @@ module FastlaneCore
967
981
  return provider_ids
968
982
  ensure
969
983
  if use_api_key
970
- FileUtils.rm_rf(api_key[:key_dir]) unless api_key.nil?
984
+ @transporter_executor.cleanup(api_key)
971
985
  end
972
986
  end
973
987
 
@@ -387,6 +387,8 @@ module Match
387
387
 
388
388
  if Helper.mac?
389
389
  installed_profile = FastlaneCore::ProvisioningProfile.install(stored_profile_path, keychain_path)
390
+ else
391
+ UI.message("Skipping installation of provisioning profile as it would not work on this operating system.")
390
392
  end
391
393
 
392
394
  if params[:output_path]
@@ -2,6 +2,7 @@ require 'open-uri'
2
2
  require 'ostruct'
3
3
 
4
4
  require_relative '../../module'
5
+ require_relative '../interface'
5
6
 
6
7
  module Match
7
8
  module Storage
@@ -36,7 +37,7 @@ module Match
36
37
  url = URI("#{file_url}/download")
37
38
 
38
39
  begin
39
- destination_file = "#{working_directory}/#{@file.name}"
40
+ destination_file = Storage.download_path(working_directory, @file.name)
40
41
 
41
42
  create_subfolders(working_directory)
42
43
  File.open(destination_file, "wb") do |saved_file|
@@ -158,7 +158,7 @@ module Match
158
158
 
159
159
  bucket.files.each do |current_file|
160
160
  file_path = current_file.name # e.g. "N8X438SEU2/certs/distribution/XD9G7QCACF.cer"
161
- download_path = File.join(self.working_directory, file_path)
161
+ download_path = Storage.download_path(self.working_directory, file_path)
162
162
 
163
163
  FileUtils.mkdir_p(File.expand_path("..", download_path))
164
164
  UI.verbose("Downloading file from Google Cloud Storage '#{file_path}' on bucket #{self.bucket_name}")
@@ -2,6 +2,16 @@ require_relative '../module'
2
2
 
3
3
  module Match
4
4
  module Storage
5
+ # Where a file named `remote_name` by the storage is written in `working_directory`.
6
+ # Fails when the name would place it outside, e.g. with `..` segments.
7
+ def self.download_path(working_directory, remote_name)
8
+ root = File.expand_path(working_directory)
9
+ path = File.expand_path(File.join(root, remote_name))
10
+ return path if path.start_with?(root + File::SEPARATOR)
11
+
12
+ UI.user_error!("Refusing to download #{remote_name.inspect}: it would be written outside #{root}")
13
+ end
14
+
5
15
  class Interface
6
16
  MATCH_VERSION_FILE_NAME = "match_version.txt"
7
17
  # The working directory in which we download all the profiles
@@ -114,7 +114,7 @@ module Match
114
114
  file_path = strip_s3_object_prefix(object.key) # :s3_object_prefix:team_id/path/to/file
115
115
 
116
116
  # strip s3_prefix from file_path
117
- download_path = File.join(self.working_directory, file_path)
117
+ download_path = Storage.download_path(self.working_directory, file_path)
118
118
 
119
119
  FileUtils.mkdir_p(File.expand_path("..", download_path))
120
120
  UI.verbose("Downloading file from S3 '#{file_path}' on bucket #{self.s3_bucket}")
@@ -41,7 +41,11 @@ module Sigh
41
41
  ENV["SIGH_UDID"] = ENV["SIGH_UUID"] = uuid if uuid
42
42
  ENV["SIGH_NAME"] = name if name
43
43
 
44
- FastlaneCore::ProvisioningProfile.install(profile, keychain_path)
44
+ if Helper.mac?
45
+ FastlaneCore::ProvisioningProfile.install(profile, keychain_path)
46
+ else
47
+ UI.message("Skipping installation of provisioning profile as it would not work on this operating system.")
48
+ end
45
49
  end
46
50
  end
47
51
  end
@@ -252,6 +252,8 @@ module Spaceship
252
252
  else
253
253
  # Log to file by default
254
254
  path = File.join(Dir.tmpdir, "spaceship#{Time.now.to_i}_#{Process.pid}_#{Thread.current.object_id}.log")
255
+ # The log holds every request and response body: create it readable by its owner only
256
+ File.open(path, File::WRONLY | File::CREAT | File::APPEND, 0o600) {}
255
257
  @logger = Logger.new(path)
256
258
  end
257
259
 
@@ -279,10 +281,12 @@ module Spaceship
279
281
  def store_cookie(path: nil)
280
282
  path ||= persistent_cookie_path
281
283
  FileUtils.mkdir_p(File.expand_path("..", path))
284
+ File.chmod(0o700, File.expand_path("..", path))
282
285
 
283
286
  # really important to specify the session to true
284
287
  # otherwise myacinfo and more won't be stored
285
288
  @cookie.save(path, :yaml, session: true)
289
+ File.chmod(0o600, path)
286
290
  return File.read(path)
287
291
  end
288
292
 
@@ -880,6 +884,8 @@ module Spaceship
880
884
  begin
881
885
  if File.exist?(persistent_cookie_path)
882
886
  puts("Loading session from '#{persistent_cookie_path}'") if Spaceship::Globals.verbose?
887
+ File.chmod(0o700, File.expand_path("..", persistent_cookie_path))
888
+ File.chmod(0o600, persistent_cookie_path)
883
889
  @cookie.load(persistent_cookie_path)
884
890
  return true
885
891
  end
@@ -1144,7 +1150,7 @@ module Spaceship
1144
1150
 
1145
1151
  def log_response(method, url, response, headers = nil, &block)
1146
1152
  url ||= extract_key_from_block('url', &block)
1147
- body = response.body.kind_of?(String) ? response.body.force_encoding(Encoding::UTF_8) : response.body
1153
+ body = response.body.kind_of?(String) ? response.body.dup.force_encoding(Encoding::UTF_8) : response.body
1148
1154
  logger.debug("<< #{method.upcase} #{url}: #{response.status} #{body}")
1149
1155
  end
1150
1156
 
metadata CHANGED
@@ -1,36 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.241.1
4
+ version: 2.241.2
5
5
  platform: ruby
6
6
  authors:
7
- - Felix Krause
8
- - Jimmy Dee
9
- - Connor Tumbleson
10
- - Manish Rathi
11
- - Daniel Jankowski
12
- - Kohki Miki
13
7
  - Josh Holtz
14
- - Manu Wallner
15
- - Stefan Natchev
16
- - Olivier Halligon
8
+ - Jimmy Dee
9
+ - Joshua Liebowitz
17
10
  - Max Ott
11
+ - Olivier Halligon
18
12
  - Helmut Januschka
19
- - Luka Mirosevic
20
13
  - Satoshi Namai
21
- - Roger Oba
22
14
  - Danielle Tomlinson
23
- - Andrew McBurney
15
+ - Manish Rathi
24
16
  - Iulian Onofrei
25
- - Aaron Brager
26
- - Jan Piotrowski
27
- - Jérôme Lacoste
17
+ - Kohki Miki
28
18
  - Jorge Revuelta H
19
+ - Andrew McBurney
20
+ - Connor Tumbleson
21
+ - Jan Piotrowski
29
22
  - Maksym Grebenets
30
- - Joshua Liebowitz
31
- - Fumiya Nakamura
23
+ - Manu Wallner
24
+ - Stefan Natchev
32
25
  - Łukasz Grabowski
33
26
  - Matthew Ellis
27
+ - Roger Oba
28
+ - Luka Mirosevic
29
+ - Aaron Brager
30
+ - Fumiya Nakamura
31
+ - Jérôme Lacoste
32
+ - Daniel Jankowski
33
+ - Felix Krause
34
34
  bindir: bin
35
35
  cert_chain: []
36
36
  date: 1980-01-02 00:00:00.000000000 Z