cupertino 0.6.1 → 0.7.0

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.
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