adsedare 0.0.6 → 0.0.8

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: '0188667ec36408b4bd8e25d1396efcd88ccf529b54812a3b8b3120c40f6f56f1'
4
+ data.tar.gz: 29d5b36a0868ee133de957963a7da00f7362d836fd76a3a5b4d63c40c861905b
5
5
  SHA512:
6
- metadata.gz: 8e012d0c7ef9ccdb72467abdcaa1a7d0df15c9adfb1aabf7bad3630c29e6177a0743f27c206891e20e53a969610fa8174e92f445c302384874c7d57275e2e37b
7
- data.tar.gz: 99c7b3a952de4ec42926655452a161c11fbae9b842fcdaa58a7c73943e6ed53d7948a5d5047134c20beac6a14224a99154bed7245c83c2f005765224e6e9ab98
6
+ metadata.gz: 199c7941e00a2b71260ed190e0855742a10238e8c8265af59d60329cc99532fc0f22648955a04eda31debe0ed064a9f7fa633f79495a096bd051c7424278c2c5
7
+ data.tar.gz: 553d74cd5cf5e3ab379d98bce99ff5bccd7a9fa6e27d99a0b9ca0e7570a09786da025953ebba162d4106e4b2720292da0bb60aa6cdb382e07ca0bffb3135cf79
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.8"
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
@@ -137,10 +137,14 @@ module Adsedare
137
137
  devices = get_devices(team_id)
138
138
  deviceIds = devices.map { |device| device["id"] }
139
139
 
140
+ profileDeviceIds = profile["provisioningProfile"]["devices"].map {
141
+ |device| device["deviceId"]
142
+ }
143
+
140
144
  need_update = false
141
145
 
142
- profile["provisioningProfile"]["devices"].each do |device|
143
- if !deviceIds.include?(device["deviceId"])
146
+ deviceIds.each do |deviceId|
147
+ if !profileDeviceIds.include?(deviceId)
144
148
  need_update = true
145
149
  break
146
150
  end
@@ -174,8 +178,8 @@ module Adsedare
174
178
  need_update = false
175
179
 
176
180
  capabilities.each do |capability|
177
- if capability.check?(bundle_info)
178
- else
181
+ if !capability.check?(bundle_info)
182
+ logger.warn "Bundle '#{bundle_identifier}' is missing capability '#{capability.type}'."
179
183
  need_update = true
180
184
  end
181
185
  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,14 @@
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.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - alexstrnik
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-05-05 00:00:00.000000000 Z
11
+ date: 2025-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: 3.2.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: fastlane-sirp
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.0'
97
111
  description: AdSedare is a powerful library designed to simplify the process of iOS
98
112
  ad-hoc distribution. By automating and streamlining the distribution of builds,
99
113
  it ensures a smooth, pain-free experience for developers and testers. AdSedare is