spaceship 0.0.15 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/assets/languageMapping.json +224 -0
- data/lib/spaceship.rb +20 -63
- data/lib/spaceship/base.rb +71 -14
- data/lib/spaceship/client.rb +9 -274
- data/lib/spaceship/launcher.rb +1 -1
- data/lib/spaceship/portal/app.rb +125 -0
- data/lib/spaceship/portal/certificate.rb +273 -0
- data/lib/spaceship/portal/device.rb +102 -0
- data/lib/spaceship/portal/portal.rb +6 -0
- data/lib/spaceship/portal/portal_base.rb +13 -0
- data/lib/spaceship/portal/portal_client.rb +289 -0
- data/lib/spaceship/portal/provisioning_profile.rb +369 -0
- data/lib/spaceship/portal/spaceship.rb +94 -0
- data/lib/spaceship/{ui → portal/ui}/select_team.rb +0 -0
- data/lib/spaceship/tunes/app_screenshot.rb +28 -0
- data/lib/spaceship/tunes/app_status.rb +63 -0
- data/lib/spaceship/tunes/app_submission.rb +149 -0
- data/lib/spaceship/tunes/app_version.rb +337 -0
- data/lib/spaceship/tunes/application.rb +253 -0
- data/lib/spaceship/tunes/build.rb +128 -0
- data/lib/spaceship/tunes/build_train.rb +79 -0
- data/lib/spaceship/tunes/language_converter.rb +44 -0
- data/lib/spaceship/tunes/language_item.rb +54 -0
- data/lib/spaceship/tunes/processing_build.rb +30 -0
- data/lib/spaceship/tunes/spaceship.rb +26 -0
- data/lib/spaceship/tunes/tester.rb +177 -0
- data/lib/spaceship/tunes/tunes.rb +12 -0
- data/lib/spaceship/tunes/tunes_base.rb +15 -0
- data/lib/spaceship/tunes/tunes_client.rb +360 -0
- data/lib/spaceship/version.rb +1 -1
- metadata +27 -7
- data/lib/spaceship/app.rb +0 -125
- data/lib/spaceship/certificate.rb +0 -271
- data/lib/spaceship/device.rb +0 -100
- data/lib/spaceship/provisioning_profile.rb +0 -367
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Spaceship
|
4
|
+
module Portal
|
5
|
+
# Represents a certificate from the Apple Developer Portal.
|
6
|
+
#
|
7
|
+
# This can either be a code signing identity or a push profile
|
8
|
+
class Certificate < PortalBase
|
9
|
+
# @return (String) The ID given from the developer portal. You'll probably not need it.
|
10
|
+
# @example
|
11
|
+
# "P577TH3PAA"
|
12
|
+
attr_accessor :id
|
13
|
+
|
14
|
+
# @return (String) The name of the certificate
|
15
|
+
# @example Company
|
16
|
+
# "SunApps GmbH"
|
17
|
+
# @example Push Profile
|
18
|
+
# "com.krausefx.app"
|
19
|
+
attr_accessor :name
|
20
|
+
|
21
|
+
# @return (String) Status of the certificate
|
22
|
+
# @example
|
23
|
+
# "Issued"
|
24
|
+
attr_accessor :status
|
25
|
+
|
26
|
+
# @return (Date) The date and time when the certificate was created
|
27
|
+
# @example
|
28
|
+
# 2015-04-01 21:24:00 UTC
|
29
|
+
attr_accessor :created
|
30
|
+
|
31
|
+
# @return (Date) The date and time when the certificate will expire
|
32
|
+
# @example
|
33
|
+
# 2016-04-01 21:24:00 UTC
|
34
|
+
attr_accessor :expires
|
35
|
+
|
36
|
+
# @return (String) The owner type that defines if it's a push profile
|
37
|
+
# or a code signing identity
|
38
|
+
#
|
39
|
+
# @example Code Signing Identity
|
40
|
+
# "team"
|
41
|
+
# @example Push Certificate
|
42
|
+
# "bundle"
|
43
|
+
attr_accessor :owner_type
|
44
|
+
|
45
|
+
# @return (String) The name of the owner
|
46
|
+
#
|
47
|
+
# @example Code Signing Identity (usually the company name)
|
48
|
+
# "SunApps Gmbh"
|
49
|
+
# @example Push Certificate (the name of your App ID)
|
50
|
+
# "Awesome App"
|
51
|
+
attr_accessor :owner_name
|
52
|
+
|
53
|
+
# @return (String) The ID of the owner, that can be used to
|
54
|
+
# fetch more information
|
55
|
+
# @example
|
56
|
+
# "75B83SPLAA"
|
57
|
+
attr_accessor :owner_id
|
58
|
+
|
59
|
+
# Indicates the type of this certificate
|
60
|
+
# which is automatically used to determine the class of
|
61
|
+
# the certificate. Available values listed in CERTIFICATE_TYPE_IDS
|
62
|
+
# @return (String) The type of the certificate
|
63
|
+
# @example Production Certificate
|
64
|
+
# "R58UK2EWSO"
|
65
|
+
# @example Development Certificate
|
66
|
+
# "5QPB9NHCEI"
|
67
|
+
attr_accessor :type_display_id
|
68
|
+
|
69
|
+
attr_mapping({
|
70
|
+
'certificateId' => :id,
|
71
|
+
'name' => :name,
|
72
|
+
'statusString' => :status,
|
73
|
+
'dateCreated' => :created,
|
74
|
+
'expirationDate' => :expires,
|
75
|
+
'ownerType' => :owner_type,
|
76
|
+
'ownerName' => :owner_name,
|
77
|
+
'ownerId' => :owner_id,
|
78
|
+
'certificateTypeDisplayId' => :type_display_id
|
79
|
+
})
|
80
|
+
|
81
|
+
#####################################################
|
82
|
+
# Certs are not associated with apps
|
83
|
+
#####################################################
|
84
|
+
|
85
|
+
# A development code signing certificate used for development environment
|
86
|
+
class Development < Certificate; end
|
87
|
+
|
88
|
+
# A production code signing certificate used for distribution environment
|
89
|
+
class Production < Certificate; end
|
90
|
+
|
91
|
+
# An In House code signing certificate used for enterprise distributions
|
92
|
+
class InHouse < Certificate; end
|
93
|
+
|
94
|
+
#####################################################
|
95
|
+
# Certs that are specific for one app
|
96
|
+
#####################################################
|
97
|
+
|
98
|
+
# Abstract class for push certificates. Check out the subclasses
|
99
|
+
# DevelopmentPush, ProductionPush, WebsitePush and VoipPush
|
100
|
+
class PushCertificate < Certificate; end
|
101
|
+
|
102
|
+
# A push notification certificate for development environment
|
103
|
+
class DevelopmentPush < PushCertificate; end
|
104
|
+
|
105
|
+
# A push notification certificate for production environment
|
106
|
+
class ProductionPush < PushCertificate; end
|
107
|
+
|
108
|
+
# A push notification certificate for websites
|
109
|
+
class WebsitePush < PushCertificate; end
|
110
|
+
|
111
|
+
# A push notification certificate for the VOIP environment
|
112
|
+
class VoipPush < PushCertificate; end
|
113
|
+
|
114
|
+
# Passbook certificate
|
115
|
+
class Passbook < Certificate; end
|
116
|
+
|
117
|
+
# ApplePay certificate
|
118
|
+
class ApplePay < Certificate; end
|
119
|
+
|
120
|
+
CERTIFICATE_TYPE_IDS = {
|
121
|
+
"5QPB9NHCEI" => Development,
|
122
|
+
"R58UK2EWSO" => Production,
|
123
|
+
"9RQEK7MSXA" => InHouse,
|
124
|
+
"LA30L5BJEU" => Certificate,
|
125
|
+
"BKLRAVXMGM" => DevelopmentPush,
|
126
|
+
"3BQKVH9I2X" => ProductionPush,
|
127
|
+
"Y3B2F3TYSI" => Passbook,
|
128
|
+
"3T2ZP62QW8" => WebsitePush,
|
129
|
+
"E5D663CMZW" => WebsitePush,
|
130
|
+
"4APLUP237T" => ApplePay
|
131
|
+
}
|
132
|
+
|
133
|
+
#class methods
|
134
|
+
class << self
|
135
|
+
# Create a new code signing request that can be used to
|
136
|
+
# generate a new certificate
|
137
|
+
# @example
|
138
|
+
# Create a new certificate signing request
|
139
|
+
# csr, pkey = Spaceship.certificate.create_certificate_signing_request
|
140
|
+
#
|
141
|
+
# # Use the signing request to create a new distribution certificate
|
142
|
+
# Spaceship.certificate.production.create!(csr: csr)
|
143
|
+
def create_certificate_signing_request
|
144
|
+
key = OpenSSL::PKey::RSA.new 2048
|
145
|
+
csr = OpenSSL::X509::Request.new
|
146
|
+
csr.version = 0
|
147
|
+
csr.subject = OpenSSL::X509::Name.new([
|
148
|
+
['CN', 'PEM', OpenSSL::ASN1::UTF8STRING]
|
149
|
+
])
|
150
|
+
csr.public_key = key.public_key
|
151
|
+
csr.sign(key, OpenSSL::Digest::SHA1.new)
|
152
|
+
return [csr, key]
|
153
|
+
end
|
154
|
+
|
155
|
+
# Create a new object based on a hash.
|
156
|
+
# This is used to create a new object based on the server response.
|
157
|
+
def factory(attrs)
|
158
|
+
# Example:
|
159
|
+
# => {"name"=>"iOS Distribution: SunApps GmbH",
|
160
|
+
# "certificateId"=>"XC5PH8DAAA",
|
161
|
+
# "serialNumber"=>"797E732CCE8B7AAA",
|
162
|
+
# "status"=>"Issued",
|
163
|
+
# "statusCode"=>0,
|
164
|
+
# "expirationDate"=>#<DateTime: 2015-11-25T22:45:50+00:00 ((2457352j,81950s,0n),+0s,2299161j)>,
|
165
|
+
# "certificatePlatform"=>"ios",
|
166
|
+
# "certificateType"=>
|
167
|
+
# {"certificateTypeDisplayId"=>"R58UK2EAAA",
|
168
|
+
# "name"=>"iOS Distribution",
|
169
|
+
# "platform"=>"ios",
|
170
|
+
# "permissionType"=>"distribution",
|
171
|
+
# "distributionType"=>"store",
|
172
|
+
# "distributionMethod"=>"app",
|
173
|
+
# "ownerType"=>"team",
|
174
|
+
# "daysOverlap"=>364,
|
175
|
+
# "maxActive"=>2}}
|
176
|
+
|
177
|
+
if attrs['certificateType']
|
178
|
+
# On some accounts this is nested, so we need to flatten it
|
179
|
+
attrs.merge!(attrs['certificateType'])
|
180
|
+
attrs.delete('certificateType')
|
181
|
+
end
|
182
|
+
|
183
|
+
# Parse the dates
|
184
|
+
attrs['expirationDate'] = (Time.parse(attrs['expirationDate']) rescue attrs['expirationDate'])
|
185
|
+
attrs['dateCreated'] = (Time.parse(attrs['dateCreated']) rescue attrs['dateCreated'])
|
186
|
+
|
187
|
+
# Here we go
|
188
|
+
klass = CERTIFICATE_TYPE_IDS[attrs['certificateTypeDisplayId']]
|
189
|
+
klass ||= Certificate
|
190
|
+
klass.client = @client
|
191
|
+
klass.new(attrs)
|
192
|
+
end
|
193
|
+
|
194
|
+
# @return (Array) Returns all certificates of this account.
|
195
|
+
# If this is called from a subclass of Certificate, this will
|
196
|
+
# only include certificates matching the current type.
|
197
|
+
def all
|
198
|
+
if (self == Certificate) # are we the base-class?
|
199
|
+
types = CERTIFICATE_TYPE_IDS.keys
|
200
|
+
else
|
201
|
+
types = [CERTIFICATE_TYPE_IDS.key(self)]
|
202
|
+
end
|
203
|
+
|
204
|
+
client.certificates(types).map do |cert|
|
205
|
+
factory(cert)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# @return (Certificate) Find a certificate based on the ID of the certificate.
|
210
|
+
def find(certificate_id)
|
211
|
+
all.find do |c|
|
212
|
+
c.id == certificate_id
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Generate a new certificate based on a code certificate signing request
|
217
|
+
# @param csr (OpenSSL::X509::Request) (required): The certificate signing request to use. Get one using
|
218
|
+
# `create_certificate_signing_request`
|
219
|
+
# @param bundle_id (String) (optional): The app identifier this certificate is for.
|
220
|
+
# This value is only needed if you create a push profile. For normal code signing
|
221
|
+
# certificates, you must only pass a certificate signing request.
|
222
|
+
# @example
|
223
|
+
# # Create a new certificate signing request
|
224
|
+
# csr, pkey = Spaceship::Certificate.create_certificate_signing_request
|
225
|
+
#
|
226
|
+
# # Use the signing request to create a new distribution certificate
|
227
|
+
# Spaceship::Certificate::Production.create!(csr: csr)
|
228
|
+
# @return (Device): The newly created device
|
229
|
+
def create!(csr: nil, bundle_id: nil)
|
230
|
+
type = CERTIFICATE_TYPE_IDS.key(self)
|
231
|
+
|
232
|
+
# look up the app_id by the bundle_id
|
233
|
+
if bundle_id
|
234
|
+
app = Spaceship::App.find(bundle_id)
|
235
|
+
raise "Could not find app with bundle id '#{bundle_id}'" unless app
|
236
|
+
app_id = app.app_id
|
237
|
+
end
|
238
|
+
|
239
|
+
# ensure csr is a OpenSSL::X509::Request
|
240
|
+
csr = OpenSSL::X509::Request.new(csr) if csr.is_a?(String)
|
241
|
+
|
242
|
+
# if this succeeds, we need to save the .cer and the private key in keychain access or wherever they go in linux
|
243
|
+
response = client.create_certificate!(type, csr.to_pem, app_id)
|
244
|
+
# munge the response to make it work for the factory
|
245
|
+
response['certificateTypeDisplayId'] = response['certificateType']['certificateTypeDisplayId']
|
246
|
+
self.new(response)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# instance methods
|
251
|
+
|
252
|
+
# @return (String) Download the raw data of the certificate without parsing
|
253
|
+
def download_raw
|
254
|
+
client.download_certificate(id, type_display_id)
|
255
|
+
end
|
256
|
+
|
257
|
+
# @return (OpenSSL::X509::Certificate) Downloads and parses the certificate
|
258
|
+
def download
|
259
|
+
OpenSSL::X509::Certificate.new(download_raw)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Revoke the certificate. You shouldn't use this method probably.
|
263
|
+
def revoke!
|
264
|
+
client.revoke_certificate!(id, type_display_id)
|
265
|
+
end
|
266
|
+
|
267
|
+
# @return (Bool): Is this certificate a push profile for apps?
|
268
|
+
def is_push?
|
269
|
+
self.kind_of?PushCertificate
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Portal
|
3
|
+
# Represents a device from the Apple Developer Portal
|
4
|
+
class Device < PortalBase
|
5
|
+
# @return (String) The ID given from the developer portal. You'll probably not need it.
|
6
|
+
# @example
|
7
|
+
# "XJXGVS46MW"
|
8
|
+
attr_accessor :id
|
9
|
+
|
10
|
+
# @return (String) The name of the device
|
11
|
+
# @example
|
12
|
+
# "Felix Krause's iPhone 6"
|
13
|
+
attr_accessor :name
|
14
|
+
|
15
|
+
# @return (String) The UDID of the device
|
16
|
+
# @example
|
17
|
+
# "4c24a7ee5caaa4847f49aaab2d87483053f53b65"
|
18
|
+
attr_accessor :udid
|
19
|
+
|
20
|
+
# @return (String) The platform of the device. This is probably always "ios"
|
21
|
+
# @example
|
22
|
+
# "ios"
|
23
|
+
attr_accessor :platform
|
24
|
+
|
25
|
+
# @return (String) Status of the device. Probably always "c"
|
26
|
+
# @example
|
27
|
+
# "c"
|
28
|
+
attr_accessor :status
|
29
|
+
|
30
|
+
attr_mapping({
|
31
|
+
'deviceId' => :id,
|
32
|
+
'name' => :name,
|
33
|
+
'deviceNumber' => :udid,
|
34
|
+
'devicePlatform' => :platform,
|
35
|
+
'status' => :status
|
36
|
+
})
|
37
|
+
|
38
|
+
class << self
|
39
|
+
# Create a new object based on a hash.
|
40
|
+
# This is used to create a new object based on the server response.
|
41
|
+
def factory(attrs)
|
42
|
+
self.new(attrs)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return (Array) Returns all devices registered for this account
|
46
|
+
def all
|
47
|
+
client.devices.map { |device| self.factory(device) }
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return (Device) Find a device based on the ID of the device. *Attention*:
|
51
|
+
# This is *not* the UDID. nil if no device was found.
|
52
|
+
def find(device_id)
|
53
|
+
all.find do |device|
|
54
|
+
device.id == device_id
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return (Device) Find a device based on the UDID of the device. nil if no device was found.
|
59
|
+
def find_by_udid(device_udid)
|
60
|
+
all.find do |device|
|
61
|
+
device.udid == device_udid
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return (Device) Find a device based on its name. nil if no device was found.
|
66
|
+
def find_by_name(device_name)
|
67
|
+
all.find do |device|
|
68
|
+
device.name == device_name
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Register a new device to this account
|
73
|
+
# @param name (String) (required): The name of the new device
|
74
|
+
# @param udid (String) (required): The UDID of the new device
|
75
|
+
# @example
|
76
|
+
# Spaceship.device.create!(name: "Felix Krause's iPhone 6", udid: "4c24a7ee5caaa4847f49aaab2d87483053f53b65")
|
77
|
+
# @return (Device): The newly created device
|
78
|
+
def create!(name: nil, udid: nil)
|
79
|
+
# Check whether the user has passed in a UDID and a name
|
80
|
+
unless (udid and name)
|
81
|
+
raise "You cannot create a device without a device_id (UDID) and name"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Find the device by UDID, raise an exception if it already exists
|
85
|
+
if self.find_by_udid(udid)
|
86
|
+
raise "The device UDID '#{udid}' already exists on this team."
|
87
|
+
end
|
88
|
+
|
89
|
+
# Find the device by name, raise an exception if it already exists
|
90
|
+
if self.find_by_name(name)
|
91
|
+
raise "The device name '#{name}' already exists on this team, use different one."
|
92
|
+
end
|
93
|
+
|
94
|
+
device = client.create_device!(name, udid)
|
95
|
+
|
96
|
+
# Update self with the new device
|
97
|
+
self.new(device)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
module Spaceship
|
2
|
+
class PortalClient < Spaceship::Client
|
3
|
+
|
4
|
+
#####################################################
|
5
|
+
# @!group Init and Login
|
6
|
+
#####################################################
|
7
|
+
|
8
|
+
def self.hostname
|
9
|
+
"https://developer.apple.com/services-account/#{PROTOCOL_VERSION}/"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Fetches the latest API Key from the Apple Dev Portal
|
13
|
+
def api_key
|
14
|
+
cache_path = "/tmp/spaceship_api_key.txt"
|
15
|
+
begin
|
16
|
+
cached = File.read(cache_path)
|
17
|
+
rescue Errno::ENOENT
|
18
|
+
end
|
19
|
+
return cached if cached
|
20
|
+
|
21
|
+
landing_url = "https://developer.apple.com/membercenter/index.action"
|
22
|
+
logger.info("GET: " + landing_url)
|
23
|
+
headers = @client.get(landing_url).headers
|
24
|
+
results = headers['location'].match(/.*appIdKey=(\h+)/)
|
25
|
+
if results.length > 1
|
26
|
+
api_key = results[1]
|
27
|
+
File.write(cache_path, api_key)
|
28
|
+
return api_key
|
29
|
+
else
|
30
|
+
raise "Could not find latest API Key from the Dev Portal"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_login_request(user, password)
|
35
|
+
response = request(:post, "https://idmsa.apple.com/IDMSWebAuth/authenticate", {
|
36
|
+
appleId: user,
|
37
|
+
accountPassword: password,
|
38
|
+
appIdKey: api_key
|
39
|
+
})
|
40
|
+
|
41
|
+
if response['Set-Cookie'] =~ /myacinfo=(\w+);/
|
42
|
+
@cookie = "myacinfo=#{$1};"
|
43
|
+
return @client
|
44
|
+
else
|
45
|
+
# User Credentials are wrong
|
46
|
+
raise InvalidUserCredentialsError.new(response)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return (Array) A list of all available teams
|
51
|
+
def teams
|
52
|
+
r = request(:post, 'account/listTeams.action')
|
53
|
+
parse_response(r, 'teams')
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return (String) The currently selected Team ID
|
57
|
+
def team_id
|
58
|
+
return @current_team_id if @current_team_id
|
59
|
+
|
60
|
+
if teams.count > 1
|
61
|
+
puts "The current user is in #{teams.count} teams. Pass a team ID or call `select_team` to choose a team. Using the first one for now."
|
62
|
+
end
|
63
|
+
@current_team_id ||= teams[0]['teamId']
|
64
|
+
end
|
65
|
+
|
66
|
+
# Shows a team selection for the user in the terminal. This should not be
|
67
|
+
# called on CI systems
|
68
|
+
def select_team
|
69
|
+
@current_team_id = self.UI.select_team
|
70
|
+
end
|
71
|
+
|
72
|
+
# Set a new team ID which will be used from now on
|
73
|
+
def team_id=(team_id)
|
74
|
+
@current_team_id = team_id
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return (Hash) Fetches all information of the currently used team
|
78
|
+
def team_information
|
79
|
+
teams.find do |t|
|
80
|
+
t['teamId'] == team_id
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Is the current session from an Enterprise In House account?
|
85
|
+
def in_house?
|
86
|
+
# TODO: move to portal
|
87
|
+
return @in_house unless @in_house.nil?
|
88
|
+
@in_house = (team_information['type'] == 'In-House')
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
#####################################################
|
93
|
+
# @!group Apps
|
94
|
+
#####################################################
|
95
|
+
|
96
|
+
def apps
|
97
|
+
paging do |page_number|
|
98
|
+
r = request(:post, 'account/ios/identifiers/listAppIds.action', {
|
99
|
+
teamId: team_id,
|
100
|
+
pageNumber: page_number,
|
101
|
+
pageSize: page_size,
|
102
|
+
sort: 'name=asc'
|
103
|
+
})
|
104
|
+
parse_response(r, 'appIds')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def details_for_app(app)
|
109
|
+
r = request(:post, 'account/ios/identifiers/getAppIdDetail.action', {
|
110
|
+
teamId: team_id,
|
111
|
+
appIdId: app.app_id
|
112
|
+
})
|
113
|
+
parse_response(r, 'appId')
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_app!(type, name, bundle_id)
|
117
|
+
ident_params = case type.to_sym
|
118
|
+
when :explicit
|
119
|
+
{
|
120
|
+
type: 'explicit',
|
121
|
+
explicitIdentifier: bundle_id,
|
122
|
+
appIdentifierString: bundle_id,
|
123
|
+
push: 'on',
|
124
|
+
inAppPurchase: 'on',
|
125
|
+
gameCenter: 'on'
|
126
|
+
}
|
127
|
+
when :wildcard
|
128
|
+
{
|
129
|
+
type: 'wildcard',
|
130
|
+
wildcardIdentifier: bundle_id,
|
131
|
+
appIdentifierString: bundle_id
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
params = {
|
136
|
+
appIdName: name,
|
137
|
+
teamId: team_id
|
138
|
+
}
|
139
|
+
|
140
|
+
params.merge!(ident_params)
|
141
|
+
|
142
|
+
r = request(:post, 'account/ios/identifiers/addAppId.action', params)
|
143
|
+
parse_response(r, 'appId')
|
144
|
+
end
|
145
|
+
|
146
|
+
def delete_app!(app_id)
|
147
|
+
r = request(:post, 'account/ios/identifiers/deleteAppId.action', {
|
148
|
+
teamId: team_id,
|
149
|
+
appIdId: app_id
|
150
|
+
})
|
151
|
+
parse_response(r)
|
152
|
+
end
|
153
|
+
|
154
|
+
#####################################################
|
155
|
+
# @!group Devices
|
156
|
+
#####################################################
|
157
|
+
|
158
|
+
def devices
|
159
|
+
paging do |page_number|
|
160
|
+
r = request(:post, 'account/ios/device/listDevices.action', {
|
161
|
+
teamId: team_id,
|
162
|
+
pageNumber: page_number,
|
163
|
+
pageSize: page_size,
|
164
|
+
sort: 'name=asc'
|
165
|
+
})
|
166
|
+
parse_response(r, 'devices')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def create_device!(device_name, device_id)
|
171
|
+
r = request(:post) do |r|
|
172
|
+
r.url "https://developerservices2.apple.com/services/#{PROTOCOL_VERSION}/ios/addDevice.action"
|
173
|
+
r.params = {
|
174
|
+
teamId: team_id,
|
175
|
+
deviceNumber: device_id,
|
176
|
+
name: device_name
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
parse_response(r, 'device')
|
181
|
+
end
|
182
|
+
|
183
|
+
#####################################################
|
184
|
+
# @!group Certificates
|
185
|
+
#####################################################
|
186
|
+
|
187
|
+
def certificates(types)
|
188
|
+
paging do |page_number|
|
189
|
+
r = request(:post, 'account/ios/certificate/listCertRequests.action', {
|
190
|
+
teamId: team_id,
|
191
|
+
types: types.join(','),
|
192
|
+
pageNumber: page_number,
|
193
|
+
pageSize: page_size,
|
194
|
+
sort: 'certRequestStatusCode=asc'
|
195
|
+
})
|
196
|
+
parse_response(r, 'certRequests')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def create_certificate!(type, csr, app_id = nil)
|
201
|
+
r = request(:post, 'account/ios/certificate/submitCertificateRequest.action', {
|
202
|
+
teamId: team_id,
|
203
|
+
type: type,
|
204
|
+
csrContent: csr,
|
205
|
+
appIdId: app_id #optional
|
206
|
+
})
|
207
|
+
parse_response(r, 'certRequest')
|
208
|
+
end
|
209
|
+
|
210
|
+
def download_certificate(certificate_id, type)
|
211
|
+
{type: type, certificate_id: certificate_id}.each { |k, v| raise "#{k} must not be nil" if v.nil? }
|
212
|
+
|
213
|
+
r = request(:post, 'https://developer.apple.com/account/ios/certificate/certificateContentDownload.action', {
|
214
|
+
teamId: team_id,
|
215
|
+
displayId: certificate_id,
|
216
|
+
type: type
|
217
|
+
})
|
218
|
+
parse_response(r)
|
219
|
+
end
|
220
|
+
|
221
|
+
def revoke_certificate!(certificate_id, type)
|
222
|
+
r = request(:post, 'account/ios/certificate/revokeCertificate.action', {
|
223
|
+
teamId: team_id,
|
224
|
+
certificateId: certificate_id,
|
225
|
+
type: type
|
226
|
+
})
|
227
|
+
parse_response(r, 'certRequests')
|
228
|
+
end
|
229
|
+
|
230
|
+
#####################################################
|
231
|
+
# @!group Provisioning Profiles
|
232
|
+
#####################################################
|
233
|
+
|
234
|
+
def provisioning_profiles
|
235
|
+
r = request(:post) do |r|
|
236
|
+
r.url "https://developerservices2.apple.com/services/#{PROTOCOL_VERSION}/ios/listProvisioningProfiles.action"
|
237
|
+
r.params = {
|
238
|
+
teamId: team_id,
|
239
|
+
includeInactiveProfiles: true,
|
240
|
+
onlyCountLists: true,
|
241
|
+
}
|
242
|
+
end
|
243
|
+
|
244
|
+
parse_response(r, 'provisioningProfiles')
|
245
|
+
end
|
246
|
+
|
247
|
+
def create_provisioning_profile!(name, distribution_method, app_id, certificate_ids, device_ids)
|
248
|
+
r = request(:post, 'account/ios/profile/createProvisioningProfile.action', {
|
249
|
+
teamId: team_id,
|
250
|
+
provisioningProfileName: name,
|
251
|
+
appIdId: app_id,
|
252
|
+
distributionType: distribution_method,
|
253
|
+
certificateIds: certificate_ids,
|
254
|
+
deviceIds: device_ids
|
255
|
+
})
|
256
|
+
parse_response(r, 'provisioningProfile')
|
257
|
+
end
|
258
|
+
|
259
|
+
def download_provisioning_profile(profile_id)
|
260
|
+
r = request(:get, 'https://developer.apple.com/account/ios/profile/profileContentDownload.action', {
|
261
|
+
teamId: team_id,
|
262
|
+
displayId: profile_id
|
263
|
+
})
|
264
|
+
parse_response(r)
|
265
|
+
end
|
266
|
+
|
267
|
+
def delete_provisioning_profile!(profile_id)
|
268
|
+
r = request(:post, 'account/ios/profile/deleteProvisioningProfile.action', {
|
269
|
+
teamId: team_id,
|
270
|
+
provisioningProfileId: profile_id
|
271
|
+
})
|
272
|
+
parse_response(r)
|
273
|
+
end
|
274
|
+
|
275
|
+
def repair_provisioning_profile!(profile_id, name, distribution_method, app_id, certificate_ids, device_ids)
|
276
|
+
r = request(:post, 'account/ios/profile/regenProvisioningProfile.action', {
|
277
|
+
teamId: team_id,
|
278
|
+
provisioningProfileId: profile_id,
|
279
|
+
provisioningProfileName: name,
|
280
|
+
appIdId: app_id,
|
281
|
+
distributionType: distribution_method,
|
282
|
+
certificateIds: certificate_ids.first, # we are most of the times only allowed to pass one
|
283
|
+
deviceIds: device_ids
|
284
|
+
})
|
285
|
+
|
286
|
+
parse_response(r, 'provisioningProfile')
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|