class Cupertino::ProvisioningPortal::Agent

Attributes

password[RW]
team[RW]
team_id[RW]
username[RW]

Public Class Methods

new() click to toggle source
Calls superclass method
# File lib/cupertino/provisioning_portal/agent.rb, line 14
def initialize
  super
  @profile_csrf_headers = {}
  self.user_agent_alias = 'Mac Safari'

  self.log ||= Logger.new(STDOUT)
  self.log.level = Logger::ERROR

  if ENV['HTTP_PROXY']
    uri = URI.parse(ENV['HTTP_PROXY'])
    user = ENV['HTTP_PROXY_USER'] if ENV['HTTP_PROXY_USER']
    password = ENV['HTTP_PROXY_PASSWORD'] if ENV['HTTP_PROXY_PASSWORD']

    set_proxy(uri.host, uri.port, user || uri.user, password || uri.password)
  end

  if ENV['IOS_USERNAME'] and ENV['IOS_PASSWORD']
    @username, @password = ENV['IOS_USERNAME'], ENV['IOS_PASSWORD']
  else
    pw = Security::InternetPassword.find(:server => Cupertino::ProvisioningPortal::HOST)
    @username, @password = pw.attributes['acct'], pw.password if pw
  end
end

Public Instance Methods

add_app_id(app_id) click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 307
def add_app_id(app_id)
  get("https://developer.apple.com/account/ios/identifiers/bundle/bundleCreate.action")

  form = page.form_with(:name => 'bundleSave') or raise UnexpectedContentError
  form.method = 'POST'
  form.action = "https://developer.apple.com/account/ios/identifiers/bundle/bundleConfirm.action"
  form.field_with(:name => "appIdName").value = app_id.description
  form.field_with(:name => "explicitIdentifier").value = app_id.identifier
  form.checkbox_with(:name => "push").check()
  form.add_field!("appIdentifierString", app_id.identifier)
  form.add_field!("formID", "#{rand(10000000)}")
  form.add_field!("clientToken", "undefined")
  form.submit

  if form = page.form_with(:name => 'bundleSubmit')
    form.method = 'POST'

    adssuv = cookies.find{|cookie| cookie.name == 'adssuv'}
    form.add_field!("adssuv-value", Mechanize::Util::uri_unescape(adssuv.value))

    form.add_field!("inAppPurchase", "on")
    form.add_field!("gameCenter", "on")
    form.add_field!("push", "on")

    form.add_field!("explicitIdentifier", app_id.identifier)
    form.add_field!("appIdentifierString", app_id.identifier)
    form.add_field!("appIdName", app_id.description)
    form.add_field!("type", "explicit")

    form.submit
  elsif form = page.form_with(:name => 'bundleSave')
    form.submit
  else
    raise UnexpectedContentError
  end
end
add_devices(*devices) click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 144
def add_devices(*devices)
  return if devices.empty?

  get('https://developer.apple.com/account/ios/device/deviceCreate.action')

  begin
    file = Tempfile.new(%w(devices .txt))
    file.write("Device ID\tDevice Name")
    devices.each do |device|
      file.write("\n#{device.udid}\t#{device.name}")
    end
    file.rewind

    form = page.form_with(:name => 'deviceImport') or raise UnexpectedContentError

    upload = form.file_uploads.first
    upload.file_name = file.path
    form.radiobuttons.first.check()
    form.submit

    if form = page.form_with(:name => 'deviceSubmit')
      form.method = 'POST'
      form.field_with(:name => 'deviceNames').name = 'name'
      form.field_with(:name => 'deviceNumbers').name = 'deviceNumber'
      form.submit
    elsif form = page.form_with(:name => 'deviceImport')
      form.submit
    else
      raise UnexpectedContentError
    end

  ensure
    file.close!
  end
end
download_certificate(certificate) click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 110
def download_certificate(certificate)
  list_certificates(certificate.type)

  self.pluggable_parser.default = Mechanize::Download
  download = post(certificate.download_url)
  download.save
  download.filename
end
download_profile(profile) click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 235
def download_profile(profile)
  self.pluggable_parser.default = Mechanize::Download
  download = get(profile.download_url)
  download.save
  download.filename
end
get(uri, parameters = [], referer = nil, headers = {}) click to toggle source
Calls superclass method
# File lib/cupertino/provisioning_portal/agent.rb, line 47
def get(uri, parameters = [], referer = nil, headers = {})
  uri = ::File.join("https://#{Cupertino::ProvisioningPortal::HOST}", uri) unless /^https?/ === uri

  3.times do
    super(uri, parameters, referer, headers)

    return page unless page.respond_to?(:title)

    case page.title
    when /Sign in with your Apple ID/
      login!
    when /Select Team/
      select_team!
    else
      return page
    end
  end

  raise UnsuccessfulAuthenticationError
end
list_app_ids() click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 344
def list_app_ids
  get('https://developer.apple.com/account/ios/identifiers/bundle/bundleList.action')

  regex = /bundleDataURL = "([^"]*)"/
  bundle_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
  bundle_data_url += "&pageSize=50&pageNumber=1&sort=name=asc"

  post(bundle_data_url)
  bundle_data = page.content
  parsed_bundle_data = JSON.parse(bundle_data)

  app_ids = []
  parsed_bundle_data['appIds'].each do |row|
    app_id = AppID.new
    app_id.bundle_seed_id = [row['prefix'], row['identifier']].join(".")
    app_id.description = row['name']
    app_id.identifier = row['identifier']

    app_id.development_properties, app_id.distribution_properties = [], []
    row['features'].each do |feature, value|
      if value == true
        app_id.development_properties << feature
      elsif value.kind_of?(String) && !value.empty?
        app_id.development_properties << "#{feature}: #{value}"
      end
    end

    row['enabledFeatures'].each do |feature|
      app_id.distribution_properties << feature
    end

    app_ids << app_id
  end

  app_ids
end
list_certificates(type = :development) click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 68
def list_certificates(type = :development)
  url = case type
        when :development
          "https://developer.apple.com/account/ios/certificate/certificateList.action?type=development"
        when :distribution
          "https://developer.apple.com/account/ios/certificate/certificateList.action?type=distribution"
        else
          raise ArgumentError, "Certificate type must be :development or :distribution"
        end

  get(url)

  regex = /certificateDataURL = "([^"]*)"/
  certificate_data_url = (page.body.match regex or raise UnexpectedContentError)[1]

  regex = /certificateRequestTypes = "([^"]*)"/
  certificate_request_types = (page.body.match regex or raise UnexpectedContentError)[1]

  regex = /certificateStatuses = "([^"]*)"/
  certificate_statuses = (page.body.match regex or raise UnexpectedContentError)[1]

  certificate_data_url += certificate_request_types + certificate_statuses
  certificate_data_url += "&pageSize=50&pageNumber=1&sort=name=asc"

  post(certificate_data_url)
  certificate_data = page.content
  parsed_certificate_data = JSON.parse(certificate_data)

  certificates = []
  parsed_certificate_data['certRequests'].each do |row|
    certificate = Certificate.new
    certificate.name = row['name']
    certificate.type = type
    certificate.download_url = "https://developer.apple.com/account/ios/certificate/certificateContentDownload.action?displayId=#{row['certificateId']}&type=#{row['certificateTypeDisplayId']}"
    certificate.expiration = (Date.parse(row['expirationDateString']) rescue nil)
    certificate.status = row['statusString']
    certificates << certificate
  end

  certificates
end
list_devices() click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 119
def list_devices
  get('https://developer.apple.com/account/ios/device/deviceList.action')

  regex = /deviceDataURL = "([^"]*)"/
  device_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
  device_data_url += "&pageSize=500&pageNumber=1&sort=name=asc"

  post(device_data_url)

  device_data = page.content
  parsed_device_data = JSON.parse(device_data)

  devices = []
  parsed_device_data['devices'].each do |row|
    device = Device.new
    device.name = row['name']
    device.enabled = (row['status'] == 'c' ? 'Y' : 'N')
    device.device_id = row['deviceId']
    device.udid = row['deviceNumber']
    devices << device
  end

  devices
end
list_devices_for_profile(profile) click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 283
def list_devices_for_profile(profile)
  devices = list_devices

  begin
    get(profile.edit_url)
  rescue Mechanize::ResponseCodeError
    say_error "Cannot manage devices for #{profile}" and abort
  end

  on, off = [], []
  page.search('dd.selectDevices div.rows div').each do |row|
    checkbox = row.search('input[type="checkbox"]').first
    device = devices.detect{|d| d.device_id == checkbox['value']}

    if checkbox['checked']
      on << device
    else
      off << device
    end
  end

  { :on => on, :off => off }
end
list_profiles(type = :development) click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 180
def list_profiles(type = :development)
  url = case type
        when :development
          'https://developer.apple.com/account/ios/profile/profileList.action?type=limited'
        when :distribution
          'https://developer.apple.com/account/ios/profile/profileList.action?type=production'
        else
          raise ArgumentError, 'Provisioning profile type must be :development or :distribution'
        end

  self.pluggable_parser.default = Mechanize::File
  get(url)

  regex = /profileDataURL = "([^"]*)"/
  profile_data_url = (page.body.match regex or raise UnexpectedContentError)[1]

  profile_data_url += case type
                      when :development
                        '&type=limited'
                      when :distribution
                        '&type=production'
                      end

  profile_data_url += "&pageSize=500&pageNumber=1&sort=name=asc"

  post(profile_data_url)
  @profile_csrf_headers = {
    'csrf' => page.response['csrf'],
    'csrf_ts' => page.response['csrf_ts']
  }

  @profile_csrf_headers = {
    'csrf' => page.response['csrf'],
    'csrf_ts' => page.response['csrf_ts']
  }

  profile_data = page.content
  parsed_profile_data = JSON.parse(profile_data)

  profiles = []
  parsed_profile_data['provisioningProfiles'].each do |row|
    profile = ProvisioningProfile.new
    profile.name = row['name']
    profile.type = type
    profile.status = row['status']
    profile.expiration = (Time.parse(row['dateExpire']) rescue nil)
    profile.download_url = "https://developer.apple.com/account/ios/profile/profileContentDownload.action?displayId=#{row['provisioningProfileId']}"
    profile.edit_url = "https://developer.apple.com/account/ios/profile/profileEdit.action?provisioningProfileId=#{row['provisioningProfileId']}"
    profile.identifier = row['UUID']
    profiles << profile
  end

  profiles
end
manage_devices_for_profile(profile) { |on, off| ... } click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 242
def manage_devices_for_profile(profile)
  raise ArgumentError unless block_given?

  devices = list_devices

  begin
    get(profile.edit_url)
  rescue Mechanize::ResponseCodeError
    say_error "Cannot manage devices for #{profile}" and abort
  end

  on, off = [], []
  page.search('dd.selectDevices div.rows div').each do |row|
    checkbox = row.search('input[type="checkbox"]').first
    device = devices.detect{|d| d.device_id == checkbox['value']}

    if checkbox['checked']
      on << device
    else
      off << device
    end
  end

  devices = yield on, off

  form = page.form_with(:name => 'profileEdit') or raise UnexpectedContentError
  form.checkboxes_with(:name => 'deviceIds').each do |checkbox|
    if devices.detect{|device| device.device_id == checkbox['value']}
      checkbox.check
    else
      checkbox.uncheck
    end
  end

  adssuv = cookies.find{|cookie| cookie.name == 'adssuv'}
  form.add_field!('adssuv-value', Mechanize::Util::uri_unescape(adssuv.value))

  form.method = 'POST'
  form.submit(nil, @profile_csrf_headers)
end
username=(value) click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 38
def username=(value)
  @username = value

  if not(@password and ENV['IOS_PASSWORD'])
    pw = Security::InternetPassword.find(:a => self.username, :server => Cupertino::ProvisioningPortal::HOST)
    @password = pw.password if pw
  end
end

Private Instance Methods

login!() click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 383
def login!
  if form = page.forms.first
    form.fields_with(type: 'text').first.value = self.username
    form.fields_with(type: 'password').first.value = self.password

    form.submit
  end
end
select_team!() click to toggle source
# File lib/cupertino/provisioning_portal/agent.rb, line 392
def select_team!
  if form = page.form_with(:name => 'saveTeamSelection')
    team_option = form.radiobutton_with(:value => self.team_id)
    team_option.check

    button = form.button_with(:name => 'action:saveTeamSelection!save')
    form.click_button(button)
  end
end