cupertino 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cupertino (0.6.1)
4
+ cupertino (0.7.0)
5
5
  certified (>= 0.1.0)
6
6
  commander (~> 4.1.2)
7
7
  mechanize (~> 2.5.1)
data/README.md CHANGED
@@ -94,6 +94,58 @@ $ ios certificates:list
94
94
  +------------------+----------------------------------+-----------------+--------+
95
95
  ```
96
96
 
97
+ ### Pass Type IDs
98
+
99
+ ```sh
100
+ $ ios pass_type_ids:add pass.com.example.coupon.myExamplePass --description "My Example Pass Coupon"
101
+
102
+ Added pass.com.example.coupon.myExamplePass: My Example Pass Coupon
103
+ ```
104
+
105
+ ---
106
+
107
+ ```sh
108
+ $ ios pass_type_ids:list
109
+
110
+ +------------+--------------------------------------------+--------------+-------------------+
111
+ | Card ID | Identifier | Description | Pass Certificates |
112
+ +------------+--------------------------------------------+--------------+-------------------+
113
+ | WWWWWWWWWW | pass.com.example.coupon.myExamplePass | Coupon | None |
114
+ | XXXXXXXXXX | pass.com.example.eventTicket.myExamplePass | Event Ticket | Pass Certificate |
115
+ | YYYYYYYYYY | pass.com.example.movieTicket.myExamplePass | Movie Ticket | Pass Certificate |
116
+ | ZZZZZZZZZZ | pass.com.example.test.001 | Test | Pass Certificate |
117
+ +------------+--------------------------------------------+--------------+-------------------+
118
+ ```
119
+
120
+ ---
121
+
122
+ ```sh
123
+ $ ios pass_type_ids:certificates:add pass.com.example.coupon.myExamplePass --csr /path/to/csr
124
+
125
+ Configured pass.com.example.coupon.myExamplePass. Apple is generating the certificate...
126
+ Certificate generated and is ready to be downloaded.
127
+ ```
128
+
129
+ ---
130
+
131
+ ```sh
132
+ $ ios pass_type_ids:certificates:list pass.com.example.coupon.myExamplePass
133
+
134
+ +------------------+------------+-----------------+----------------+
135
+ | Name | Status | Expiration Date | Certificate ID |
136
+ +------------------+------------+-----------------+----------------+
137
+ | Pass Certificate | Configured | Nov 21, 2013 | AAAAAAAAAA |
138
+ +------------------+------------+-----------------+----------------+
139
+ ```
140
+
141
+ ---
142
+
143
+ ```sh
144
+ $ ios pass_type_ids:certificates:download pass.com.example.coupon.myExamplePass --certificate_id AAAAAAAAAA
145
+
146
+ Successfully downloaded: 'AAAAAAAAAA.cer'
147
+ ```
148
+
97
149
  ## Commands
98
150
 
99
151
  _Crossed out commands are not yet implemented_
@@ -118,6 +170,14 @@ _Crossed out commands are not yet implemented_
118
170
  - `app_ids:list`
119
171
  - ~~`app_ids:new`~~
120
172
 
173
+ - `pass_type_ids:list`
174
+ - `pass_type_ids:add`
175
+ - ~~`pass_type_ids:remove`~~
176
+ - `pass_type_ids:certificates:list`
177
+ - `pass_type_ids:certificates:add`
178
+ - `pass_type_ids:certificates:download`
179
+ - ~~`pass_type_ids:certificates:revoke CERTIFICATE_NAME`~~
180
+
121
181
  ## Contact
122
182
 
123
183
  Mattt Thompson
@@ -1,5 +1,6 @@
1
1
  require 'mechanize'
2
2
  require 'security'
3
+ require 'json'
3
4
 
4
5
  module Cupertino
5
6
  module ProvisioningPortal
@@ -243,7 +244,82 @@ module Cupertino
243
244
  end
244
245
  app_ids
245
246
  end
246
-
247
+
248
+ def list_pass_type_ids
249
+ get("https://developer.apple.com/ios/manage/passtypeids/index.action")
250
+
251
+ pass_type_ids = []
252
+ page.parser.xpath('//fieldset[@id="fs-0"]/table/tbody/tr').each do |row|
253
+ pass_type_id = PassTypeID.new
254
+ pass_type_id.card_id = row.at_xpath('td[@class="checkbox"]/input[@name="selectedValues"]')['value'].to_s.strip rescue nil
255
+ pass_type_id.id = row.at_xpath('td[@class="name"]/strong/text()').to_s.strip rescue nil
256
+ pass_type_id.description = row.at_xpath('td[@class="name"]/text()').to_s.strip rescue nil
257
+ pass_type_id.pass_certificates = row.at_xpath('td[@class="profile"]').inner_text.strip rescue nil
258
+
259
+ pass_type_ids << pass_type_id
260
+ end
261
+ pass_type_ids
262
+ end
263
+
264
+ def list_pass_certificates(pass_type_id)
265
+ pass_type_id = list_pass_type_ids().delete_if{ |item| item.id != pass_type_id }.shift rescue nil
266
+ return [] if pass_type_id.nil?
267
+
268
+ get("https://developer.apple.com/ios/manage/passtypeids/configure.action?displayId=#{pass_type_id.card_id}")
269
+
270
+ pass_certificates = []
271
+ page.parser.xpath('//form[@name="form_logginMemberCert"]/table/tr[position()>1]').each do |row|
272
+ pass_certificate = PassCertificate.new
273
+ pass_certificate.name = row.at_xpath('td[1]').inner_text.strip.gsub(/^\p{Space}+|\p{Space}+$/, '') rescue nil
274
+ pass_certificate.status = row.at_xpath('td[2]/span/text()').to_s.strip rescue nil
275
+ pass_certificate.expiration_date = row.at_xpath('td[3]/text()').to_s.strip rescue nil
276
+ pass_certificate.certificate_id = row.at_xpath('td[4]//a[@id="form_logginMemberCert_"]')['href'].to_s.strip.match(/certDisplayId=(.+?)$/)[1] rescue nil
277
+
278
+ pass_certificates << pass_certificate unless pass_certificate.certificate_id.nil?
279
+ end
280
+ pass_certificates
281
+ end
282
+
283
+ def add_pass_type_id(pass_type_id, description)
284
+ get("https://developer.apple.com/ios/manage/passtypeids/add.action")
285
+
286
+ if form = page.form_with(:name => 'save')
287
+ form['cardName'] = description
288
+ form['cardIdentifier'] = pass_type_id
289
+
290
+ button = form.button_with(:name => 'submit')
291
+ form.click_button(button)
292
+ end
293
+ end
294
+
295
+ def add_pass_certificate(pass_type_id, csr_path, aps_cert_type = 'development')
296
+ pass_type_id = list_pass_type_ids().delete_if{ |item| item.id != pass_type_id }.shift rescue nil
297
+ return if pass_type_id.nil?
298
+
299
+ csr_contents = ::File.open(csr_path, "rb").read
300
+
301
+ post("https://developer.apple.com/ios/assistant/passtypecommit.action", { 'cardIdValue' => pass_type_id.card_id, 'csrValue' => csr_contents, 'apsCertType' => aps_cert_type })
302
+
303
+ JSON.parse(page.content)
304
+ end
305
+
306
+ def pass_type_generate(aps_cert_type = 'development')
307
+ post("https://developer.apple.com/ios/assistant/passtypegenerate.action", { 'apsCertType' => aps_cert_type })
308
+
309
+ ::JSON.parse(page.content)
310
+ end
311
+
312
+ def download_pass_certificate(pass_type_id, certificate_id = nil)
313
+ pass_certificate = (certificate_id.nil? ? list_pass_certificates(pass_type_id).last : list_pass_certificates(pass_type_id).delete_if{ |item| item.certificate_id != certificate_id }.shift) rescue nil
314
+ return nil if pass_certificate.nil?
315
+
316
+ self.pluggable_parser.default = Mechanize::Download
317
+ download = get("/ios/manage/passtypeids/downloadCert.action?certDisplayId=#{pass_certificate.certificate_id}")
318
+ download.filename = "#{pass_certificate.certificate_id}.cer"
319
+ download.save
320
+ return download.filename
321
+ end
322
+
247
323
  private
248
324
 
249
325
  def login!
@@ -0,0 +1,139 @@
1
+ command :'pass_type_ids:list' do |c|
2
+ c.syntax = 'ios pass_type_ids:list'
3
+ c.summary = 'Lists the Pass Type IDs'
4
+ c.description = ''
5
+
6
+ c.action do |args, options|
7
+ pass_type_ids = try{agent.list_pass_type_ids}
8
+
9
+ say_warning "No pass type IDs found." and abort if pass_type_ids.empty?
10
+
11
+ table = Terminal::Table.new do |t|
12
+ t << ["Card ID", "Identifier", "Description", "Pass Certificates"]
13
+ t.add_separator
14
+ pass_type_ids.each do |pass_type_id|
15
+ t << [pass_type_id.card_id, pass_type_id.id, pass_type_id.description, pass_type_id.pass_certificates]
16
+ end
17
+ end
18
+
19
+ puts table
20
+ end
21
+ end
22
+
23
+ alias_command :pass_type_ids, :'pass_type_ids:list'
24
+
25
+ command :'pass_type_ids:add' do |c|
26
+ c.syntax = 'ios pass_type_ids:add PASS_TYPE_ID --description STRING'
27
+ c.summary = 'Adds the Pass Type to the Provisioning Portal'
28
+ c.description = ''
29
+ c.option '-d', '--description DESCRIPTION', 'Description'
30
+
31
+ c.action do |args, options|
32
+ pass_type_id = args.first
33
+ pass_type_id ||= ask "Pass Type ID:"
34
+ say_error "Pass Type ID must begin with the string 'pass.' and use reverse-domain name style. Example: pass.domainname.passname" and abort unless PASS_TYPE_ID_REGEXP === pass_type_id
35
+
36
+ description = options.description
37
+ description ||= ask "Description (alphanumeric characters and spaces only):"
38
+ say_error "Invalid description. Only alphanumeric characters and spaces are allowed." and abort unless /^[\w ]*$/ === description
39
+
40
+ agent.add_pass_type_id(pass_type_id, description)
41
+
42
+ say_ok "Added #{pass_type_id}: #{description}"
43
+ end
44
+ end
45
+
46
+ command :'pass_type_ids:certificates:list' do |c|
47
+ c.syntax = 'ios pass_type_ids:certificates:list PASS_TYPE_ID'
48
+ c.summary = 'Lists the Pass Certificates for a specific Pass Type ID'
49
+ c.description = ''
50
+
51
+ c.action do |args, options|
52
+ pass_type_id = args.first || determine_pass_type_id!
53
+
54
+ pass_certificates = try{agent.list_pass_certificates(pass_type_id)}
55
+ say_warning "No pass certificates found for Pass Type ID (#{pass_type_id})." and abort if pass_certificates.empty?
56
+
57
+ table = Terminal::Table.new do |t|
58
+ t << ["Name", "Status", "Expiration Date", "Certificate ID"]
59
+
60
+ t.add_separator
61
+
62
+ pass_certificates.each do |pass_certificate|
63
+ t << [pass_certificate.name, pass_certificate.status, pass_certificate.expiration_date, pass_certificate.certificate_id]
64
+ end
65
+ end
66
+
67
+ puts table
68
+ end
69
+ end
70
+
71
+ alias_command :'pass_type_ids:certificates', :'pass_type_ids:certificates:list'
72
+
73
+ command :'pass_type_ids:certificates:add' do |c|
74
+ c.syntax = 'ios pass_type_ids:certificates:add PASS_TYPE_ID --csr STRING'
75
+ c.summary = 'Adds the pass certificate for pass type ID to the Provisioning Portal'
76
+ c.description = ''
77
+ c.option '-r', '--csr CERTIFICATE_SIGNING_REQUEST', 'Path to Certificate Signing Request (CSR)'
78
+
79
+ c.action do |args, options|
80
+ pass_type_id = args.first || determine_pass_type_id!
81
+
82
+ csr = options.csr
83
+ csr ||= ask "CSR Path:"
84
+ say_error "No Certificate Signing Request found at path #{csr}." and abort if not ::File.exists?(csr)
85
+
86
+ result = agent.add_pass_certificate(pass_type_id, csr)
87
+ say_error "Failed to configure #{pass_type_id}" and abort if not result["acknowledgement"] or result["pageError"]
88
+
89
+ say_ok "Configured #{pass_type_id}. Apple is generating the certificate..."
90
+
91
+ catch(:generated) do
92
+ say_ok "Certificate generated and is ready to be downloaded." and abort
93
+ end
94
+
95
+ # Wait up to 30 seconds for certificate to be generated
96
+ 6.times do
97
+ throw :generated if agent.pass_type_generate["certStatus"] rescue false
98
+ sleep 5
99
+ end
100
+
101
+ say_warning "Certificate is not generated after waiting for 30 seconds, aborting further monitoring. You might have reached the maximum number of certificates generated (5) for a pass." and abort
102
+ end
103
+ end
104
+
105
+ command :'pass_type_ids:certificates:download' do |c|
106
+ c.syntax = 'ios pass_type_ids:certificates:download PASS_TYPE_ID [--certificate_id STRING]'
107
+ c.summary = 'Adds the pass certificate for pass type ID to the Provisioning Portal'
108
+ c.description = ''
109
+ c.option '-c', '--certificate_id ID', 'Certificate ID'
110
+
111
+ c.action do |args, options|
112
+ pass_type_id = args.first || determine_pass_type_id!
113
+
114
+ certificate_id = options.certificate_id
115
+
116
+ if filename = agent.download_pass_certificate(pass_type_id, certificate_id)
117
+ say_ok "Successfully downloaded: '#{filename}'"
118
+ else
119
+ say_error "Could not download pass certificate"
120
+ end
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ PASS_TYPE_ID_REGEXP = /^pass\.([A-Za-z0-9-]+\.?)+(\*|[^\.])$/
127
+
128
+ def determine_pass_type_id!
129
+ pass_type_ids = try{agent.list_pass_type_ids}
130
+
131
+ case pass_type_ids.length
132
+ when 0
133
+ say_error "No Pass Types found." and abort
134
+ when 1
135
+ pass_type_id = pass_type_ids.first
136
+ else
137
+ pass_type_id = choose "Select a Pass Type", *pass_type_ids
138
+ end
139
+ end
@@ -5,5 +5,6 @@ require 'cupertino/provisioning_portal/commands/certificates'
5
5
  require 'cupertino/provisioning_portal/commands/devices'
6
6
  require 'cupertino/provisioning_portal/commands/profiles'
7
7
  require 'cupertino/provisioning_portal/commands/app_ids'
8
+ require 'cupertino/provisioning_portal/commands/pass_type_ids'
8
9
  require 'cupertino/provisioning_portal/commands/login'
9
10
  require 'cupertino/provisioning_portal/commands/logout'
@@ -3,6 +3,8 @@ require 'certified'
3
3
 
4
4
  module Cupertino
5
5
  module ProvisioningPortal
6
+ class UnsuccessfulAuthenticationError < RuntimeError; end
7
+
6
8
  class Device < Struct.new(:name, :udid)
7
9
  def to_s
8
10
  "#{self.name} #{self.udid}"
@@ -26,8 +28,18 @@ module Cupertino
26
28
  "#{self.name}"
27
29
  end
28
30
  end
29
-
30
- class UnsuccessfulAuthenticationError < RuntimeError; end
31
+
32
+ class PassTypeID < Struct.new(:description, :id, :pass_certificates, :card_id)
33
+ def to_s
34
+ "#{self.id} #{self.description}"
35
+ end
36
+ end
37
+
38
+ class PassCertificate < Struct.new(:name, :status, :expiration_date, :certificate_id)
39
+ def to_s
40
+ "#{self.certificate_id}"
41
+ end
42
+ end
31
43
  end
32
44
  end
33
45
 
data/lib/cupertino.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module Cupertino
2
- VERSION = '0.6.1'
2
+ VERSION = '0.7.0'
3
3
  HOSTNAME = "developer.apple.com"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cupertino
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -162,9 +162,6 @@ executables:
162
162
  extensions: []
163
163
  extra_rdoc_files: []
164
164
  files:
165
- - ./cupertino-0.5.0.gem
166
- - ./cupertino-0.5.1.gem
167
- - ./cupertino-0.6.0.gem
168
165
  - ./cupertino.gemspec
169
166
  - ./Gemfile
170
167
  - ./Gemfile.lock
@@ -174,6 +171,7 @@ files:
174
171
  - ./lib/cupertino/provisioning_portal/commands/devices.rb
175
172
  - ./lib/cupertino/provisioning_portal/commands/login.rb
176
173
  - ./lib/cupertino/provisioning_portal/commands/logout.rb
174
+ - ./lib/cupertino/provisioning_portal/commands/pass_type_ids.rb
177
175
  - ./lib/cupertino/provisioning_portal/commands/profiles.rb
178
176
  - ./lib/cupertino/provisioning_portal/commands.rb
179
177
  - ./lib/cupertino/provisioning_portal/helpers.rb
@@ -197,7 +195,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
197
195
  version: '0'
198
196
  segments:
199
197
  - 0
200
- hash: -4523240717827512371
198
+ hash: 2354490475708821008
201
199
  required_rubygems_version: !ruby/object:Gem::Requirement
202
200
  none: false
203
201
  requirements:
@@ -206,7 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
204
  version: '0'
207
205
  segments:
208
206
  - 0
209
- hash: -4523240717827512371
207
+ hash: 2354490475708821008
210
208
  requirements: []
211
209
  rubyforge_project:
212
210
  rubygems_version: 1.8.24
data/cupertino-0.5.0.gem DELETED
Binary file
data/cupertino-0.5.1.gem DELETED
Binary file
data/cupertino-0.6.0.gem DELETED
Binary file