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