adsedare 0.0.6 → 0.0.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f6d62902a49a3f9ae322a91a6409eafe479b473251f1fbfa3ced67fbfbd3683
4
- data.tar.gz: 2d8bf85017eea5e2f54d67b021f53c3546cd332bf3f08e0cba9366fa137a05af
3
+ metadata.gz: 690388aeb46674b4df24ccbc320f54604f70671538c38146200bab7579f752dd
4
+ data.tar.gz: 942a38b80af4938b37a9487ed200f8cef5952a16566a72b175d6f670943c40c1
5
5
  SHA512:
6
- metadata.gz: 8e012d0c7ef9ccdb72467abdcaa1a7d0df15c9adfb1aabf7bad3630c29e6177a0743f27c206891e20e53a969610fa8174e92f445c302384874c7d57275e2e37b
7
- data.tar.gz: 99c7b3a952de4ec42926655452a161c11fbae9b842fcdaa58a7c73943e6ed53d7948a5d5047134c20beac6a14224a99154bed7245c83c2f005765224e6e9ab98
6
+ metadata.gz: f9e0d0d44e7020d5a0c4e6011238ee01996634f313e802d83dd28971c1eb9d97820c3820497029761242a70e9bf9c3d059651685ad299348cd0891b6dac93900
7
+ data.tar.gz: 40325179dec57602f0c40e5ae745b5098c9922e9af8cf35a3384bab65cc6cdb51222e68d71cfec189496a844001a5d52170a8baa1ecc4eaddf88190f46a2a420
data/adsedare.gemspec CHANGED
@@ -33,4 +33,5 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency "faraday", "~> 2.7"
34
34
  spec.add_dependency "faraday-cookie_jar", "~> 0.0.7"
35
35
  spec.add_dependency "plist", "~> 3.2.0"
36
+ spec.add_dependency "fastlane-sirp", "~> 1.0"
36
37
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Adsedare
4
- VERSION = "0.0.6"
4
+ VERSION = "0.0.7"
5
5
  end
@@ -35,8 +35,10 @@ module Adsedare
35
35
  bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
36
36
  end
37
37
 
38
- bundles_with_profiles["included"].each do |profile|
39
- profiles_by_id[profile["id"]] = profile
38
+ if bundles_with_profiles["included"]
39
+ bundles_with_profiles["included"].each do |profile|
40
+ profiles_by_id[profile["id"]] = profile
41
+ end
40
42
  end
41
43
 
42
44
  project.targets.each do |target|
data/lib/adsedare.rb CHANGED
@@ -174,8 +174,8 @@ module Adsedare
174
174
  need_update = false
175
175
 
176
176
  capabilities.each do |capability|
177
- if capability.check?(bundle_info)
178
- else
177
+ if !capability.check?(bundle_info)
178
+ logger.warn "Bundle '#{bundle_identifier}' is missing capability '#{capability.type}'."
179
179
  need_update = true
180
180
  end
181
181
  end
@@ -24,7 +24,6 @@ module AppStoreConnect
24
24
  if response.status == 200
25
25
  JSON.parse(response.body)
26
26
  else
27
- puts response.body
28
27
  raise "Failed to get bundle IDs: #{response.status}"
29
28
  end
30
29
  end
@@ -7,19 +7,13 @@ module Starship
7
7
  def initialize
8
8
  end
9
9
 
10
- # Get the 2FA code
11
- # @param session_id [String] The session ID from Apple
12
- # @param scnt [String] The scnt value from Apple
13
- # @return [String] The 2FA code
14
10
  def get_code(session_id, scnt)
15
11
  raise NotImplementedError, "Subclasses must implement get_code"
16
12
  end
17
13
 
18
- # Verify if this provider can handle the given 2FA type
19
- # @param type [String] The 2FA type (sms, voice, etc.)
20
- # @return [Boolean] Whether this provider can handle the given type
21
- def can_handle?(type)
22
- raise NotImplementedError, "Subclasses must implement can_handle?"
14
+ # @return [String] The type of 2FA ("phone", "trusteddevice")
15
+ def two_factor_type
16
+ raise NotImplementedError, "Subclasses must implement two_factor_type"
23
17
  end
24
18
  end
25
19
 
@@ -33,8 +27,8 @@ module Starship
33
27
  code
34
28
  end
35
29
 
36
- def can_handle?(type)
37
- true
30
+ def two_factor_type
31
+ "trusteddevice"
38
32
  end
39
33
  end
40
34
  end
@@ -8,6 +8,7 @@ require "digest"
8
8
  require "fileutils"
9
9
  require "openssl"
10
10
  require "securerandom"
11
+ require "fastlane-sirp"
11
12
 
12
13
  require_relative "2fa_provider"
13
14
  require_relative "../logging"
@@ -18,13 +19,19 @@ module Starship
18
19
  # AuthHelper handles authentication with Apple's developer portal
19
20
  class AuthHelper
20
21
  include Logging
21
-
22
+
23
+ @two_factor_provider = Starship::ManualTwoFactorProvider.new
22
24
  attr_reader :session, :csrf, :csrf_ts, :session_data
23
25
 
24
26
  AUTH_ENDPOINT = "https://idmsa.apple.com/appleauth/auth"
25
27
  WIDGET_KEY_URL = "https://appstoreconnect.apple.com/olympus/v1/app/config?hostname=itunesconnect.apple.com"
26
28
 
27
- def initialize(two_factor_provider = nil)
29
+ def two_factor_provider=(provider)
30
+ @two_factor_provider = provider
31
+ logger.info "Two-factor provider set to #{provider.class.name}"
32
+ end
33
+
34
+ def initialize()
28
35
  # Create session directory if it doesn't exist
29
36
  @session_directory = File.expand_path("~/.starship")
30
37
  FileUtils.mkdir_p(@session_directory)
@@ -34,7 +41,6 @@ module Starship
34
41
  @csrf_ts = nil
35
42
  @email = nil
36
43
  @session_data = {}
37
- @two_factor_provider = two_factor_provider || Starship::ManualTwoFactorProvider.new
38
44
 
39
45
  # Initialize Faraday with cookie jar
40
46
  @session = Faraday.new do |builder|
@@ -72,8 +78,6 @@ module Starship
72
78
  auth_result = authenticate_with_srp(email, password)
73
79
 
74
80
  if auth_result == :two_factor_required
75
- logger.info "Two-factor authentication required. Requesting verification code..."
76
-
77
81
  handle_two_factor_auth
78
82
  elsif auth_result
79
83
  # After successful authentication, get CSRF tokens
@@ -303,13 +307,60 @@ module Starship
303
307
  end
304
308
  end
305
309
 
310
+ def to_hex(str)
311
+ str.unpack1('H*')
312
+ end
313
+
314
+ def to_byte(str)
315
+ [str].pack('H*')
316
+ end
317
+
318
+ def pbkdf2(password, salt, iterations, key_length, digest = OpenSSL::Digest::SHA256.new)
319
+ password = OpenSSL::Digest::SHA256.digest(password)
320
+ OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, key_length, digest)
321
+ end
322
+
323
+ def fetch_hashcash
324
+ response = @session.get(
325
+ "#{AUTH_ENDPOINT}/signin?widgetKey=#{widget_key}",
326
+ )
327
+ headers = response.headers
328
+
329
+ bits = headers["X-Apple-HC-Bits"]
330
+ challenge = headers["X-Apple-HC-Challenge"]
331
+
332
+ if bits.nil? || challenge.nil?
333
+ logger.warn "Unable to find 'X-Apple-HC-Bits' and 'X-Apple-HC-Challenge' to make hashcash"
334
+ return nil
335
+ end
336
+
337
+ return make_hashcash(bits: bits, challenge: challenge)
338
+ end
339
+
340
+ def make_hashcash(bits: nil, challenge: nil)
341
+ version = 1
342
+ date = Time.now.strftime("%Y%m%d%H%M%S")
343
+
344
+ counter = 0
345
+ loop do
346
+ hc = [
347
+ version, bits, date, challenge, ":#{counter}"
348
+ ].join(":")
349
+
350
+ if Digest::SHA1.digest(hc).unpack1('B*')[0, bits.to_i].to_i == 0
351
+ return hc
352
+ end
353
+ counter += 1
354
+ end
355
+ end
356
+
306
357
  # Authenticate with Secure Remote Password protocol
307
358
  # @param email [String] The email address
308
359
  # @param password [String] The password
309
360
  # @return [Boolean, Symbol] True if successful, :two_factor_required if 2FA is needed, false otherwise
310
361
  def authenticate_with_srp(email, password)
311
- # This is a simplified SRP implementation since Ruby doesn"t have a direct equivalent to Python"s srp library
312
- # In a real implementation, you would use a proper SRP library or implement the full protocol
362
+ client = SIRP::Client.new(2048)
363
+ a = client.start_authentication()
313
364
 
314
365
  headers = {
315
366
  "Accept" => "application/json, text/javascript",
@@ -318,6 +369,17 @@ module Starship
318
369
  "X-Apple-Widget-Key" => widget_key,
319
370
  }
320
371
 
372
+ federate_data = {
373
+ "accountName" => email,
374
+ "rememberMe" => false,
375
+ }
376
+
377
+ response = @session.post(
378
+ "#{AUTH_ENDPOINT}/federate?isRememberMeEnabled=false",
379
+ federate_data.to_json,
380
+ headers
381
+ )
382
+
321
383
  if @session_data["session_id"]
322
384
  headers.update({
323
385
  "X-Apple-ID-Session-Id" => @session_data["session_id"],
@@ -325,18 +387,54 @@ module Starship
325
387
  })
326
388
  end
327
389
 
328
- # For simplicity, we"ll use a direct password-based authentication approach
329
- # In a real implementation, you would implement the full SRP protocol
330
- auth_data = {
331
- "accountName" => email,
332
- "password" => password,
333
- "rememberMe" => true,
390
+ init_data = {
391
+ "a": Base64.strict_encode64(to_byte(a)),
392
+ "accountName": email,
393
+ "protocols": ["s2k", "s2k_fo"],
334
394
  }
335
395
 
336
- logger.info "Initializing authentication request to Apple ID..."
337
396
  response = @session.post(
338
- "#{AUTH_ENDPOINT}/signin",
339
- auth_data.to_json,
397
+ "#{AUTH_ENDPOINT}/signin/init",
398
+ init_data.to_json,
399
+ headers
400
+ )
401
+
402
+ body = JSON.parse(response.body)
403
+ salt = Base64.strict_decode64(body["salt"])
404
+ b = Base64.strict_decode64(body["b"])
405
+ c = body["c"]
406
+ iterations = body["iteration"]
407
+ key_length = 32
408
+
409
+ encrypted_password = pbkdf2(password, salt, iterations, key_length)
410
+
411
+ m1 = client.process_challenge(
412
+ email,
413
+ to_hex(encrypted_password),
414
+ to_hex(salt),
415
+ to_hex(b),
416
+ is_password_encrypted: true
417
+ )
418
+ m2 = client.H_AMK
419
+
420
+ complete_data = {
421
+ "accountName": email,
422
+ "c": c,
423
+ "m1": Base64.encode64(to_byte(m1)).strip,
424
+ "m2": Base64.encode64(to_byte(m2)).strip,
425
+ "rememberMe": false
426
+ }
427
+
428
+ hashcash = self.fetch_hashcash
429
+ if hashcash
430
+ headers.update({
431
+ "X-Apple-HC" => hashcash,
432
+ })
433
+ end
434
+
435
+ response = @session.post(
436
+ "#{AUTH_ENDPOINT}/signin/complete?isRememberMeEnabled=false",
437
+ complete_data.to_json,
340
438
  headers
341
439
  )
342
440
 
@@ -391,11 +489,60 @@ module Starship
391
489
  end
392
490
 
393
491
  begin
394
- # Get 2FA code from provider
492
+ two_factor_type = @two_factor_provider.two_factor_type
493
+ logger.info "Two-factor authentication required. Requesting #{two_factor_type} verification code..."
494
+
495
+ auth_options_headers = {
496
+ "Accept" => "application/json, text/javascript",
497
+ "Content-Type" => "application/json",
498
+ "X-Requested-With" => "XMLHttpRequest",
499
+ "X-Apple-ID-Session-Id" => session_id,
500
+ "scnt" => scnt,
501
+ "X-Apple-Widget-Key" => widget_key,
502
+ }
503
+
504
+ auth_options_response = @session.get(
505
+ "#{AUTH_ENDPOINT}/auth",
506
+ nil,
507
+ auth_options_headers
508
+ )
509
+ auth_options = JSON.parse(auth_options_response.body)
510
+
511
+ trigger_headers = {
512
+ "Accept" => "application/json",
513
+ "Content-Type" => "application/json",
514
+ "X-Requested-With" => "XMLHttpRequest",
515
+ "X-Apple-ID-Session-Id" => session_id,
516
+ "scnt" => scnt,
517
+ "X-Apple-Widget-Key" => widget_key,
518
+ }
519
+
520
+ trigger_data = {
521
+ }
522
+
523
+ if two_factor_type == "phone"
524
+ phone_number = auth_options["trustedPhoneNumbers"][0]
525
+ trigger_data = {
526
+ "phoneNumber" => {
527
+ "id" => phone_number["id"],
528
+ },
529
+ "mode" => "sms",
530
+ }
531
+ logger.info "Sending SMS to #{phone_number["numberWithDialCode"]}"
532
+ end
533
+
534
+ response = @session.put(
535
+ "#{AUTH_ENDPOINT}/verify/#{two_factor_type}",
536
+ trigger_data.to_json,
537
+ trigger_headers
538
+ )
539
+
395
540
  code = @two_factor_provider.get_code(session_id, scnt)
396
541
 
542
+ logger.info "Received code: #{code}"
543
+
397
544
  verify_headers = {
398
- "Accept" => "application/json, text/javascript",
545
+ "Accept" => "application/json",
399
546
  "Content-Type" => "application/json",
400
547
  "X-Requested-With" => "XMLHttpRequest",
401
548
  "X-Apple-ID-Session-Id" => session_id,
@@ -405,14 +552,19 @@ module Starship
405
552
 
406
553
  verify_data = { "securityCode" => { "code" => code.strip } }
407
554
 
555
+ if two_factor_type == "phone"
556
+ verify_data["phoneNumber"] = trigger_data["phoneNumber"]
557
+ verify_data["mode"] = trigger_data["mode"]
558
+ end
559
+
408
560
  # First verify the security code
409
561
  verify_response = @session.post(
410
- "#{AUTH_ENDPOINT}/verify/trusteddevice/securitycode",
562
+ "#{AUTH_ENDPOINT}/verify/#{two_factor_type}/securitycode",
411
563
  verify_data.to_json,
412
564
  verify_headers
413
565
  )
414
566
 
415
- if verify_response.status == 204
567
+ if verify_response.status == 204 || verify_response.status == 200
416
568
  logger.info "Two-factor code verified successfully."
417
569
  logger.info "Trusting the session after 2FA verification..."
418
570
 
@@ -423,7 +575,7 @@ module Starship
423
575
  verify_headers
424
576
  )
425
577
 
426
- if trust_response.status == 204
578
+ if trust_response.status == 204 || trust_response.status == 200
427
579
  # Store all relevant session data
428
580
  @session_data.update({
429
581
  "session_id" => session_id,
data/lib/starship.rb CHANGED
@@ -8,6 +8,10 @@ require_relative "starship/auth_helper"
8
8
  # Starship is a wrapper around Apple's developer portal API
9
9
  # Inspired by Fatslane's Spaceship
10
10
  module Starship
11
+ def self.set_provider(provider)
12
+ Client.auth_helper.two_factor_provider = provider
13
+ end
14
+
11
15
  class Client
12
16
  DEV_SERVICES_V1 = "https://developer.apple.com/services-account/v1"
13
17
  DEV_SERVICES_QH65B2 = "https://developer.apple.com/services-account/QH65B2"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adsedare
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - alexstrnik
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-05-05 00:00:00.000000000 Z
10
+ date: 2025-05-27 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake
@@ -94,6 +93,20 @@ dependencies:
94
93
  - - "~>"
95
94
  - !ruby/object:Gem::Version
96
95
  version: 3.2.0
96
+ - !ruby/object:Gem::Dependency
97
+ name: fastlane-sirp
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.0'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.0'
97
110
  description: AdSedare is a powerful library designed to simplify the process of iOS
98
111
  ad-hoc distribution. By automating and streamlining the distribution of builds,
99
112
  it ensures a smooth, pain-free experience for developers and testers. AdSedare is
@@ -130,7 +143,6 @@ licenses:
130
143
  metadata:
131
144
  homepage_uri: https://github.com/AlexStrNik/AdSedare
132
145
  source_code_uri: https://github.com/AlexStrNik/AdSedare
133
- post_install_message:
134
146
  rdoc_options: []
135
147
  require_paths:
136
148
  - lib
@@ -145,8 +157,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
157
  - !ruby/object:Gem::Version
146
158
  version: '0'
147
159
  requirements: []
148
- rubygems_version: 3.0.3.1
149
- signing_key:
160
+ rubygems_version: 3.6.3
150
161
  specification_version: 4
151
162
  summary: A cross-platform library for seamless, pain-free iOS ad-hoc distribution.
152
163
  test_files: []