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 +1 -1
- data/README.md +60 -0
- data/lib/cupertino/provisioning_portal/agent.rb +77 -1
- data/lib/cupertino/provisioning_portal/commands/pass_type_ids.rb +139 -0
- data/lib/cupertino/provisioning_portal/commands.rb +1 -0
- data/lib/cupertino/provisioning_portal.rb +14 -2
- data/lib/cupertino.rb +1 -1
- metadata +4 -6
- data/cupertino-0.5.0.gem +0 -0
- data/cupertino-0.5.1.gem +0 -0
- data/cupertino-0.6.0.gem +0 -0
data/Gemfile.lock
CHANGED
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
|
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
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.
|
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:
|
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:
|
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
|