adsedare 0.0.1 → 0.0.3
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.
- checksums.yaml +4 -4
- data/adsedare.gemspec +1 -1
- data/lib/adsedare/capabilities.rb +43 -38
- data/lib/adsedare/export_options.rb +93 -0
- data/lib/adsedare/install_profiles.rb +76 -0
- data/lib/adsedare/keychain.rb +131 -0
- data/lib/adsedare/version.rb +1 -1
- data/lib/adsedare/xcodeproj.rb +76 -0
- data/lib/adsedare.rb +17 -348
- data/lib/appstoreconnect.rb +65 -65
- data/lib/starship/2fa_provider.rb +4 -4
- data/lib/starship/auth_helper.rb +87 -88
- data/lib/starship.rb +60 -60
- metadata +5 -1
data/lib/adsedare.rb
CHANGED
@@ -8,8 +8,12 @@ require "plist"
|
|
8
8
|
|
9
9
|
require_relative "adsedare/version"
|
10
10
|
require_relative "adsedare/capabilities"
|
11
|
-
require_relative "
|
11
|
+
require_relative "adsedare/keychain"
|
12
|
+
require_relative "adsedare/xcodeproj"
|
13
|
+
require_relative "adsedare/export_options"
|
14
|
+
require_relative "adsedare/install_profiles"
|
12
15
|
|
16
|
+
require_relative "logging"
|
13
17
|
require_relative "starship"
|
14
18
|
require_relative "appstoreconnect"
|
15
19
|
|
@@ -22,20 +26,20 @@ module Adsedare
|
|
22
26
|
def renew_profiles(project_path = nil, certificate_id = nil, team_id = nil)
|
23
27
|
raise "Project path is not set" unless project_path
|
24
28
|
raise "Certificate ID is not set" unless certificate_id
|
25
|
-
|
29
|
+
|
26
30
|
project = Xcodeproj::Project.open(project_path)
|
27
31
|
project_dir = File.dirname(project_path)
|
28
|
-
|
32
|
+
|
29
33
|
bundle_entitlements = {}
|
30
34
|
|
31
35
|
project.targets.each do |target|
|
32
36
|
target.build_configurations.each do |config|
|
33
37
|
bundle_identifier = config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
|
34
38
|
entitlements_path = config.build_settings["CODE_SIGN_ENTITLEMENTS"]
|
35
|
-
|
39
|
+
|
36
40
|
# If team_id is not set, use the first one from the project
|
37
41
|
team_id ||= config.build_settings["DEVELOPMENT_TEAM"]
|
38
|
-
|
42
|
+
|
39
43
|
if entitlements_path
|
40
44
|
full_entitlements_path = File.join(project_dir, entitlements_path)
|
41
45
|
bundle_entitlements[bundle_identifier] = full_entitlements_path
|
@@ -51,10 +55,10 @@ module Adsedare
|
|
51
55
|
unless bundle_id
|
52
56
|
logger.warn "Bundle '#{bundle_identifier}' is missing in Apple Developer portal. Will create."
|
53
57
|
bundle_id = Starship::Client.create_bundle(
|
54
|
-
bundle_identifier,
|
58
|
+
bundle_identifier,
|
55
59
|
team_id,
|
56
60
|
# You cannot create bundle without this capability
|
57
|
-
[
|
61
|
+
[SimpleCapability.new("IN_APP_PURCHASE").to_bundle_capability(nil, nil)]
|
58
62
|
)["data"]["id"]
|
59
63
|
bundle_by_identifier[bundle_identifier] = bundle_id
|
60
64
|
logger.info "Bundle '#{bundle_identifier}' created with ID '#{bundle_id}'"
|
@@ -86,340 +90,6 @@ module Adsedare
|
|
86
90
|
end
|
87
91
|
end
|
88
92
|
|
89
|
-
def install_profiles(project_path = nil)
|
90
|
-
raise "Project path is not set" unless project_path
|
91
|
-
|
92
|
-
project = Xcodeproj::Project.open(project_path)
|
93
|
-
|
94
|
-
project_bundles = project.targets.map do |target|
|
95
|
-
target.build_configurations.map do |config|
|
96
|
-
config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
|
97
|
-
end
|
98
|
-
end.flatten.uniq
|
99
|
-
|
100
|
-
bundles_with_profiles = AppStoreConnect::Client.get_bundles_with_profiles(project_bundles)
|
101
|
-
bundle_by_identifier = {}
|
102
|
-
profiles_by_id = {}
|
103
|
-
|
104
|
-
bundles_with_profiles["data"].each do |bundle_id|
|
105
|
-
bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
|
106
|
-
end
|
107
|
-
|
108
|
-
bundles_with_profiles["included"].each do |profile|
|
109
|
-
profiles_by_id[profile["id"]] = profile
|
110
|
-
end
|
111
|
-
|
112
|
-
project_bundles.each do |bundle_identifier|
|
113
|
-
bundle_id = bundle_by_identifier[bundle_identifier]
|
114
|
-
unless bundle_id
|
115
|
-
logger.warn "Bundle '#{bundle_identifier}' is missing in App Store Connect. Skipping."
|
116
|
-
next
|
117
|
-
end
|
118
|
-
|
119
|
-
logger.info "Bundle '#{bundle_identifier}' resolved to Bundle ID '#{bundle_id['id']}'"
|
120
|
-
|
121
|
-
profiles = bundle_id["relationships"]["profiles"]["data"]
|
122
|
-
unless profiles
|
123
|
-
logger.warn "Profile for Bundle ID '#{bundle_id['id']}' is missing in App Store Connect. Skipping."
|
124
|
-
next
|
125
|
-
end
|
126
|
-
|
127
|
-
ad_hoc_profile = nil
|
128
|
-
profiles.each do |profile|
|
129
|
-
profile_id = profile["id"]
|
130
|
-
profile = profiles_by_id[profile_id]
|
131
|
-
|
132
|
-
if profile["attributes"]["profileType"] == "IOS_APP_ADHOC" && profile["attributes"]["profileState"] == "ACTIVE"
|
133
|
-
ad_hoc_profile = profile
|
134
|
-
break
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
unless ad_hoc_profile
|
139
|
-
logger.warn "Profile for Bundle ID '#{bundle_id['id']}' is missing in App Store Connect. Skipping."
|
140
|
-
next
|
141
|
-
end
|
142
|
-
|
143
|
-
logger.info "Profile for Bundle ID '#{bundle_id['id']}' resolved to Profile '#{ad_hoc_profile['attributes']['name']}'"
|
144
|
-
|
145
|
-
uuid = ad_hoc_profile["attributes"]["uuid"]
|
146
|
-
profile_content = Base64.decode64(ad_hoc_profile["attributes"]["profileContent"])
|
147
|
-
profile_path = "#{Dir.home}/Library/MobileDevice/Provisioning Profiles/#{uuid}.mobileprovision"
|
148
|
-
|
149
|
-
FileUtils.mkdir_p(File.dirname(profile_path))
|
150
|
-
File.write(profile_path, profile_content)
|
151
|
-
|
152
|
-
logger.info "Profile '#{ad_hoc_profile['attributes']['name']}' installed to '#{profile_path}'"
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def create_keychain(keychain_path = nil, keychain_password = nil, make_default = true)
|
157
|
-
raise "Keychain path is not set" unless keychain_path
|
158
|
-
raise "Keychain password is not set" unless keychain_password
|
159
|
-
|
160
|
-
keychain_path = File.expand_path(keychain_path)
|
161
|
-
|
162
|
-
logger.info "Creating keychain at '#{keychain_path}'"
|
163
|
-
|
164
|
-
FileUtils.mkdir_p(File.dirname(keychain_path))
|
165
|
-
status = system("security create-keychain -p #{keychain_password} #{keychain_path}")
|
166
|
-
unless status
|
167
|
-
logger.error "Failed to create keychain at '#{keychain_path}'"
|
168
|
-
return
|
169
|
-
end
|
170
|
-
|
171
|
-
apple_certs = [
|
172
|
-
"AppleWWDRCAG2.cer",
|
173
|
-
"AppleWWDRCAG3.cer",
|
174
|
-
"AppleWWDRCAG4.cer",
|
175
|
-
"AppleWWDRCAG5.cer",
|
176
|
-
"AppleWWDRCAG6.cer",
|
177
|
-
"AppleWWDRCAG7.cer",
|
178
|
-
"AppleWWDRCAG8.cer",
|
179
|
-
"DeveloperIDG2CA.cer"
|
180
|
-
]
|
181
|
-
|
182
|
-
apple_certs.each do |cert|
|
183
|
-
logger.info "Downloading certificate '#{cert}'"
|
184
|
-
|
185
|
-
response = Faraday.get(
|
186
|
-
"https://www.apple.com/certificateauthority/#{cert}"
|
187
|
-
)
|
188
|
-
unless response.status == 200
|
189
|
-
logger.error "Failed to download certificate '#{cert}'"
|
190
|
-
next
|
191
|
-
end
|
192
|
-
|
193
|
-
file = Tempfile.new(cert)
|
194
|
-
file.write(response.body)
|
195
|
-
file.close
|
196
|
-
|
197
|
-
install_certificate(file.path, keychain_path)
|
198
|
-
|
199
|
-
file.unlink
|
200
|
-
end
|
201
|
-
|
202
|
-
logger.info "Downloading certificate 'AppleWWDRCA.cer'"
|
203
|
-
response = Faraday.get(
|
204
|
-
"https://developer.apple.com/certificationauthority/AppleWWDRCA.cer"
|
205
|
-
)
|
206
|
-
unless response.status == 200
|
207
|
-
logger.error "Failed to download certificate 'AppleWWDRCA.cer'"
|
208
|
-
else
|
209
|
-
file = Tempfile.new("AppleWWDRCA.cer")
|
210
|
-
file.write(response.body)
|
211
|
-
file.close
|
212
|
-
|
213
|
-
install_certificate(file.path, keychain_path)
|
214
|
-
|
215
|
-
file.unlink
|
216
|
-
end
|
217
|
-
|
218
|
-
ad_hoc_certificate = ENV["AD_HOC_CERTIFICATE"]
|
219
|
-
ad_hoc_private_key = ENV["AD_HOC_PRIVATE_KEY"]
|
220
|
-
ad_hoc_key_password = ENV["AD_HOC_KEY_PASSWORD"]
|
221
|
-
|
222
|
-
unless ad_hoc_certificate || ad_hoc_private_key || ad_hoc_key_password
|
223
|
-
logger.warn "AD_HOC_CERTIFICATE, AD_HOC_PRIVATE_KEY, or AD_HOC_KEY_PASSWORD is not set"
|
224
|
-
return
|
225
|
-
end
|
226
|
-
|
227
|
-
install_certificate(ad_hoc_private_key, keychain_path, ad_hoc_key_password, "priv")
|
228
|
-
install_certificate(ad_hoc_certificate, keychain_path, "", "cert")
|
229
|
-
|
230
|
-
if make_default
|
231
|
-
status = system("security default-keychain -d user -s #{keychain_path}")
|
232
|
-
unless status
|
233
|
-
logger.warn "Failed to set default keychain"
|
234
|
-
return
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
status = system("security set-keychain-settings #{keychain_path}")
|
239
|
-
unless status
|
240
|
-
logger.error "Failed to set keychain settings"
|
241
|
-
return
|
242
|
-
end
|
243
|
-
|
244
|
-
status = system("security set-key-partition-list -S apple-tool:,apple: -k #{keychain_password} #{keychain_path}")
|
245
|
-
unless status
|
246
|
-
logger.error "Failed to set keychain partition list"
|
247
|
-
return
|
248
|
-
end
|
249
|
-
|
250
|
-
status = system("security unlock-keychain -p #{keychain_password} #{keychain_path}")
|
251
|
-
unless status
|
252
|
-
logger.error "Failed to unlock keychain"
|
253
|
-
return
|
254
|
-
end
|
255
|
-
|
256
|
-
logger.info "Keychain created at '#{keychain_path}'"
|
257
|
-
end
|
258
|
-
|
259
|
-
def make_export_options(project_path = nil, export_path = nil, team_id = nil, options = {})
|
260
|
-
raise "Project path is not set" unless project_path
|
261
|
-
raise "Export path is not set" unless export_path
|
262
|
-
|
263
|
-
project = Xcodeproj::Project.open(project_path)
|
264
|
-
export_options = {
|
265
|
-
"method" => "ad-hoc",
|
266
|
-
"destination" => "export",
|
267
|
-
"signingStyle" => "manual",
|
268
|
-
"provisioningProfiles" => {}
|
269
|
-
}.merge(options)
|
270
|
-
|
271
|
-
project_bundles = []
|
272
|
-
|
273
|
-
project.targets.each do |target|
|
274
|
-
target.build_configurations.each do |config|
|
275
|
-
team_id ||= config.build_settings["DEVELOPMENT_TEAM"]
|
276
|
-
project_bundles << config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
export_options["teamID"] = team_id
|
281
|
-
|
282
|
-
logger.info "Fetching bundles with profiles for team ID '#{team_id}'"
|
283
|
-
|
284
|
-
bundles_with_profiles = AppStoreConnect::Client.get_bundles_with_profiles(project_bundles)
|
285
|
-
bundle_by_identifier = {}
|
286
|
-
profiles_by_id = {}
|
287
|
-
|
288
|
-
bundles_with_profiles["data"].each do |bundle_id|
|
289
|
-
bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
|
290
|
-
end
|
291
|
-
|
292
|
-
bundles_with_profiles["included"].each do |profile|
|
293
|
-
profiles_by_id[profile["id"]] = profile
|
294
|
-
end
|
295
|
-
|
296
|
-
project_bundles.each do |bundle_identifier|
|
297
|
-
bundle_id = bundle_by_identifier[bundle_identifier]
|
298
|
-
unless bundle_id
|
299
|
-
logger.warn "Bundle '#{bundle_identifier}' is missing in App Store Connect. Skipping."
|
300
|
-
next
|
301
|
-
end
|
302
|
-
|
303
|
-
logger.info "Bundle '#{bundle_identifier}' resolved to Bundle ID '#{bundle_id['id']}'"
|
304
|
-
|
305
|
-
profiles = bundle_id["relationships"]["profiles"]["data"]
|
306
|
-
unless profiles
|
307
|
-
logger.warn "Profile for Bundle ID '#{bundle_id['id']}' is missing in App Store Connect. Skipping."
|
308
|
-
next
|
309
|
-
end
|
310
|
-
|
311
|
-
ad_hoc_profile = nil
|
312
|
-
profiles.each do |profile|
|
313
|
-
profile_id = profile["id"]
|
314
|
-
profile = profiles_by_id[profile_id]
|
315
|
-
|
316
|
-
if profile["attributes"]["profileType"] == "IOS_APP_ADHOC" && profile["attributes"]["profileState"] == "ACTIVE"
|
317
|
-
ad_hoc_profile = profile
|
318
|
-
break
|
319
|
-
end
|
320
|
-
end
|
321
|
-
|
322
|
-
unless ad_hoc_profile
|
323
|
-
logger.warn "Profile for Bundle ID '#{bundle_id['id']}' is missing in App Store Connect. Skipping."
|
324
|
-
next
|
325
|
-
end
|
326
|
-
|
327
|
-
logger.info "Profile for Bundle ID '#{bundle_id['id']}' resolved to Profile '#{ad_hoc_profile['attributes']['name']}'"
|
328
|
-
|
329
|
-
profile_name = ad_hoc_profile["attributes"]["name"]
|
330
|
-
|
331
|
-
export_options["provisioningProfiles"][bundle_identifier] = profile_name
|
332
|
-
end
|
333
|
-
|
334
|
-
options_plist = Plist::Emit.dump(export_options)
|
335
|
-
File.write(export_path, options_plist)
|
336
|
-
|
337
|
-
return export_options
|
338
|
-
end
|
339
|
-
|
340
|
-
def patch_project(project_path, team_id = nil)
|
341
|
-
raise "Project path is not set" unless project_path
|
342
|
-
|
343
|
-
project = Xcodeproj::Project.open(project_path)
|
344
|
-
|
345
|
-
project_bundles = project.targets.map do |target|
|
346
|
-
target.build_configurations.map do |config|
|
347
|
-
config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
|
348
|
-
end
|
349
|
-
end.flatten.uniq
|
350
|
-
|
351
|
-
bundles_with_profiles = AppStoreConnect::Client.get_bundles_with_profiles(project_bundles)
|
352
|
-
bundle_by_identifier = {}
|
353
|
-
profiles_by_id = {}
|
354
|
-
|
355
|
-
bundles_with_profiles["data"].each do |bundle_id|
|
356
|
-
bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
|
357
|
-
end
|
358
|
-
|
359
|
-
bundles_with_profiles["included"].each do |profile|
|
360
|
-
profiles_by_id[profile["id"]] = profile
|
361
|
-
end
|
362
|
-
|
363
|
-
project.targets.each do |target|
|
364
|
-
target.build_configurations.each do |config|
|
365
|
-
bundle_identifier = config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
|
366
|
-
bundle_id = bundle_by_identifier[bundle_identifier]
|
367
|
-
unless bundle_id
|
368
|
-
logger.warn "Bundle '#{bundle_identifier}' is missing in App Store Connect. Skipping."
|
369
|
-
next
|
370
|
-
end
|
371
|
-
|
372
|
-
logger.info "Bundle '#{bundle_identifier}' resolved to Bundle ID '#{bundle_id['id']}'"
|
373
|
-
|
374
|
-
profiles = bundle_id["relationships"]["profiles"]["data"]
|
375
|
-
unless profiles
|
376
|
-
logger.warn "Profile for Bundle ID '#{bundle_id['id']}' is missing in App Store Connect. Skipping."
|
377
|
-
next
|
378
|
-
end
|
379
|
-
|
380
|
-
ad_hoc_profile = nil
|
381
|
-
profiles.each do |profile|
|
382
|
-
profile_id = profile["id"]
|
383
|
-
profile = profiles_by_id[profile_id]
|
384
|
-
|
385
|
-
if profile["attributes"]["profileType"] == "IOS_APP_ADHOC" && profile["attributes"]["profileState"] == "ACTIVE"
|
386
|
-
ad_hoc_profile = profile
|
387
|
-
break
|
388
|
-
end
|
389
|
-
end
|
390
|
-
|
391
|
-
unless ad_hoc_profile
|
392
|
-
logger.warn "Profile for Bundle ID '#{bundle_id['id']}' is missing in App Store Connect. Skipping."
|
393
|
-
next
|
394
|
-
end
|
395
|
-
|
396
|
-
config.build_settings["CODE_SIGN_IDENTITY"] = "iPhone Distribution"
|
397
|
-
config.build_settings["CODE_SIGN_STYLE"] = "Manual"
|
398
|
-
if team_id
|
399
|
-
config.build_settings["DEVELOPMENT_TEAM"] = team_id
|
400
|
-
end
|
401
|
-
config.build_settings["PROVISIONING_PROFILE_SPECIFIER"] = ad_hoc_profile["attributes"]["name"]
|
402
|
-
end
|
403
|
-
end
|
404
|
-
|
405
|
-
project.save
|
406
|
-
end
|
407
|
-
|
408
|
-
private
|
409
|
-
|
410
|
-
def install_certificate(certificate_path, keychain_path, certificate_password = "", certificate_type = "cert")
|
411
|
-
certificate_name = File.basename(certificate_path)
|
412
|
-
logger.info "Installing certificate '#{certificate_name}' to keychain '#{keychain_path}'"
|
413
|
-
|
414
|
-
status = system("security import #{certificate_path} -k #{keychain_path} -t #{certificate_type} -A -P #{certificate_password} -T /usr/bin/codesign -T /usr/bin/security -T /usr/bin/productbuild")
|
415
|
-
unless status
|
416
|
-
logger.error "Failed to install certificate '#{certificate_name}' to keychain '#{keychain_path}'"
|
417
|
-
return
|
418
|
-
end
|
419
|
-
|
420
|
-
logger.info "Certificate '#{certificate_name}' installed to keychain '#{keychain_path}'"
|
421
|
-
end
|
422
|
-
|
423
93
|
def get_devices(team_id)
|
424
94
|
Starship::Client.get_devices(team_id)
|
425
95
|
end
|
@@ -474,7 +144,7 @@ module Adsedare
|
|
474
144
|
|
475
145
|
if need_update
|
476
146
|
logger.warn "Profile '#{profile["provisioningProfile"]["name"]}' is missing one or more devices."
|
477
|
-
|
147
|
+
|
478
148
|
Starship::Client.regen_provisioning_profile(profile, team_id, deviceIds)
|
479
149
|
|
480
150
|
logger.info "Profile '#{profile["provisioningProfile"]["name"]}' updated."
|
@@ -490,7 +160,7 @@ module Adsedare
|
|
490
160
|
logger.info "Checking capabilities for bundle '#{bundle_identifier}'"
|
491
161
|
|
492
162
|
capabilities = parse_entitlements(entitlements_path)
|
493
|
-
|
163
|
+
|
494
164
|
need_update = false
|
495
165
|
|
496
166
|
capabilities.each do |capability|
|
@@ -502,11 +172,10 @@ module Adsedare
|
|
502
172
|
|
503
173
|
if need_update
|
504
174
|
logger.warn "Bundle '#{bundle_identifier}' is missing one or more capabilities."
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
175
|
+
# You can't remove IN_APP_PURCHASE capability for some reason
|
176
|
+
new_capabilities = capabilities + [SimpleCapability.new("IN_APP_PURCHASE")]
|
177
|
+
new_capabilities = new_capabilities.map { |capability| capability.to_bundle_capability(bundle_info, team_id) }
|
178
|
+
|
510
179
|
Starship::Client.patch_bundle(bundle_info, team_id, new_capabilities)
|
511
180
|
|
512
181
|
logger.info "Bundle '#{bundle_identifier}' capabilities updated."
|
data/lib/appstoreconnect.rb
CHANGED
@@ -7,80 +7,80 @@ require "jwt"
|
|
7
7
|
module AppStoreConnect
|
8
8
|
class Client
|
9
9
|
BASE_URL = "https://api.appstoreconnect.apple.com/v1"
|
10
|
-
|
10
|
+
|
11
11
|
class << self
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
if response.status == 200
|
25
|
-
JSON.parse(response.body)
|
26
|
-
else
|
27
|
-
puts response.body
|
28
|
-
raise "Failed to get bundle IDs: #{response.status}"
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
12
|
+
def get_bundles_with_profiles(bundle_identifiers)
|
13
|
+
response = request(
|
14
|
+
BASE_URL + "/bundleIds",
|
15
|
+
method: :get,
|
16
|
+
params: {
|
17
|
+
"fields[bundleIds]" => "name,platform,identifier,profiles",
|
18
|
+
"filter[identifier]" => bundle_identifiers.join(","),
|
19
|
+
"fields[profiles]" => "name,profileType,profileState,profileContent,uuid",
|
20
|
+
"include" => "profiles",
|
21
|
+
},
|
22
|
+
)
|
33
23
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
24
|
+
if response.status == 200
|
25
|
+
JSON.parse(response.body)
|
26
|
+
else
|
27
|
+
puts response.body
|
28
|
+
raise "Failed to get bundle IDs: #{response.status}"
|
29
|
+
end
|
30
|
+
end
|
40
31
|
|
41
|
-
|
42
|
-
default_headers = default_headers.merge(headers)
|
43
|
-
end
|
32
|
+
private
|
44
33
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
Faraday.put(endpoint, body, default_headers)
|
52
|
-
when :delete
|
53
|
-
Faraday.delete(endpoint, default_headers)
|
54
|
-
when :patch
|
55
|
-
Faraday.patch(endpoint, body, default_headers)
|
56
|
-
end
|
34
|
+
def request(endpoint, method: :get, params: nil, body: nil, headers: nil)
|
35
|
+
default_headers = {
|
36
|
+
"Authorization" => "Bearer #{jwt_token}",
|
37
|
+
"Content-Type" => "application/json",
|
38
|
+
"Accept" => "application/json",
|
39
|
+
}
|
57
40
|
|
58
|
-
|
41
|
+
if headers
|
42
|
+
default_headers = default_headers.merge(headers)
|
59
43
|
end
|
60
44
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
45
|
+
response = case method
|
46
|
+
when :get
|
47
|
+
Faraday.get(endpoint, params, default_headers)
|
48
|
+
when :post
|
49
|
+
Faraday.post(endpoint, body, default_headers)
|
50
|
+
when :put
|
51
|
+
Faraday.put(endpoint, body, default_headers)
|
52
|
+
when :delete
|
53
|
+
Faraday.delete(endpoint, default_headers)
|
54
|
+
when :patch
|
55
|
+
Faraday.patch(endpoint, body, default_headers)
|
56
|
+
end
|
65
57
|
|
66
|
-
|
67
|
-
|
68
|
-
{
|
69
|
-
iss: issuer_id,
|
70
|
-
exp: Time.now.to_i + 20 * 60,
|
71
|
-
aud: "appstoreconnect-v1",
|
72
|
-
},
|
73
|
-
private_key,
|
74
|
-
"ES256",
|
75
|
-
header_fields = {
|
76
|
-
alg: "ES256",
|
77
|
-
kid: key_id,
|
78
|
-
typ: "JWT"
|
79
|
-
}
|
80
|
-
)
|
58
|
+
return response
|
59
|
+
end
|
81
60
|
|
82
|
-
|
83
|
-
|
61
|
+
def jwt_token
|
62
|
+
key_id = ENV["APPSTORE_CONNECT_KEY_ID"]
|
63
|
+
key = ENV["APPSTORE_CONNECT_KEY"]
|
64
|
+
issuer_id = ENV["APPSTORE_CONNECT_ISSUER_ID"]
|
65
|
+
|
66
|
+
private_key = OpenSSL::PKey.read(key)
|
67
|
+
token = JWT.encode(
|
68
|
+
{
|
69
|
+
iss: issuer_id,
|
70
|
+
exp: Time.now.to_i + 20 * 60,
|
71
|
+
aud: "appstoreconnect-v1",
|
72
|
+
},
|
73
|
+
private_key,
|
74
|
+
"ES256",
|
75
|
+
header_fields = {
|
76
|
+
alg: "ES256",
|
77
|
+
kid: key_id,
|
78
|
+
typ: "JWT",
|
79
|
+
}
|
80
|
+
)
|
81
|
+
|
82
|
+
return token
|
83
|
+
end
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|
@@ -6,7 +6,7 @@ module Starship
|
|
6
6
|
class TwoFactorProvider
|
7
7
|
def initialize
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
# Get the 2FA code
|
11
11
|
# @param session_id [String] The session ID from Apple
|
12
12
|
# @param scnt [String] The scnt value from Apple
|
@@ -14,7 +14,7 @@ module Starship
|
|
14
14
|
def get_code(session_id, scnt)
|
15
15
|
raise NotImplementedError, "Subclasses must implement get_code"
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
# Verify if this provider can handle the given 2FA type
|
19
19
|
# @param type [String] The 2FA type (sms, voice, etc.)
|
20
20
|
# @return [Boolean] Whether this provider can handle the given type
|
@@ -22,7 +22,7 @@ module Starship
|
|
22
22
|
raise NotImplementedError, "Subclasses must implement can_handle?"
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
# Manual 2FA provider that prompts the user for a code
|
27
27
|
class ManualTwoFactorProvider < TwoFactorProvider
|
28
28
|
include Logging
|
@@ -32,7 +32,7 @@ module Starship
|
|
32
32
|
code = gets.chomp.strip
|
33
33
|
code
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
def can_handle?(type)
|
37
37
|
true
|
38
38
|
end
|