Class: Spaceship::Portal::Certificate

Inherits:
Spaceship::PortalBase show all
Defined in:
spaceship/lib/spaceship/portal/certificate.rb

Overview

Represents a certificate from the Apple Developer Portal.

This can either be a code signing identity or a push profile

Defined Under Namespace

Classes: AppleDevelopment, AppleDistribution, ApplePay, ApplePayMerchantIdentity, DeveloperIdApplication, DeveloperIdInstaller, Development, DevelopmentPush, InHouse, MacAppDistribution, MacDevelopment, MacDevelopmentPush, MacInstallerDistribution, MacProductionPush, Passbook, Production, ProductionPush, PushCertificate, VoipPush, WebsitePush

Constant Summary collapse

APPLE_CERTIFICATE_TYPE_IDS =
{
  "83Q87W3TGH" => AppleDevelopment,
  "WXV89964HE" => AppleDistribution
}
IOS_CERTIFICATE_TYPE_IDS =
{
  "5QPB9NHCEI" => Development,
  "R58UK2EWSO" => Production,
  "9RQEK7MSXA" => InHouse,
  "LA30L5BJEU" => Certificate,
  "BKLRAVXMGM" => DevelopmentPush,
  "UPV3DW712I" => ProductionPush,
  "Y3B2F3TYSI" => Passbook,
  "3T2ZP62QW8" => WebsitePush,
  "E5D663CMZW" => VoipPush,
  "4APLUP237T" => ApplePay,
  "MD8Q2VRT6A" => ApplePayMerchantIdentity
}
OLDER_IOS_CERTIFICATE_TYPES =
[
  # those are also sent by the browser, but not sure what they represent
  "T44PTHVNID",
  "DZQUP8189Y",
  "FGQUP4785Z",
  "S5WE21TULA",
  "3BQKVH9I2X", # ProductionPush,
  "FUOY7LWJET"
]
MAC_CERTIFICATE_TYPE_IDS =
{
  "749Y1QAGU7" => MacDevelopment,
  "HXZEUKP0FP" => MacAppDistribution,
  "2PQI8IDXNH" => MacInstallerDistribution,
  "OYVN2GW35E" => DeveloperIdInstaller,
  "W0EURJRMC5" => DeveloperIdApplication,
  "CDZ7EMXIZ1" => MacProductionPush,
  "HQ4KP3I34R" => MacDevelopmentPush,
  "DIVN2GW3XT" => DeveloperIdApplication
}
CERTIFICATE_TYPE_IDS =
APPLE_CERTIFICATE_TYPE_IDS
.merge(IOS_CERTIFICATE_TYPE_IDS)
.merge(MAC_CERTIFICATE_TYPE_IDS)

Instance Attribute Summary collapse

Attributes inherited from Base

#client, #raw_data

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Spaceship::PortalBase

client

Methods inherited from Base

attr_accessor, attr_mapping, attributes, #attributes, #initialize, #inspect, mapping_module, method_missing, set_client, #setup, #to_s

Constructor Details

This class inherits a constructor from Spaceship::Base

Instance Attribute Details

#can_downloadBool

Returns Whether or not the certificate can be downloaded.

Returns:

  • (Bool)

    Whether or not the certificate can be downloaded


73
74
75
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 73

def can_download
  @can_download
end

#createdDate

Returns The date and time when the certificate was created.

Examples:

2015-04-01 21:24:00 UTC

Returns:

  • (Date)

    The date and time when the certificate was created


32
33
34
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 32

def created
  @created
end

#expiresDate

Returns The date and time when the certificate will expire.

Examples:

2016-04-01 21:24:00 UTC

Returns:

  • (Date)

    The date and time when the certificate will expire


37
38
39
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 37

def expires
  @expires
end

#idString

Returns The ID given from the developer portal. You'll probably not need it.

Examples:

"P577TH3PAA"

Returns:

  • (String)

    The ID given from the developer portal. You'll probably not need it.


15
16
17
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 15

def id
  @id
end

#nameString

Returns The name of the certificate.

Examples:

Company

"SunApps GmbH"

Push Profile

"Apple Push Services"

Returns:

  • (String)

    The name of the certificate


22
23
24
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 22

def name
  @name
end

#owner_idString

Returns The ID of the owner, that can be used to fetch more information.

Examples:

"75B83SPLAA"

Returns:

  • (String)

    The ID of the owner, that can be used to fetch more information


60
61
62
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 60

def owner_id
  @owner_id
end

#owner_nameString

Returns The name of the owner.

Examples:

Code Signing Identity (usually the company name)

"SunApps Gmbh"

Push Certificate (the bundle identifier)

"tools.fastlane.app"

Returns:

  • (String)

    The name of the owner


54
55
56
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 54

def owner_name
  @owner_name
end

#owner_typeString

Returns The owner type that defines if it's a push profile or a code signing identity.

Examples:

Code Signing Identity

"team"

Push Certificate

"bundle"

Returns:

  • (String)

    The owner type that defines if it's a push profile or a code signing identity


46
47
48
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 46

def owner_type
  @owner_type
end

#statusString

Returns Status of the certificate.

Examples:

"Issued"

Returns:

  • (String)

    Status of the certificate


27
28
29
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 27

def status
  @status
end

#type_display_idString

Indicates the type of this certificate which is automatically used to determine the class of the certificate. Available values listed in CERTIFICATE_TYPE_IDS

Examples:

Production Certificate

"R58UK2EWSO"

Development Certificate

"5QPB9NHCEI"

Returns:

  • (String)

    The type of the certificate


70
71
72
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 70

def type_display_id
  @type_display_id
end

Class Method Details

.all(mac: false) ⇒ Array

Returns all certificates of this account. If this is called from a subclass of Certificate, this will only include certificates matching the current type.

Parameters:

  • mac (Bool) (defaults to: false)

    Fetches Mac certificates if true. (Ignored if called from a subclass)

Returns:

  • (Array)

    Returns all certificates of this account. If this is called from a subclass of Certificate, this will only include certificates matching the current type.


272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 272

def all(mac: false)
  if self == Certificate # are we the base-class?
    type_ids = mac ? MAC_CERTIFICATE_TYPE_IDS : IOS_CERTIFICATE_TYPE_IDS
    type_ids = APPLE_CERTIFICATE_TYPE_IDS.merge(type_ids)
    types = type_ids.keys
    types += OLDER_IOS_CERTIFICATE_TYPES unless mac
  else
    types = [CERTIFICATE_TYPE_IDS.key(self)]
    mac = MAC_CERTIFICATE_TYPE_IDS.values.include?(self)
  end

  client.certificates(types, mac: mac).map do |cert|
    factory(cert)
  end
end

.create!(csr: nil, bundle_id: nil) ⇒ Certificate

Generate a new certificate based on a code certificate signing request

Examples:

# Create a new certificate signing request
csr, pkey = Spaceship::Certificate.create_certificate_signing_request

# Use the signing request to create a new distribution certificate
Spaceship::Certificate::Production.create!(csr: csr)

Parameters:

  • csr (OpenSSL::X509::Request) (defaults to: nil)

    (required): The certificate signing request to use. Get one using `create_certificate_signing_request`

  • bundle_id (String) (defaults to: nil)

    (optional): The app identifier this certificate is for. This value is only needed if you create a push profile. For normal code signing certificates, you must only pass a certificate signing request.

Returns:


309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 309

def create!(csr: nil, bundle_id: nil)
  type = CERTIFICATE_TYPE_IDS.key(self)
  mac = MAC_CERTIFICATE_TYPE_IDS.include?(type)

  # look up the app_id by the bundle_id
  if bundle_id
    app = portal_type.set_client(client).find(bundle_id)
    raise "Could not find app with bundle id '#{bundle_id}'" unless app
    app_id = app.app_id
  end

  # ensure csr is a OpenSSL::X509::Request
  csr = OpenSSL::X509::Request.new(csr) if csr.kind_of?(String)

  # if this succeeds, we need to save the .cer and the private key in keychain access or wherever they go in linux
  response = client.create_certificate!(type, csr.to_pem, app_id, mac)
  # munge the response to make it work for the factory
  response['certificateTypeDisplayId'] = response['certificateType']['certificateTypeDisplayId']
  self.new(response)
end

.create_certificate_signing_requestObject

Create a new code signing request that can be used to generate a new certificate

Examples:

Create a new certificate signing request
csr, pkey = Spaceship.certificate.create_certificate_signing_request

# Use the signing request to create a new distribution certificate
Spaceship.certificate.production.create!(csr: csr)

215
216
217
218
219
220
221
222
223
224
225
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 215

def create_certificate_signing_request
  key = OpenSSL::PKey::RSA.new(2048)
  csr = OpenSSL::X509::Request.new
  csr.version = 0
  csr.subject = OpenSSL::X509::Name.new([
                                          ['CN', 'PEM', OpenSSL::ASN1::UTF8STRING]
                                        ])
  csr.public_key = key.public_key
  csr.sign(key, OpenSSL::Digest::SHA1.new)
  return [csr, key]
end

.factory(attrs) ⇒ Object

Create a new object based on a hash. This is used to create a new object based on the server response.


229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 229

def factory(attrs)
  # Example:
  # => {"name"=>"iOS Distribution: SunApps GmbH",
  #  "certificateId"=>"XC5PH8DAAA",
  #  "serialNumber"=>"797E732CCE8B7AAA",
  #  "status"=>"Issued",
  #  "statusCode"=>0,
  #  "expirationDate"=>#<DateTime: 2015-11-25T22:45:50+00:00 ((2457352j,81950s,0n),+0s,2299161j)>,
  #  "certificatePlatform"=>"ios",
  #  "certificateType"=>
  #   {"certificateTypeDisplayId"=>"R58UK2EAAA",
  #    "name"=>"iOS Distribution",
  #    "platform"=>"ios",
  #    "permissionType"=>"distribution",
  #    "distributionType"=>"store",
  #    "distributionMethod"=>"app",
  #    "ownerType"=>"team",
  #    "daysOverlap"=>364,
  #    "maxActive"=>2}}

  if attrs['certificateType']
    # On some accounts this is nested, so we need to flatten it
    attrs.merge!(attrs['certificateType'])
    attrs.delete('certificateType')
  end

  # Parse the dates
  # rubocop:disable Style/RescueModifier
  attrs['expirationDate'] = (Time.parse(attrs['expirationDate']) rescue attrs['expirationDate'])
  attrs['dateCreated'] = (Time.parse(attrs['dateCreated']) rescue attrs['dateCreated'])
  # rubocop:enable Style/RescueModifier

  # Here we go
  klass = CERTIFICATE_TYPE_IDS[attrs['certificateTypeDisplayId']]
  klass ||= Certificate
  klass.client = @client
  klass.new(attrs)
end

.find(certificate_id, mac: false) ⇒ Certificate

Returns Find a certificate based on the ID of the certificate.

Parameters:

  • mac (Bool) (defaults to: false)

    Searches Mac certificates if true

Returns:

  • (Certificate)

    Find a certificate based on the ID of the certificate.


290
291
292
293
294
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 290

def find(certificate_id, mac: false)
  all(mac: mac).find do |c|
    c.id == certificate_id
  end
end

.portal_typeClass

Default portal class to use when finding by bundle_id

Returns:

  • (Class)

    : The class this type of certificate belongs to


332
333
334
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 332

def portal_type
  Spaceship::Portal::App
end

Instance Method Details

#downloadOpenSSL::X509::Certificate

Returns Downloads and parses the certificate.

Returns:

  • (OpenSSL::X509::Certificate)

    Downloads and parses the certificate


345
346
347
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 345

def download
  OpenSSL::X509::Certificate.new(download_raw)
end

#download_rawString

Returns Download the raw data of the certificate without parsing.

Returns:

  • (String)

    Download the raw data of the certificate without parsing


340
341
342
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 340

def download_raw
  client.download_certificate(id, type_display_id, mac: mac?)
end

#is_push?Bool

Returns : Is this certificate a push profile for apps?.

Returns:

  • (Bool)

    : Is this certificate a push profile for apps?


355
356
357
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 355

def is_push?
  self.kind_of?(PushCertificate)
end

#mac?Bool

Returns Is this a Mac profile?.

Returns:

  • (Bool)

    Is this a Mac profile?


360
361
362
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 360

def mac?
  MAC_CERTIFICATE_TYPE_IDS.include?(type_display_id)
end

#revoke!Object

Revoke the certificate. You shouldn't use this method probably.


350
351
352
# File 'spaceship/lib/spaceship/portal/certificate.rb', line 350

def revoke!
  client.revoke_certificate!(id, type_display_id, mac: mac?)
end