boxen 2.7.1 → 2.7.2

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.name = "boxen"
5
- gem.version = "2.7.1"
5
+ gem.version = "2.7.2"
6
6
  gem.authors = ["John Barnette", "Will Farrington", "David Goodlad"]
7
7
  gem.email = ["jbarnette@github.com", "wfarr@github.com", "dgoodlad@github.com"]
8
8
  gem.description = "Manage Mac development boxes with love (and Puppet)."
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
16
16
 
17
17
  gem.add_dependency "ansi", "~> 1.4"
18
18
  gem.add_dependency "hiera", "~> 1.0"
19
- gem.add_dependency "highline", "~> 1.6"
19
+ gem.add_dependency "highline", "~> 1.6.0"
20
20
  gem.add_dependency "json_pure", [">= 1.7.7", "< 2.0"]
21
21
  gem.add_dependency "librarian-puppet", "~> 1.0.0"
22
22
  gem.add_dependency "octokit", "~> 2.7", ">= 2.7.1"
@@ -1,6 +1,8 @@
1
1
  require "boxen/preflight"
2
2
  require "highline"
3
3
  require "octokit"
4
+ require "digest"
5
+ require "socket"
4
6
 
5
7
  # HACK: Unless this is `false`, HighLine has some really bizarre
6
8
  # problems with empty/expended streams at bizarre intervals.
@@ -68,12 +70,27 @@ class Boxen::Preflight::Creds < Boxen::Preflight
68
70
  fetch_login_and_password
69
71
  tokens = get_tokens
70
72
 
71
- unless auth = tokens.detect { |a| a.note == "Boxen" }
72
- auth = tmp_api.create_authorization \
73
- :note => "Boxen",
74
- :scopes => %w(repo user),
75
- :headers => headers
76
- end
73
+ # Boxen now supports the updated GitHub Authorizations API by using a unique
74
+ # `fingerprint` for each Boxen installation for a user. We delete any older
75
+ # authorization that does not make use of `fingerprint` so that the "legacy"
76
+ # authorization doesn't persist in the user's list of personal access
77
+ # tokens.
78
+ legacy_auth = tokens.detect { |a| a.note == "Boxen" && a.fingerprint == nil }
79
+ tmp_api.delete_authorization(legacy_auth.id, :headers => headers) if legacy_auth
80
+
81
+ # The updated GitHub authorizations API, in order to improve security, no
82
+ # longer returns a plaintext `token` for existing authorizations. So, if an
83
+ # authorization already exists for this machine we need to first delete it
84
+ # so that we can create a new one.
85
+ auth = tokens.detect { |a| a.note == note && a.fingerprint == fingerprint }
86
+ tmp_api.delete_authorization(auth.id, :headers => headers) if auth
87
+
88
+ auth = tmp_api.create_authorization(
89
+ :note => note,
90
+ :scopes => %w(repo user),
91
+ :fingerprint => fingerprint,
92
+ :headers => headers
93
+ )
77
94
 
78
95
  config.token = auth.token
79
96
 
@@ -105,4 +122,28 @@ class Boxen::Preflight::Creds < Boxen::Preflight
105
122
  warn "Oh, looks like you've provided your #{thing} as environmental variable..."
106
123
  found
107
124
  end
125
+
126
+ def fingerprint
127
+ @fingerprint ||= begin
128
+ # See Apple technical note TN1103, "Uniquely Identifying a Macintosh
129
+ # Computer."
130
+ serial_number_match_data = IO.popen(
131
+ ["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"]
132
+ ).read.match(/"IOPlatformSerialNumber" = "([[:alnum:]]+)"/)
133
+ if serial_number_match_data
134
+ # The fingerprint must be unique across all personal access tokens for a
135
+ # given user. We prefix the serial number with the application name to
136
+ # differentiate between any other personal access token that uses the
137
+ # Mac serial number for the fingerprint.
138
+ Digest::SHA256.hexdigest("Boxen: #{serial_number_match_data[1]}")
139
+ else
140
+ abort "Sorry, I was unable to obtain your Mac's serial number.",
141
+ "Boxen requires access to your Mac's serial number in order to generate a unique GitHub personal access token."
142
+ end
143
+ end
144
+ end
145
+
146
+ def note
147
+ @note ||= "Boxen: #{Socket.gethostname}"
148
+ end
108
149
  end
@@ -3,6 +3,9 @@ require 'boxen/config'
3
3
  require 'boxen/preflight/creds'
4
4
 
5
5
  class BoxenPreflightCredsTest < Boxen::Test
6
+ # Make a struct to use for stubbing out authorization objects.
7
+ Struct.new("Authorization", :id, :note, :fingerprint, :token)
8
+
6
9
  def setup
7
10
  @config = Boxen::Config.new do |c|
8
11
  c.user = 'mojombo'
@@ -77,4 +80,98 @@ class BoxenPreflightCredsTest < Boxen::Test
77
80
  assert_equal "l", @config.login
78
81
  assert_equal "p", preflight.instance_variable_get(:@password)
79
82
  end
83
+
84
+ def test_run_with_existing_token
85
+ preflight = Boxen::Preflight::Creds.new @config
86
+ note = "App1"
87
+ fingerprint = "Fingerprint1"
88
+ existing_token = Struct::Authorization.new(1, note, fingerprint, "Token1")
89
+
90
+ preflight.stubs(:fetch_login_and_password).returns("")
91
+ preflight.stubs(:get_tokens).returns([existing_token])
92
+ preflight.stubs(:note).returns(note)
93
+ preflight.stubs(:fingerprint).returns(fingerprint)
94
+ preflight.stubs(:ok?).returns(true)
95
+ preflight.tmp_api.expects(:delete_authorization).with(existing_token.id, :headers => {})
96
+ preflight.tmp_api.expects(:create_authorization).with(
97
+ :note => note,
98
+ :fingerprint => fingerprint,
99
+ :scopes => %w(repo user),
100
+ :headers => {}
101
+ ).returns(existing_token)
102
+
103
+ preflight.run
104
+ end
105
+
106
+ def test_run_with_no_existing_token
107
+ preflight = Boxen::Preflight::Creds.new @config
108
+ note = "App1"
109
+ fingerprint = "Fingerprint1"
110
+ new_token = Struct::Authorization.new(1, note, fingerprint, "Token1")
111
+
112
+ preflight.stubs(:fetch_login_and_password).returns("")
113
+ preflight.stubs(:get_tokens).returns([])
114
+ preflight.stubs(:note).returns(note)
115
+ preflight.stubs(:fingerprint).returns(fingerprint)
116
+ preflight.stubs(:ok?).returns(true)
117
+ preflight.tmp_api.expects(:delete_authorization).never
118
+ preflight.tmp_api.expects(:create_authorization).with(
119
+ :note => note,
120
+ :fingerprint => fingerprint,
121
+ :scopes => %w(repo user),
122
+ :headers => {}
123
+ ).returns(new_token)
124
+
125
+ preflight.run
126
+ end
127
+
128
+ def test_run_does_not_delete_unrelated_tokens
129
+ preflight = Boxen::Preflight::Creds.new @config
130
+ note = "App1"
131
+ fingerprint = "Fingerprint1"
132
+ new_token = Struct::Authorization.new(1, note, fingerprint, "Token1")
133
+ unrelated_token = Struct::Authorization.new(2, "App2", fingerprint, "Token2")
134
+ unrelated_token_with_fingerprint = Struct::Authorization.new(3, "App3", "Fingerprint3", "Token3")
135
+
136
+ preflight.stubs(:fetch_login_and_password).returns("")
137
+ preflight.stubs(:get_tokens).returns(
138
+ [unrelated_token, unrelated_token_with_fingerprint]
139
+ )
140
+ preflight.stubs(:note).returns(note)
141
+ preflight.stubs(:fingerprint).returns(fingerprint)
142
+ preflight.stubs(:ok?).returns(true)
143
+ preflight.tmp_api.expects(:delete_authorization).never
144
+ preflight.tmp_api.expects(:create_authorization).with(
145
+ :note => note,
146
+ :fingerprint => fingerprint,
147
+ :scopes => %w(repo user),
148
+ :headers => {}
149
+ ).returns(new_token)
150
+
151
+ preflight.run
152
+ end
153
+
154
+ def test_run_does_delete_legacy_token
155
+ preflight = Boxen::Preflight::Creds.new @config
156
+ note = "App1"
157
+ fingerprint = "Fingerprint1"
158
+ existing_token = Struct::Authorization.new(1, note, fingerprint, "Token1")
159
+ legacy_token = Struct::Authorization.new(2, "Boxen", nil, "Token2")
160
+
161
+ preflight.stubs(:fetch_login_and_password).returns("")
162
+ preflight.stubs(:get_tokens).returns([existing_token, legacy_token])
163
+ preflight.stubs(:note).returns(note)
164
+ preflight.stubs(:fingerprint).returns(fingerprint)
165
+ preflight.stubs(:ok?).returns(true)
166
+ preflight.tmp_api.expects(:delete_authorization).with(2, :headers => {})
167
+ preflight.tmp_api.expects(:delete_authorization).with(1, :headers => {})
168
+ preflight.tmp_api.expects(:create_authorization).with(
169
+ :note => note,
170
+ :fingerprint => fingerprint,
171
+ :scopes => %w(repo user),
172
+ :headers => {}
173
+ ).returns(existing_token)
174
+
175
+ preflight.run
176
+ end
80
177
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boxen
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
8
  - 7
9
- - 1
10
- version: 2.7.1
9
+ - 2
10
+ version: 2.7.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - John Barnette
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2015-03-10 00:00:00 +00:00
20
+ date: 2015-05-18 00:00:00 +01:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency
@@ -58,11 +58,12 @@ dependencies:
58
58
  requirements:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
- hash: 3
61
+ hash: 15
62
62
  segments:
63
63
  - 1
64
64
  - 6
65
- version: "1.6"
65
+ - 0
66
+ version: 1.6.0
66
67
  type: :runtime
67
68
  version_requirements: *id003
68
69
  - !ruby/object:Gem::Dependency