adsedare 0.0.1 → 0.0.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.
- checksums.yaml +4 -4
- data/adsedare.gemspec +1 -1
- data/lib/adsedare/capabilities.rb +43 -38
- data/lib/adsedare/export_options.rb +90 -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 +22 -287
- 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 +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de6a221a878c99d756c8cb9779cc91145f061084c64f816e5476d4d16e297c84
|
4
|
+
data.tar.gz: 0c9afea95fdd8fa86ad25831528ee11591cdcf4a5557738767a7ad5d8dbdcd26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9178bc2fae2c7e1ff727702a9cd1fcc9b4fdc4b1ff51ae9e714a8ba44619108ce31b686a6e1e460d9f509d60b25e95d38b8de7f68d3a1c2053d7a291074f0bb0
|
7
|
+
data.tar.gz: aa57db0c849da84a65a18a58bb821a98e72facc91ef81df28f51d5050b4be08b927944d0b89b47a15c0562a09528f70b9785b3dd5d10f1f1d58fbdcc72fe22ee
|
data/adsedare.gemspec
CHANGED
@@ -31,6 +31,6 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_dependency "xcodeproj", "~> 1.27.0"
|
32
32
|
spec.add_dependency "jwt", "~> 2.7"
|
33
33
|
spec.add_dependency "faraday", "~> 2.7"
|
34
|
-
spec.add_dependency "faraday-cookie_jar", "~> 0.0.7"
|
34
|
+
spec.add_dependency "faraday-cookie_jar", "~> 0.0.7"
|
35
35
|
spec.add_dependency "plist", "~> 3.2.0"
|
36
36
|
end
|
@@ -28,59 +28,60 @@ module Adsedare
|
|
28
28
|
"com.apple.developer.family-controls" => "FAMILY_CONTROLS",
|
29
29
|
"com.apple.developer.devicecheck.appattest-environment" => "APP_ATTEST",
|
30
30
|
"com.apple.developer.game-center" => "GAME_CENTER",
|
31
|
-
"com.apple.developer.carplay-maps" => "CARPLAY_NAVIGATION"
|
31
|
+
"com.apple.developer.carplay-maps" => "CARPLAY_NAVIGATION",
|
32
32
|
}
|
33
33
|
|
34
34
|
class << self
|
35
35
|
private
|
36
|
-
|
36
|
+
|
37
37
|
def parse_entitlements(path)
|
38
38
|
raise Error, "Entitlements file not found: #{path}" unless File.exist?(path)
|
39
|
-
|
39
|
+
|
40
40
|
entitlements = Plist.parse_xml(path)
|
41
41
|
capabilities = []
|
42
|
-
|
42
|
+
|
43
43
|
entitlements.each do |key, value|
|
44
44
|
capability_type = ENTITLEMENTS_MAPPING[key]
|
45
45
|
next unless capability_type
|
46
|
-
|
46
|
+
|
47
47
|
if key == "com.apple.security.application-groups"
|
48
48
|
capabilities << AppGroupsCapability.new(capability_type, value)
|
49
49
|
else
|
50
50
|
capabilities << SimpleCapability.new(capability_type)
|
51
51
|
end
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
capabilities
|
55
55
|
end
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
class Capability
|
59
59
|
attr_reader :type
|
60
|
-
|
60
|
+
|
61
61
|
def initialize(type)
|
62
62
|
@type = type
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
def check?(bundle_info)
|
66
|
-
bundle_info["included"].any? {
|
67
|
-
|cap|
|
66
|
+
bundle_info["included"].any? {
|
67
|
+
|cap|
|
68
|
+
cap["type"] == "capabilities" && cap["id"] == @type
|
68
69
|
}
|
69
70
|
end
|
70
|
-
|
71
|
+
|
71
72
|
def to_bundle_capability(bundle_info, team_id)
|
72
73
|
return {
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
74
|
+
"type" => "bundleIdCapabilities",
|
75
|
+
"attributes" => {
|
76
|
+
"enabled" => true,
|
77
|
+
"settings" => [],
|
78
|
+
},
|
79
|
+
"relationships" => {
|
80
|
+
"capability" => {
|
81
|
+
"data" => { "type" => "capabilities", "id" => @type },
|
82
|
+
},
|
83
|
+
},
|
84
|
+
}
|
84
85
|
end
|
85
86
|
end
|
86
87
|
|
@@ -89,34 +90,38 @@ module Adsedare
|
|
89
90
|
|
90
91
|
class AppGroupsCapability < Capability
|
91
92
|
attr_reader :groups
|
92
|
-
|
93
|
+
|
93
94
|
def initialize(type, groups)
|
94
95
|
super(type)
|
95
96
|
@groups = groups
|
96
97
|
end
|
97
|
-
|
98
|
+
|
98
99
|
def check?(bundle_info)
|
99
|
-
have_capability = bundle_info["included"].any? {
|
100
|
-
|cap|
|
100
|
+
have_capability = bundle_info["included"].any? {
|
101
|
+
|cap|
|
102
|
+
cap["type"] == "capabilities" && cap["id"] == @type
|
101
103
|
}
|
102
104
|
return false unless have_capability
|
103
105
|
|
104
|
-
registered_groups = bundle_info["included"].select {
|
105
|
-
|cap|
|
106
|
-
|
107
|
-
|
106
|
+
registered_groups = bundle_info["included"].select {
|
107
|
+
|cap|
|
108
|
+
cap["type"] == "appGroups"
|
109
|
+
}.map {
|
110
|
+
|cap|
|
111
|
+
cap["attributes"]["identifier"]
|
108
112
|
}
|
109
|
-
|
113
|
+
|
110
114
|
return @groups.all? { |group| registered_groups.include?(group) }
|
111
115
|
end
|
112
|
-
|
116
|
+
|
113
117
|
def to_bundle_capability(bundle_info, team_id)
|
114
118
|
registered_app_groups = {}
|
115
|
-
|
119
|
+
|
116
120
|
Starship::Client.get_app_groups(team_id).each {
|
117
|
-
|app_group|
|
121
|
+
|app_group|
|
122
|
+
registered_app_groups[app_group["identifier"]] = app_group["applicationGroup"]
|
118
123
|
}
|
119
|
-
|
124
|
+
|
120
125
|
@groups.each do |group|
|
121
126
|
if not registered_app_groups.include?(group)
|
122
127
|
new_app_group = Starship::Client.create_app_group(group, team_id)
|
@@ -127,11 +132,11 @@ module Adsedare
|
|
127
132
|
bundle_capability = super(bundle_info, team_id)
|
128
133
|
|
129
134
|
app_groups = {
|
130
|
-
"data" => @groups.map { |group| { "type" => "appGroups", "id" => registered_app_groups[group] } }
|
135
|
+
"data" => @groups.map { |group| { "type" => "appGroups", "id" => registered_app_groups[group] } },
|
131
136
|
}
|
132
137
|
|
133
138
|
bundle_capability["relationships"]["appGroups"] = app_groups
|
134
|
-
|
139
|
+
|
135
140
|
return bundle_capability
|
136
141
|
end
|
137
142
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require "xcodeproj"
|
2
|
+
require "plist"
|
3
|
+
|
4
|
+
require_relative "../appstoreconnect"
|
5
|
+
|
6
|
+
module Adsedare
|
7
|
+
class << self
|
8
|
+
def make_export_options(project_path = nil, export_path = nil, team_id = nil, options = {})
|
9
|
+
raise "Project path is not set" unless project_path
|
10
|
+
raise "Export path is not set" unless export_path
|
11
|
+
|
12
|
+
project = Xcodeproj::Project.open(project_path)
|
13
|
+
export_options = {
|
14
|
+
"method" => "ad-hoc",
|
15
|
+
"destination" => "export",
|
16
|
+
"signingStyle" => "manual",
|
17
|
+
"signingCertificate" => "Apple Distribution",
|
18
|
+
"provisioningProfiles" => {},
|
19
|
+
}.merge(options)
|
20
|
+
|
21
|
+
project_bundles = []
|
22
|
+
|
23
|
+
project.targets.each do |target|
|
24
|
+
target.build_configurations.each do |config|
|
25
|
+
team_id ||= config.build_settings["DEVELOPMENT_TEAM"]
|
26
|
+
project_bundles << config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
export_options["teamID"] = team_id
|
31
|
+
|
32
|
+
logger.info "Fetching bundles with profiles for team ID '#{team_id}'"
|
33
|
+
|
34
|
+
bundles_with_profiles = AppStoreConnect::Client.get_bundles_with_profiles(project_bundles)
|
35
|
+
bundle_by_identifier = {}
|
36
|
+
profiles_by_id = {}
|
37
|
+
|
38
|
+
bundles_with_profiles["data"].each do |bundle_id|
|
39
|
+
bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
|
40
|
+
end
|
41
|
+
|
42
|
+
bundles_with_profiles["included"].each do |profile|
|
43
|
+
profiles_by_id[profile["id"]] = profile
|
44
|
+
end
|
45
|
+
|
46
|
+
project_bundles.each do |bundle_identifier|
|
47
|
+
bundle_id = bundle_by_identifier[bundle_identifier]
|
48
|
+
unless bundle_id
|
49
|
+
logger.warn "Bundle '#{bundle_identifier}' is missing in App Store Connect. Skipping."
|
50
|
+
next
|
51
|
+
end
|
52
|
+
|
53
|
+
logger.info "Bundle '#{bundle_identifier}' resolved to Bundle ID '#{bundle_id["id"]}'"
|
54
|
+
|
55
|
+
profiles = bundle_id["relationships"]["profiles"]["data"]
|
56
|
+
unless profiles
|
57
|
+
logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
ad_hoc_profile = nil
|
62
|
+
profiles.each do |profile|
|
63
|
+
profile_id = profile["id"]
|
64
|
+
profile = profiles_by_id[profile_id]
|
65
|
+
|
66
|
+
if profile["attributes"]["profileType"] == "IOS_APP_ADHOC" && profile["attributes"]["profileState"] == "ACTIVE"
|
67
|
+
ad_hoc_profile = profile
|
68
|
+
break
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
unless ad_hoc_profile
|
73
|
+
logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
logger.info "Profile for Bundle ID '#{bundle_id["id"]}' resolved to Profile '#{ad_hoc_profile["attributes"]["name"]}'"
|
78
|
+
|
79
|
+
profile_name = ad_hoc_profile["attributes"]["name"]
|
80
|
+
|
81
|
+
export_options["provisioningProfiles"][bundle_identifier] = profile_name
|
82
|
+
end
|
83
|
+
|
84
|
+
options_plist = Plist::Emit.dump(export_options)
|
85
|
+
File.write(File.expand_path(export_path), options_plist)
|
86
|
+
|
87
|
+
return export_options
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "xcodeproj"
|
4
|
+
require "base64"
|
5
|
+
require "fileutils"
|
6
|
+
require "tempfile"
|
7
|
+
|
8
|
+
module Adsedare
|
9
|
+
APPLE_CERTS = [
|
10
|
+
"AppleWWDRCAG2.cer",
|
11
|
+
"AppleWWDRCAG3.cer",
|
12
|
+
"AppleWWDRCAG4.cer",
|
13
|
+
"AppleWWDRCAG5.cer",
|
14
|
+
"AppleWWDRCAG6.cer",
|
15
|
+
"AppleWWDRCAG7.cer",
|
16
|
+
"AppleWWDRCAG8.cer",
|
17
|
+
"DeveloperIDG2CA.cer",
|
18
|
+
]
|
19
|
+
APPLE_CERTS_URL = "https://www.apple.com/certificateauthority"
|
20
|
+
|
21
|
+
APPLE_WWDRCA = "https://developer.apple.com/certificationauthority/AppleWWDRCA.cer"
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def create_keychain(keychain_path = nil, keychain_password = nil, make_default = true)
|
25
|
+
raise "Keychain path is not set" unless keychain_path
|
26
|
+
raise "Keychain password is not set" unless keychain_password
|
27
|
+
|
28
|
+
keychain_path = File.expand_path(keychain_path)
|
29
|
+
|
30
|
+
logger.info "Creating keychain at '#{keychain_path}'"
|
31
|
+
|
32
|
+
FileUtils.mkdir_p(File.dirname(keychain_path))
|
33
|
+
status = system("security create-keychain -p #{keychain_password} #{keychain_path}")
|
34
|
+
unless status
|
35
|
+
logger.error "Failed to create keychain at '#{keychain_path}'"
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
APPLE_CERTS.each do |cert|
|
40
|
+
logger.info "Downloading certificate '#{cert}'"
|
41
|
+
|
42
|
+
response = Faraday.get(
|
43
|
+
"#{APPLE_CERTS_URL}/#{cert}"
|
44
|
+
)
|
45
|
+
unless response.status == 200
|
46
|
+
logger.error "Failed to download certificate '#{cert}'"
|
47
|
+
next
|
48
|
+
end
|
49
|
+
|
50
|
+
file = Tempfile.new(cert)
|
51
|
+
file.write(response.body)
|
52
|
+
file.close
|
53
|
+
|
54
|
+
install_certificate(file.path, keychain_path)
|
55
|
+
|
56
|
+
file.unlink
|
57
|
+
end
|
58
|
+
|
59
|
+
logger.info "Downloading certificate 'AppleWWDRCA.cer'"
|
60
|
+
response = Faraday.get(
|
61
|
+
APPLE_WWDRCA
|
62
|
+
)
|
63
|
+
unless response.status == 200
|
64
|
+
logger.error "Failed to download certificate 'AppleWWDRCA.cer'"
|
65
|
+
else
|
66
|
+
file = Tempfile.new("AppleWWDRCA.cer")
|
67
|
+
file.write(response.body)
|
68
|
+
file.close
|
69
|
+
|
70
|
+
install_certificate(file.path, keychain_path)
|
71
|
+
|
72
|
+
file.unlink
|
73
|
+
end
|
74
|
+
|
75
|
+
ad_hoc_certificate = ENV["AD_HOC_CERTIFICATE"]
|
76
|
+
ad_hoc_private_key = ENV["AD_HOC_PRIVATE_KEY"]
|
77
|
+
ad_hoc_key_password = ENV["AD_HOC_KEY_PASSWORD"]
|
78
|
+
|
79
|
+
unless ad_hoc_certificate || ad_hoc_private_key || ad_hoc_key_password
|
80
|
+
logger.warn "AD_HOC_CERTIFICATE, AD_HOC_PRIVATE_KEY, or AD_HOC_KEY_PASSWORD is not set"
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
install_certificate(ad_hoc_private_key, keychain_path, ad_hoc_key_password, "priv")
|
85
|
+
install_certificate(ad_hoc_certificate, keychain_path, "", "cert")
|
86
|
+
|
87
|
+
if make_default
|
88
|
+
status = system("security default-keychain -d user -s #{keychain_path}")
|
89
|
+
unless status
|
90
|
+
logger.warn "Failed to set default keychain"
|
91
|
+
return
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
status = system("security set-keychain-settings #{keychain_path}")
|
96
|
+
unless status
|
97
|
+
logger.error "Failed to set keychain settings"
|
98
|
+
return
|
99
|
+
end
|
100
|
+
|
101
|
+
status = system("security set-key-partition-list -S apple-tool:,apple: -k #{keychain_password} #{keychain_path}")
|
102
|
+
unless status
|
103
|
+
logger.error "Failed to set keychain partition list"
|
104
|
+
return
|
105
|
+
end
|
106
|
+
|
107
|
+
status = system("security unlock-keychain -p #{keychain_password} #{keychain_path}")
|
108
|
+
unless status
|
109
|
+
logger.error "Failed to unlock keychain"
|
110
|
+
return
|
111
|
+
end
|
112
|
+
|
113
|
+
logger.info "Keychain created at '#{keychain_path}'"
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def install_certificate(certificate_path, keychain_path, certificate_password = "", certificate_type = "cert")
|
119
|
+
certificate_name = File.basename(certificate_path)
|
120
|
+
logger.info "Installing certificate '#{certificate_name}' to keychain '#{keychain_path}'"
|
121
|
+
|
122
|
+
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")
|
123
|
+
unless status
|
124
|
+
logger.error "Failed to install certificate '#{certificate_name}' to keychain '#{keychain_path}'"
|
125
|
+
return
|
126
|
+
end
|
127
|
+
|
128
|
+
logger.info "Certificate '#{certificate_name}' installed to keychain '#{keychain_path}'"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/adsedare/version.rb
CHANGED
@@ -0,0 +1,76 @@
|
|
1
|
+
require "xcodeproj"
|
2
|
+
require "plist"
|
3
|
+
|
4
|
+
require_relative "../appstoreconnect"
|
5
|
+
|
6
|
+
module Adsedare
|
7
|
+
class << self
|
8
|
+
def patch_project(project_path, team_id = nil)
|
9
|
+
raise "Project path is not set" unless project_path
|
10
|
+
|
11
|
+
project = Xcodeproj::Project.open(project_path)
|
12
|
+
|
13
|
+
project_bundles = project.targets.map do |target|
|
14
|
+
target.build_configurations.map do |config|
|
15
|
+
config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
|
16
|
+
end
|
17
|
+
end.flatten.uniq
|
18
|
+
|
19
|
+
bundles_with_profiles = AppStoreConnect::Client.get_bundles_with_profiles(project_bundles)
|
20
|
+
bundle_by_identifier = {}
|
21
|
+
profiles_by_id = {}
|
22
|
+
|
23
|
+
bundles_with_profiles["data"].each do |bundle_id|
|
24
|
+
bundle_by_identifier[bundle_id["attributes"]["identifier"]] = bundle_id
|
25
|
+
end
|
26
|
+
|
27
|
+
bundles_with_profiles["included"].each do |profile|
|
28
|
+
profiles_by_id[profile["id"]] = profile
|
29
|
+
end
|
30
|
+
|
31
|
+
project.targets.each do |target|
|
32
|
+
target.build_configurations.each do |config|
|
33
|
+
bundle_identifier = config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"]
|
34
|
+
bundle_id = bundle_by_identifier[bundle_identifier]
|
35
|
+
unless bundle_id
|
36
|
+
logger.warn "Bundle '#{bundle_identifier}' is missing in App Store Connect. Skipping."
|
37
|
+
next
|
38
|
+
end
|
39
|
+
|
40
|
+
logger.info "Bundle '#{bundle_identifier}' resolved to Bundle ID '#{bundle_id["id"]}'"
|
41
|
+
|
42
|
+
profiles = bundle_id["relationships"]["profiles"]["data"]
|
43
|
+
unless profiles
|
44
|
+
logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
|
45
|
+
next
|
46
|
+
end
|
47
|
+
|
48
|
+
ad_hoc_profile = nil
|
49
|
+
profiles.each do |profile|
|
50
|
+
profile_id = profile["id"]
|
51
|
+
profile = profiles_by_id[profile_id]
|
52
|
+
|
53
|
+
if profile["attributes"]["profileType"] == "IOS_APP_ADHOC" && profile["attributes"]["profileState"] == "ACTIVE"
|
54
|
+
ad_hoc_profile = profile
|
55
|
+
break
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
unless ad_hoc_profile
|
60
|
+
logger.warn "Profile for Bundle ID '#{bundle_id["id"]}' is missing in App Store Connect. Skipping."
|
61
|
+
next
|
62
|
+
end
|
63
|
+
|
64
|
+
config.build_settings["CODE_SIGN_IDENTITY"] = "iPhone Distribution"
|
65
|
+
config.build_settings["CODE_SIGN_STYLE"] = "Manual"
|
66
|
+
if team_id
|
67
|
+
config.build_settings["DEVELOPMENT_TEAM"] = team_id
|
68
|
+
end
|
69
|
+
config.build_settings["PROVISIONING_PROFILE_SPECIFIER"] = ad_hoc_profile["attributes"]["name"]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
project.save
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|