download-profiles 1.0.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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +66 -0
- data/LICENSE +19 -0
- data/README.md +182 -0
- data/Rakefile +10 -0
- data/bin/download-profiles +22 -0
- data/download-profiles.gemspec +31 -0
- data/lib/download-profiles/provisioning_portal/agent.rb +146 -0
- data/lib/download-profiles/provisioning_portal/commands/login.rb +17 -0
- data/lib/download-profiles/provisioning_portal/commands/logout.rb +14 -0
- data/lib/download-profiles/provisioning_portal/commands/profiles.rb +41 -0
- data/lib/download-profiles/provisioning_portal/commands.rb +15 -0
- data/lib/download-profiles/provisioning_portal/helpers.rb +65 -0
- data/lib/download-profiles/provisioning_portal.rb +19 -0
- data/lib/download-profiles/version.rb +3 -0
- data/lib/download-profiles.rb +1 -0
- metadata +173 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: c8533cc7f76d098767baf67703a2ebba51372001
|
|
4
|
+
data.tar.gz: 27a68f10c07d41eff9791e3e0c64b15e6eeb4fea
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f30c46e0902a6b79af8fb671b2f988fc6dd39139c38362a3951d24efd5ca12b41b1af2c2a030a3fad81efa2fb28407fa0afdbfd20daa54987cc5fc1492d5e1f7
|
|
7
|
+
data.tar.gz: 8c146d5e638c1dba3e9a0c7716ceb89e1b883547217bfc4f5a90abe6e0250bc9276e9a0933f8f7878eb1286a8d759fc16acea488051b5a726c149009803cdda8
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
download-profiles (1.0.0)
|
|
5
|
+
certified (>= 0.1.0)
|
|
6
|
+
commander (~> 4.2.0)
|
|
7
|
+
mechanize (~> 2.7.3)
|
|
8
|
+
nokogiri (~> 1.6.3)
|
|
9
|
+
security (~> 0.1.2)
|
|
10
|
+
term-ansicolor (~> 1.0.7)
|
|
11
|
+
|
|
12
|
+
GEM
|
|
13
|
+
remote: https://rubygems.org/
|
|
14
|
+
specs:
|
|
15
|
+
certified (1.0.0)
|
|
16
|
+
commander (4.2.1)
|
|
17
|
+
highline (~> 1.6.11)
|
|
18
|
+
diff-lcs (1.2.5)
|
|
19
|
+
domain_name (0.5.23)
|
|
20
|
+
unf (>= 0.0.5, < 1.0.0)
|
|
21
|
+
highline (1.6.21)
|
|
22
|
+
http-cookie (1.0.2)
|
|
23
|
+
domain_name (~> 0.5)
|
|
24
|
+
mechanize (2.7.3)
|
|
25
|
+
domain_name (~> 0.5, >= 0.5.1)
|
|
26
|
+
http-cookie (~> 1.0)
|
|
27
|
+
mime-types (~> 2.0)
|
|
28
|
+
net-http-digest_auth (~> 1.1, >= 1.1.1)
|
|
29
|
+
net-http-persistent (~> 2.5, >= 2.5.2)
|
|
30
|
+
nokogiri (~> 1.4)
|
|
31
|
+
ntlm-http (~> 0.1, >= 0.1.1)
|
|
32
|
+
webrobots (>= 0.0.9, < 0.2)
|
|
33
|
+
mime-types (2.4.3)
|
|
34
|
+
mini_portile (0.6.2)
|
|
35
|
+
net-http-digest_auth (1.4)
|
|
36
|
+
net-http-persistent (2.9.4)
|
|
37
|
+
nokogiri (1.6.5)
|
|
38
|
+
mini_portile (~> 0.6.0)
|
|
39
|
+
ntlm-http (0.1.1)
|
|
40
|
+
rake (10.4.2)
|
|
41
|
+
rspec (3.1.0)
|
|
42
|
+
rspec-core (~> 3.1.0)
|
|
43
|
+
rspec-expectations (~> 3.1.0)
|
|
44
|
+
rspec-mocks (~> 3.1.0)
|
|
45
|
+
rspec-core (3.1.7)
|
|
46
|
+
rspec-support (~> 3.1.0)
|
|
47
|
+
rspec-expectations (3.1.2)
|
|
48
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
49
|
+
rspec-support (~> 3.1.0)
|
|
50
|
+
rspec-mocks (3.1.3)
|
|
51
|
+
rspec-support (~> 3.1.0)
|
|
52
|
+
rspec-support (3.1.2)
|
|
53
|
+
security (0.1.3)
|
|
54
|
+
term-ansicolor (1.0.7)
|
|
55
|
+
unf (0.1.4)
|
|
56
|
+
unf_ext
|
|
57
|
+
unf_ext (0.0.6)
|
|
58
|
+
webrobots (0.1.1)
|
|
59
|
+
|
|
60
|
+
PLATFORMS
|
|
61
|
+
ruby
|
|
62
|
+
|
|
63
|
+
DEPENDENCIES
|
|
64
|
+
download-profiles!
|
|
65
|
+
rake
|
|
66
|
+
rspec
|
data/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2012 Mattt Thompson (http://mattt.me/)
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
Automate administrative tasks that you would normally have to do through the Apple Dev Center websites. Life's too short to manage device identifiers by hand!
|
|
4
|
+
|
|
5
|
+
> Cupertino is named after [Cupertino, CA](http://en.wikipedia.org/wiki/Cupertino,_California): home to Apple, Inc.'s world headquarters.
|
|
6
|
+
> It's part of a series of world-class command-line utilities for iOS development, which includes [Shenzhen](https://github.com/mattt/shenzhen) (Building & Distribution), [Houston](https://github.com/mattt/houston) (Push Notifications), [Venice](https://github.com/mattt/venice) (In-App Purchase Receipt Verification), [Dubai](https://github.com/mattt/dubai) (Passbook pass generation), and [Nashville](https://github.com/nomad/nashville) (iTunes Store API).
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
Cupertino requires the [Xcode Command Line Tools](https://developer.apple.com/xcode/), which can be installed with the following command:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
$ xcode-select --install
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
$ gem install cupertino
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Authentication
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
$ ios login
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
_Credentials are saved in the Keychain. You will not be prompted for your username or password by commands while you are logged in. (Mac only)_
|
|
31
|
+
|
|
32
|
+
### Devices
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
$ ios devices:list
|
|
36
|
+
|
|
37
|
+
+------------------------------+---------------------------------------+
|
|
38
|
+
| Listing 2 devices. You can register 98 additional devices. |
|
|
39
|
+
+---------------------------+------------------------------------------+
|
|
40
|
+
| Device Name | Device Identifier |
|
|
41
|
+
+---------------------------+------------------------------------------+
|
|
42
|
+
| Johnny Appleseed iPad | 0123456789012345678901234567890123abcdef |
|
|
43
|
+
| Johnny Appleseed iPhone | abcdef0123456789012345678901234567890123 |
|
|
44
|
+
+---------------------------+------------------------------------------+
|
|
45
|
+
|
|
46
|
+
$ ios devices:add "iPad 1"=abc123
|
|
47
|
+
$ ios devices:add "iPad 2"=def456 "iPad 3"=ghi789 ...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Provisioning Profiles
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
$ ios profiles:list
|
|
54
|
+
|
|
55
|
+
+----------------------------------+--------------+---------+
|
|
56
|
+
| Profile | App ID | Status |
|
|
57
|
+
+----------------------------------+--------------+---------+
|
|
58
|
+
| iOS Team Provisioning Profile: * | ABCDEFG123.* | Valid |
|
|
59
|
+
+----------------------------------+--------------+---------+
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
$ ios profiles:manage:devices
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
_Opens an editor with a list of devices, each of which can be commented / uncommented to turn them off / on for that provisioning profile._
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
# Comment / Uncomment Devices to Turn Off / On for Provisioning Profile
|
|
72
|
+
Johnny Appleseed iPad 0123456789012345678901234567890123abcdef
|
|
73
|
+
# Johnny Appleseed iPhone abcdef0123456789012345678901234567890123
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
$ ios profiles:devices:add MyApp_Development_Profile "Johnny Appleseed iPad"=0123456789012345678901234567890123abcdef "Johnny Appleseed iPhone"=abcdef0123456789012345678901234567890123
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
_Adds (without an editor) a list of devices to a provisioning profile_
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
$ ios profiles:devices:remove MyApp_Development_Profile "Johnny Old iPad"=0123456789012345678901234567890123abcdef "Johnny Old iPhone"=abcdef0123456789012345678901234567890123
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
_Removes (without an editor) a list of devices from a provisioning profile_
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
$ ios profiles:devices:list MyApp_Development_Profile
|
|
92
|
+
|
|
93
|
+
+--------------------------+------------------------------------------+--------+
|
|
94
|
+
| Listing devices for provisioning profile MyApp_Development_Profile |
|
|
95
|
+
+--------------------------+------------------------------------------+--------+
|
|
96
|
+
| Device Name | Device Identifier | Active |
|
|
97
|
+
+--------------------------+------------------------------------------+--------+
|
|
98
|
+
| Person's iPhone 5 | 888888883e48a3e0458aab2691d565a8a63f7888 | Y |
|
|
99
|
+
+--------------------------+------------------------------------------+--------+
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### App IDs
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
$ ios app_ids:list
|
|
107
|
+
|
|
108
|
+
+-----------------------------+------------------------+-------------------+-------------------+
|
|
109
|
+
| Bundle Seed ID | Description | Development | Distribution |
|
|
110
|
+
+-----------------------------+------------------------+-------------------+-------------------+
|
|
111
|
+
| 123ABCDEFG.com.mattt.bundle | App Bundle Description | Passes | Passes |
|
|
112
|
+
| | | Data Protection | Data Protection |
|
|
113
|
+
| | | iCloud | iCloud |
|
|
114
|
+
| | | In-App Purchase | In-App Purchase |
|
|
115
|
+
| | | Game Center | Game Center |
|
|
116
|
+
| | | Push Notification | Push Notification |
|
|
117
|
+
+-----------------------------+------------------------+-------------------+-------------------+
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
$ ios app_ids:add "App Bundle Description"=123ABCDEFG.com.mattt.bundle
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Certificates
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
$ ios certificates:list
|
|
128
|
+
|
|
129
|
+
+------------------+----------------------------------+-----------------+--------+
|
|
130
|
+
| Name | Provisioning Profiles | Expiration Date | Status |
|
|
131
|
+
+------------------+----------------------------------+-----------------+--------+
|
|
132
|
+
| Johnny Appleseed | iOS Team Provisioning Profile: * | Dec 23, 2012 | Issued |
|
|
133
|
+
+------------------+----------------------------------+-----------------+--------+
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
$ ios certificates:download
|
|
138
|
+
$ ios certificates:download --type distribution
|
|
139
|
+
$ ios certificates:download NAME
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## CSV Output
|
|
143
|
+
|
|
144
|
+
The following commands will format their output as [comma-separated values](http://en.wikipedia.org/wiki/Comma-separated_values) when the `--format csv` argument is passed:
|
|
145
|
+
|
|
146
|
+
- `app_ids:list`
|
|
147
|
+
- `devices:list`
|
|
148
|
+
- `profiles:list`
|
|
149
|
+
- `profiles:manage:devices:list`
|
|
150
|
+
|
|
151
|
+
## Commands
|
|
152
|
+
|
|
153
|
+
- `login`
|
|
154
|
+
- `logout`
|
|
155
|
+
- `devices:add`
|
|
156
|
+
- `devices:list`
|
|
157
|
+
- `profiles:list`
|
|
158
|
+
- `profiles:manage:devices`
|
|
159
|
+
- `profiles:manage:devices:add`
|
|
160
|
+
- `profiles:manage:devices:remove`
|
|
161
|
+
- `profiles:download`
|
|
162
|
+
- `profiles:download:all`
|
|
163
|
+
- `profiles:devices:list`
|
|
164
|
+
- `certificates:list`
|
|
165
|
+
- `certificates:download`
|
|
166
|
+
- `app_ids:list`
|
|
167
|
+
|
|
168
|
+
## Proxies
|
|
169
|
+
|
|
170
|
+
Cupertino will access the provisioning portal through a proxy if the `HTTP_PROXY` environment variable is set, with optional credentials `HTTP_PROXY_USER` and `HTTP_PROXY_PASSWORD`.
|
|
171
|
+
|
|
172
|
+
## Contact
|
|
173
|
+
|
|
174
|
+
Mattt Thompson
|
|
175
|
+
|
|
176
|
+
- http://github.com/mattt
|
|
177
|
+
- http://twitter.com/mattt
|
|
178
|
+
- m@mattt.me
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
Cupertino is available under the MIT license. See the LICENSE file for more info.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
require "bundler"
|
|
2
|
+
Bundler.setup
|
|
3
|
+
|
|
4
|
+
gemspec = eval(File.read("download-profiles.gemspec"))
|
|
5
|
+
|
|
6
|
+
task :build => "#{gemspec.full_name}.gem"
|
|
7
|
+
|
|
8
|
+
file "#{gemspec.full_name}.gem" => gemspec.files + ["download-profiles.gemspec"] do
|
|
9
|
+
system "gem build download-profiles.gemspec"
|
|
10
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'commander/import'
|
|
4
|
+
require 'term/ansicolor'
|
|
5
|
+
require 'csv'
|
|
6
|
+
|
|
7
|
+
require 'download-profiles'
|
|
8
|
+
|
|
9
|
+
HighLine.track_eof = false # Fix for built-in Ruby
|
|
10
|
+
Signal.trap("INT") {} # Suppress backtrace when exiting command
|
|
11
|
+
|
|
12
|
+
program :version, Cupertino::VERSION
|
|
13
|
+
program :description, 'CLI tool for downloading iOS and Mac provisioning profiles'
|
|
14
|
+
|
|
15
|
+
program :help, 'Author', 'Radek Pietruszewski <this.is@radex.io>'
|
|
16
|
+
program :help, 'Website', 'https://github.com/radex'
|
|
17
|
+
program :help_formatter, :compact
|
|
18
|
+
|
|
19
|
+
default_command :download
|
|
20
|
+
|
|
21
|
+
require 'download-profiles/provisioning_portal'
|
|
22
|
+
require 'download-profiles/provisioning_portal/commands'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
|
|
4
|
+
require "download-profiles/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = "download-profiles"
|
|
8
|
+
s.license = "MIT"
|
|
9
|
+
s.authors = ["Mattt Thompson", "Radek Pietruszewski"]
|
|
10
|
+
s.email = "this.is@radex.io"
|
|
11
|
+
s.homepage = 'https://github.com/radex/download-profiles'
|
|
12
|
+
s.version = Cupertino::VERSION
|
|
13
|
+
s.platform = Gem::Platform::RUBY
|
|
14
|
+
s.summary = "download-profiles"
|
|
15
|
+
s.description = "CLI tool for downloading iOS and Mac provisioning profiles"
|
|
16
|
+
|
|
17
|
+
s.add_dependency "commander", "~> 4.2.0"
|
|
18
|
+
s.add_dependency "term-ansicolor", "~> 1.0.7"
|
|
19
|
+
s.add_dependency "mechanize", "~> 2.7.3"
|
|
20
|
+
s.add_dependency "nokogiri", "~> 1.6.3"
|
|
21
|
+
s.add_dependency "security", "~> 0.1.2"
|
|
22
|
+
s.add_dependency "certified", ">= 0.1.0"
|
|
23
|
+
|
|
24
|
+
s.add_development_dependency "rspec"
|
|
25
|
+
s.add_development_dependency "rake"
|
|
26
|
+
|
|
27
|
+
s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
|
|
28
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
29
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
30
|
+
s.require_paths = ["lib"]
|
|
31
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
require 'mechanize'
|
|
2
|
+
require 'security'
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'date'
|
|
6
|
+
require 'time'
|
|
7
|
+
require 'logger'
|
|
8
|
+
|
|
9
|
+
module Cupertino
|
|
10
|
+
module ProvisioningPortal
|
|
11
|
+
class Agent < ::Mechanize
|
|
12
|
+
attr_accessor :username, :password, :team, :team_id
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
super
|
|
16
|
+
@profile_csrf_headers = {}
|
|
17
|
+
self.user_agent_alias = 'Mac Safari'
|
|
18
|
+
|
|
19
|
+
self.log ||= Logger.new(STDOUT)
|
|
20
|
+
self.log.level = Logger::ERROR
|
|
21
|
+
|
|
22
|
+
if ENV['HTTP_PROXY']
|
|
23
|
+
uri = URI.parse(ENV['HTTP_PROXY'])
|
|
24
|
+
user = ENV['HTTP_PROXY_USER'] if ENV['HTTP_PROXY_USER']
|
|
25
|
+
password = ENV['HTTP_PROXY_PASSWORD'] if ENV['HTTP_PROXY_PASSWORD']
|
|
26
|
+
|
|
27
|
+
set_proxy(uri.host, uri.port, user || uri.user, password || uri.password)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
pw = Security::InternetPassword.find(:server => Cupertino::ProvisioningPortal::HOST)
|
|
31
|
+
@username, @password = pw.attributes['acct'], pw.password if pw
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def username=(value)
|
|
35
|
+
@username = value
|
|
36
|
+
|
|
37
|
+
pw = Security::InternetPassword.find(:a => self.username, :server => Cupertino::ProvisioningPortal::HOST)
|
|
38
|
+
@password = pw.password if pw
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def get(uri, parameters = [], referer = nil, headers = {})
|
|
42
|
+
uri = ::File.join("https://#{Cupertino::ProvisioningPortal::HOST}", uri) unless /^https?/ === uri
|
|
43
|
+
|
|
44
|
+
3.times do
|
|
45
|
+
super(uri, parameters, referer, headers)
|
|
46
|
+
|
|
47
|
+
return page unless page.respond_to?(:title)
|
|
48
|
+
|
|
49
|
+
case page.title
|
|
50
|
+
when /Sign in with your Apple ID/
|
|
51
|
+
login!
|
|
52
|
+
when /Select Team/
|
|
53
|
+
select_team!
|
|
54
|
+
else
|
|
55
|
+
return page
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
raise UnsuccessfulAuthenticationError
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def list_profiles(platform, type)
|
|
63
|
+
url = case type
|
|
64
|
+
when :development
|
|
65
|
+
"https://developer.apple.com/account/#{platform}/profile/profileList.action?type=limited"
|
|
66
|
+
when :distribution
|
|
67
|
+
"https://developer.apple.com/account/#{platform}/profile/profileList.action?type=production"
|
|
68
|
+
else
|
|
69
|
+
raise ArgumentError, 'Provisioning profile type must be :development or :distribution'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
self.pluggable_parser.default = Mechanize::File
|
|
73
|
+
get(url)
|
|
74
|
+
|
|
75
|
+
regex = /profileDataURL = "([^"]*)"/
|
|
76
|
+
profile_data_url = (page.body.match regex or raise UnexpectedContentError)[1]
|
|
77
|
+
|
|
78
|
+
profile_data_url += case type
|
|
79
|
+
when :development
|
|
80
|
+
'&type=limited'
|
|
81
|
+
when :distribution
|
|
82
|
+
'&type=production'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
post(profile_data_url)
|
|
86
|
+
@profile_csrf_headers = {
|
|
87
|
+
'csrf' => page.response['csrf'],
|
|
88
|
+
'csrf_ts' => page.response['csrf_ts']
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@profile_csrf_headers = {
|
|
92
|
+
'csrf' => page.response['csrf'],
|
|
93
|
+
'csrf_ts' => page.response['csrf_ts']
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
profile_data = page.content
|
|
97
|
+
parsed_profile_data = JSON.parse(profile_data)
|
|
98
|
+
|
|
99
|
+
profiles = []
|
|
100
|
+
parsed_profile_data['provisioningProfiles'].each do |row|
|
|
101
|
+
profile = ProvisioningProfile.new
|
|
102
|
+
profile.name = row['name']
|
|
103
|
+
profile.type = type
|
|
104
|
+
profile.platform = platform
|
|
105
|
+
profile.status = row['status']
|
|
106
|
+
profile.expiration = (Time.parse(row['dateExpire']) rescue nil)
|
|
107
|
+
profile.download_url = "https://developer.apple.com/account/#{platform}/profile/profileContentDownload.action?displayId=#{row['provisioningProfileId']}"
|
|
108
|
+
profile.edit_url = "https://developer.apple.com/account/#{platform}/profile/profileEdit.action?provisioningProfileId=#{row['provisioningProfileId']}"
|
|
109
|
+
profile.identifier = row['UUID']
|
|
110
|
+
profiles << profile
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
profiles
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def download_profile(profile)
|
|
117
|
+
ext = (profile.platform == :ios) ? 'mobileprovision' : 'provisionprofile'
|
|
118
|
+
self.pluggable_parser.default = Mechanize::Download
|
|
119
|
+
download = get(profile.download_url)
|
|
120
|
+
download.save!(::File.expand_path("~/Library/MobileDevice/Provisioning Profiles/#{profile.identifier}.#{ext}"))
|
|
121
|
+
download.filename
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def login!
|
|
127
|
+
if form = page.forms.first
|
|
128
|
+
form.fields_with(type: 'text').first.value = self.username
|
|
129
|
+
form.fields_with(type: 'password').first.value = self.password
|
|
130
|
+
|
|
131
|
+
form.submit
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def select_team!
|
|
136
|
+
if form = page.form_with(:name => 'saveTeamSelection')
|
|
137
|
+
team_option = form.radiobutton_with(:value => self.team_id)
|
|
138
|
+
team_option.check
|
|
139
|
+
|
|
140
|
+
button = form.button_with(:name => 'action:saveTeamSelection!save')
|
|
141
|
+
form.click_button(button)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
command :login do |c|
|
|
2
|
+
c.syntax = 'ios login'
|
|
3
|
+
c.summary = 'Save account credentials'
|
|
4
|
+
|
|
5
|
+
c.action do |args, options|
|
|
6
|
+
say_warning "You are already authenticated" if Security::InternetPassword.find(:server => Cupertino::ProvisioningPortal::HOST)
|
|
7
|
+
|
|
8
|
+
user = ask "Username:"
|
|
9
|
+
pass = password "Password:"
|
|
10
|
+
|
|
11
|
+
if Security::InternetPassword.add(Cupertino::ProvisioningPortal::HOST, user, pass)
|
|
12
|
+
say_ok "Account credentials saved"
|
|
13
|
+
else
|
|
14
|
+
say_error "Error saving credentials"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
command :logout do |c|
|
|
2
|
+
c.syntax = 'ios logout'
|
|
3
|
+
c.summary = 'Remove account credentials'
|
|
4
|
+
|
|
5
|
+
c.action do |args, options|
|
|
6
|
+
say_error "You are not authenticated" and abort unless Security::InternetPassword.find(:server => Cupertino::ProvisioningPortal::HOST)
|
|
7
|
+
|
|
8
|
+
if Security::InternetPassword.delete(:server => Cupertino::ProvisioningPortal::HOST)
|
|
9
|
+
say_ok "Account credentials removed"
|
|
10
|
+
else
|
|
11
|
+
say_error "Error removing credentials"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
command :download do |c|
|
|
2
|
+
c.syntax = 'download-profiles'
|
|
3
|
+
c.summary = 'Download provisioning profiles'
|
|
4
|
+
c.description = "By default, downloads and installs all (development, distribution, iOS, Mac) provisioning profiles. Pass appropriate options to constrain."
|
|
5
|
+
|
|
6
|
+
c.option '--platform [PLATFORM]', [:ios, :mac, :all], "Platform of profile (ios, mac or all)"
|
|
7
|
+
c.option '--type [TYPE]', [:development, :distribution, :all], "Type of profile (development, distribution or all)"
|
|
8
|
+
|
|
9
|
+
c.action do |args, options|
|
|
10
|
+
# parse options
|
|
11
|
+
platform = options.platform ? options.platform.downcase.to_sym : :all
|
|
12
|
+
type = options.type ? options.type.downcase.to_sym : :all
|
|
13
|
+
|
|
14
|
+
platforms = (platform == :all) ? [:ios, :mac] : [platform]
|
|
15
|
+
types = (type == :all) ? [:development, :distribution] : [type]
|
|
16
|
+
|
|
17
|
+
# get list of profiles
|
|
18
|
+
profiles =
|
|
19
|
+
platforms.product(types).flat_map { |platform, type| try{agent.list_profiles(platform, type)} }
|
|
20
|
+
.select { |profile| profile.status == 'Active'}
|
|
21
|
+
|
|
22
|
+
say_warning "No active #{type} profiles found." and abort if profiles.empty?
|
|
23
|
+
|
|
24
|
+
# download
|
|
25
|
+
failure = false
|
|
26
|
+
profiles.each do |profile|
|
|
27
|
+
if filename = agent.download_profile(profile)
|
|
28
|
+
say "Downloaded #{filename}"
|
|
29
|
+
else
|
|
30
|
+
say_error "Could not download profile: '#{profile.name}'"
|
|
31
|
+
failure = true
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if failure
|
|
36
|
+
say_warning "Not all profiles downloaded successfully"
|
|
37
|
+
else
|
|
38
|
+
say_ok "All profiles downloaded successfully"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
include Cupertino::ProvisioningPortal
|
|
2
|
+
|
|
3
|
+
require 'download-profiles/provisioning_portal/helpers'
|
|
4
|
+
include Cupertino::ProvisioningPortal::Helpers
|
|
5
|
+
|
|
6
|
+
global_option('-u', '--username USER', 'Username') { |arg| agent.username = arg unless arg.nil? }
|
|
7
|
+
global_option('-p', '--password PASSWORD', 'Password') { |arg| agent.password = arg unless arg.nil? }
|
|
8
|
+
global_option('--team TEAM', 'Team') { |arg| agent.team = arg if arg }
|
|
9
|
+
global_option('--info', 'Set log level to INFO') { agent.log.level = Logger::INFO }
|
|
10
|
+
global_option('--debug', 'Set log level to DEBUG') { agent.log.level = Logger::DEBUG }
|
|
11
|
+
global_option('--format FORMAT', [:table, :csv], "Set output format (default: table)")
|
|
12
|
+
|
|
13
|
+
require 'download-profiles/provisioning_portal/commands/profiles'
|
|
14
|
+
require 'download-profiles/provisioning_portal/commands/login'
|
|
15
|
+
require 'download-profiles/provisioning_portal/commands/logout'
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Monkey Patch Commander::UI to alias password to avoid conflicts
|
|
2
|
+
module Commander::UI
|
|
3
|
+
alias :pw :password
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
class String
|
|
7
|
+
include Term::ANSIColor
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Cupertino
|
|
11
|
+
module ProvisioningPortal
|
|
12
|
+
module Helpers
|
|
13
|
+
def agent
|
|
14
|
+
unless @agent
|
|
15
|
+
@agent = Cupertino::ProvisioningPortal::Agent.new
|
|
16
|
+
|
|
17
|
+
@agent.instance_eval do
|
|
18
|
+
def username
|
|
19
|
+
@username ||= ask "Username:"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def password
|
|
23
|
+
@password ||= pw "Password:"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def team_id
|
|
27
|
+
unless @team_id
|
|
28
|
+
teams = []
|
|
29
|
+
page.form_with(:name => 'saveTeamSelection').radiobuttons.each do |radio|
|
|
30
|
+
name = page.search(".label-primary[for=\"#{radio.dom_id}\"]").first.text.strip
|
|
31
|
+
programs = page.search(".label-secondary[for=\"#{radio.dom_id}\"]").first.text.strip.split(/\,\s+/)
|
|
32
|
+
teams << Team.new(name, programs, radio.value)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
unless team = teams.detect{|t| t.name == @team || t.identifier == @team}
|
|
36
|
+
team = choose("Select a team:", *teams)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@team_id = team.identifier
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
@team_id
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@agent
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def pluralize(n, singular, plural = nil)
|
|
51
|
+
n.to_i == 1 ? "1 #{singular}" : "#{n} #{plural || singular + 's'}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def try
|
|
55
|
+
return unless block_given?
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
yield
|
|
59
|
+
rescue UnsuccessfulAuthenticationError
|
|
60
|
+
say_error "Could not authenticate with Apple Developer Center. Check that your username & password are correct, and that your membership is valid and all pending Terms of Service & agreements are accepted. If this problem continues, try logging into https://developer.apple.com/membercenter/ from a browser to see what's going on." and abort
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'mechanize'
|
|
2
|
+
require 'certified'
|
|
3
|
+
|
|
4
|
+
module Cupertino
|
|
5
|
+
module ProvisioningPortal
|
|
6
|
+
HOST = "developer.apple.com"
|
|
7
|
+
|
|
8
|
+
class UnsuccessfulAuthenticationError < RuntimeError; end
|
|
9
|
+
class UnexpectedContentError < RuntimeError; end
|
|
10
|
+
|
|
11
|
+
class ProvisioningProfile < Struct.new(:name, :type, :platform, :app_id, :status, :expiration, :download_url, :edit_url, :identifier)
|
|
12
|
+
def to_s
|
|
13
|
+
"#{self.name}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
require 'download-profiles/provisioning_portal/agent'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'download-profiles/version'
|
metadata
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: download-profiles
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Mattt Thompson
|
|
8
|
+
- Radek Pietruszewski
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2015-01-07 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: commander
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - "~>"
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: 4.2.0
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - "~>"
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: 4.2.0
|
|
28
|
+
- !ruby/object:Gem::Dependency
|
|
29
|
+
name: term-ansicolor
|
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
|
31
|
+
requirements:
|
|
32
|
+
- - "~>"
|
|
33
|
+
- !ruby/object:Gem::Version
|
|
34
|
+
version: 1.0.7
|
|
35
|
+
type: :runtime
|
|
36
|
+
prerelease: false
|
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - "~>"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: 1.0.7
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: mechanize
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - "~>"
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: 2.7.3
|
|
49
|
+
type: :runtime
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - "~>"
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: 2.7.3
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: nokogiri
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - "~>"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: 1.6.3
|
|
63
|
+
type: :runtime
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - "~>"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: 1.6.3
|
|
70
|
+
- !ruby/object:Gem::Dependency
|
|
71
|
+
name: security
|
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - "~>"
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: 0.1.2
|
|
77
|
+
type: :runtime
|
|
78
|
+
prerelease: false
|
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - "~>"
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: 0.1.2
|
|
84
|
+
- !ruby/object:Gem::Dependency
|
|
85
|
+
name: certified
|
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: 0.1.0
|
|
91
|
+
type: :runtime
|
|
92
|
+
prerelease: false
|
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: 0.1.0
|
|
98
|
+
- !ruby/object:Gem::Dependency
|
|
99
|
+
name: rspec
|
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
type: :development
|
|
106
|
+
prerelease: false
|
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - ">="
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '0'
|
|
112
|
+
- !ruby/object:Gem::Dependency
|
|
113
|
+
name: rake
|
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
|
115
|
+
requirements:
|
|
116
|
+
- - ">="
|
|
117
|
+
- !ruby/object:Gem::Version
|
|
118
|
+
version: '0'
|
|
119
|
+
type: :development
|
|
120
|
+
prerelease: false
|
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
122
|
+
requirements:
|
|
123
|
+
- - ">="
|
|
124
|
+
- !ruby/object:Gem::Version
|
|
125
|
+
version: '0'
|
|
126
|
+
description: CLI tool for downloading iOS and Mac provisioning profiles
|
|
127
|
+
email: this.is@radex.io
|
|
128
|
+
executables:
|
|
129
|
+
- download-profiles
|
|
130
|
+
extensions: []
|
|
131
|
+
extra_rdoc_files: []
|
|
132
|
+
files:
|
|
133
|
+
- "./Gemfile"
|
|
134
|
+
- "./Gemfile.lock"
|
|
135
|
+
- "./LICENSE"
|
|
136
|
+
- "./README.md"
|
|
137
|
+
- "./Rakefile"
|
|
138
|
+
- "./download-profiles.gemspec"
|
|
139
|
+
- "./lib/download-profiles.rb"
|
|
140
|
+
- "./lib/download-profiles/provisioning_portal.rb"
|
|
141
|
+
- "./lib/download-profiles/provisioning_portal/agent.rb"
|
|
142
|
+
- "./lib/download-profiles/provisioning_portal/commands.rb"
|
|
143
|
+
- "./lib/download-profiles/provisioning_portal/commands/login.rb"
|
|
144
|
+
- "./lib/download-profiles/provisioning_portal/commands/logout.rb"
|
|
145
|
+
- "./lib/download-profiles/provisioning_portal/commands/profiles.rb"
|
|
146
|
+
- "./lib/download-profiles/provisioning_portal/helpers.rb"
|
|
147
|
+
- "./lib/download-profiles/version.rb"
|
|
148
|
+
- bin/download-profiles
|
|
149
|
+
homepage: https://github.com/radex/download-profiles
|
|
150
|
+
licenses:
|
|
151
|
+
- MIT
|
|
152
|
+
metadata: {}
|
|
153
|
+
post_install_message:
|
|
154
|
+
rdoc_options: []
|
|
155
|
+
require_paths:
|
|
156
|
+
- lib
|
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
158
|
+
requirements:
|
|
159
|
+
- - ">="
|
|
160
|
+
- !ruby/object:Gem::Version
|
|
161
|
+
version: '0'
|
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
163
|
+
requirements:
|
|
164
|
+
- - ">="
|
|
165
|
+
- !ruby/object:Gem::Version
|
|
166
|
+
version: '0'
|
|
167
|
+
requirements: []
|
|
168
|
+
rubyforge_project:
|
|
169
|
+
rubygems_version: 2.2.2
|
|
170
|
+
signing_key:
|
|
171
|
+
specification_version: 4
|
|
172
|
+
summary: download-profiles
|
|
173
|
+
test_files: []
|