boxen 1.5.2 → 2.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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.name = "boxen"
5
- gem.version = "1.5.2"
5
+ gem.version = "2.0.0"
6
6
  gem.authors = ["John Barnette", "Will Farrington"]
7
7
  gem.email = ["jbarnette@github.com", "wfarr@github.com"]
8
8
  gem.description = "Manage Mac development boxes with love (and Puppet)."
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
  gem.add_dependency "highline", "~> 1.6"
19
19
  gem.add_dependency "json_pure", [">= 1.7.7", "< 2.0"]
20
20
  gem.add_dependency "librarian-puppet", "~> 0.9.9"
21
- gem.add_dependency "octokit", "~> 1.15"
21
+ gem.add_dependency "octokit", "~> 2.0.0"
22
22
  gem.add_dependency "puppet", "~> 3.0"
23
23
 
24
24
  gem.add_development_dependency "minitest", "4.4.0" # pinned for mocha
@@ -26,7 +26,6 @@ module Boxen
26
26
  end
27
27
 
28
28
  keychain = Boxen::Keychain.new config.user
29
- config.password = keychain.password
30
29
  config.token = keychain.token
31
30
 
32
31
  if config.enterprise?
@@ -69,7 +68,6 @@ module Boxen
69
68
  end
70
69
 
71
70
  keychain = Boxen::Keychain.new config.user
72
- keychain.password = config.password
73
71
  keychain.token = config.token
74
72
 
75
73
  config
@@ -85,10 +83,10 @@ module Boxen
85
83
  end
86
84
 
87
85
  # Create an API instance using the current user creds. A new
88
- # instance is created any time `login` or `password` change.
86
+ # instance is created any time `token` changes.
89
87
 
90
88
  def api
91
- @api ||= Octokit::Client.new :login => login, :password => password
89
+ @api ||= Octokit::Client.new :login => token, :password => 'x-oauth-basic'
92
90
  end
93
91
 
94
92
  # Spew a bunch of debug logging? Default is `false`.
@@ -139,26 +137,12 @@ module Boxen
139
137
 
140
138
  # A GitHub user login. Default is `nil`.
141
139
 
142
- attr_reader :login
143
-
144
- def login=(login)
145
- @api = nil
146
- @login = login
147
- end
140
+ attr_accessor :login
148
141
 
149
142
  # A GitHub user's profile name.
150
143
 
151
144
  attr_accessor :name
152
145
 
153
- # A GitHub user password. Default is `nil`.
154
-
155
- attr_reader :password
156
-
157
- def password=(password)
158
- @api = nil
159
- @password = password
160
- end
161
-
162
146
  # Just go through the motions? Default is `false`.
163
147
 
164
148
  def pretend?
@@ -294,7 +278,12 @@ module Boxen
294
278
 
295
279
  # A GitHub OAuth token. Default is `nil`.
296
280
 
297
- attr_accessor :token
281
+ attr_reader :token
282
+
283
+ def token=(token)
284
+ @token = token
285
+ @api = nil
286
+ end
298
287
 
299
288
  # A local user login. Default is the `USER` environment variable.
300
289
 
@@ -12,7 +12,7 @@ module Boxen
12
12
  attr_reader :homedir
13
13
  attr_reader :logfile
14
14
  attr_reader :login
15
- attr_reader :password
15
+ attr_reader :token
16
16
  attr_reader :srcdir
17
17
  attr_reader :user
18
18
 
@@ -118,8 +118,8 @@ module Boxen
118
118
  @stealth = true
119
119
  end
120
120
 
121
- o.on "--password PASSWORD", "Your GitHub password." do |password|
122
- @password = password
121
+ o.on "--token TOKEN", "Your GitHub OAuth token." do |token|
122
+ @token = token
123
123
  end
124
124
 
125
125
  o.on "--profile", "Profile the Puppet run." do
@@ -158,7 +158,7 @@ module Boxen
158
158
  config.homedir = homedir if homedir
159
159
  config.logfile = logfile if logfile
160
160
  config.login = login if login
161
- config.password = password if password
161
+ config.token = token if token
162
162
  config.pretend = pretend?
163
163
  config.profile = profile?
164
164
  config.future_parser = future_parser?
@@ -4,7 +4,10 @@ module Boxen
4
4
  class Hook
5
5
  class GitHubIssue < Hook
6
6
  def perform?
7
- enabled? && !config.stealth? && !config.pretend? && checkout.master?
7
+ enabled? &&
8
+ !config.stealth? && !config.pretend? &&
9
+ !config.login.to_s.empty? &&
10
+ checkout.master?
8
11
  end
9
12
 
10
13
  def call
@@ -18,14 +18,8 @@ module Boxen
18
18
 
19
19
  def initialize(login)
20
20
  @login = login
21
- end
22
-
23
- def password
24
- get PASSWORD_SERVICE
25
- end
26
-
27
- def password=(password)
28
- set PASSWORD_SERVICE, password
21
+ # Clear the password. We're storing tokens now.
22
+ set PASSWORD_SERVICE, ""
29
23
  end
30
24
 
31
25
  def token
@@ -47,14 +41,14 @@ module Boxen
47
41
  $?.success? ? result : nil
48
42
  end
49
43
 
50
- def set(service, password)
51
- cmd = shellescape(HELPER, service, login, password)
44
+ def set(service, token)
45
+ cmd = shellescape(HELPER, service, login, token)
52
46
 
53
47
  unless system *cmd
54
48
  raise Boxen::Error, "Can't save #{service} in the keychain."
55
49
  end
56
50
 
57
- password
51
+ token
58
52
  end
59
53
 
60
54
  def shellescape(*args)
@@ -8,21 +8,62 @@ require "octokit"
8
8
  HighLine.track_eof = false
9
9
 
10
10
  class Boxen::Preflight::Creds < Boxen::Preflight
11
- def basic?
12
- config.api.user rescue nil
13
- end
11
+ attr :otp
12
+ attr :password
14
13
 
15
14
  def ok?
16
- basic? && token?
15
+ if config.token && config.api.user
16
+ # There was a period of time when login wasn't geting set on first run.
17
+ # This should correct that.
18
+ config.login = config.api.user.login
19
+ true
20
+ end
21
+ rescue
22
+ nil
23
+ end
24
+
25
+ def tmp_api
26
+ @tmp_api ||= Octokit::Client.new :login => config.login, :password => password, :auto_paginate => true
17
27
  end
18
28
 
19
- def token?
20
- return unless config.token
29
+ def headers
30
+ otp.nil? ? {} : {"X-GitHub-OTP" => otp}
31
+ end
32
+
33
+ def get_otp
34
+ console = HighLine.new
35
+
36
+ # junk API call to send OTP until we implement PUT
37
+ tmp_api.create_authorization rescue nil
21
38
 
22
- tapi = Octokit::Client.new \
23
- :login => config.login, :oauth_token => config.token
39
+ @otp = console.ask "One time password (via SMS or device):" do |q|
40
+ q.echo = '*'
41
+ end
42
+ end
24
43
 
25
- tapi.user rescue nil
44
+ # Attempt to use the username+password to get a list of the user's OAuth
45
+ # authorizations from the API. If it fails because of 2FA, ask the user for
46
+ # her OTP and try again.
47
+ #
48
+ # Returns a list of authorizations
49
+ def get_tokens
50
+ begin
51
+ tmp_api.authorizations(:headers => headers)
52
+ rescue Octokit::Unauthorized => e
53
+ puts
54
+ if e.message =~ /OTP/
55
+ if otp.nil?
56
+ warn "It looks like you have two-factor auth enabled."
57
+ else
58
+ warn "That one time password didn't work. Let's try again."
59
+ end
60
+ get_otp
61
+ get_tokens
62
+ else
63
+ abort "Sorry, I can't auth you on GitHub.",
64
+ "Please check your credentials and teams and give it another try."
65
+ end
66
+ end
26
67
  end
27
68
 
28
69
  def run
@@ -34,27 +75,26 @@ class Boxen::Preflight::Creds < Boxen::Preflight
34
75
  q.default = config.login || config.user
35
76
  q.validate = /\A[^@]+\Z/
36
77
  end
37
-
38
- config.password = console.ask "GitHub password: " do |q|
78
+
79
+ @password = console.ask "GitHub password: " do |q|
39
80
  q.echo = "*"
40
81
  end
41
82
 
42
- unless basic?
43
- puts # i <3 vertical whitespace
83
+ tokens = get_tokens
44
84
 
45
- abort "Sorry, I can't auth you on GitHub.",
46
- "Please check your credentials and teams and give it another try."
85
+ unless auth = tokens.detect { |a| a.note == "Boxen" }
86
+ auth = tmp_api.create_authorization \
87
+ :note => "Boxen",
88
+ :scopes => %w(repo user),
89
+ :headers => headers
47
90
  end
48
91
 
49
- # Okay, the basic creds are good, let's deal with an OAuth token.
92
+ config.token = auth.token
50
93
 
51
- unless auth = config.api.authorizations.detect { |a| a.note == "Boxen" }
52
- auth = config.api.create_authorization \
53
- :note => "Boxen", :scopes => %w(repo user)
94
+ unless ok?
95
+ puts
96
+ abort "Something went terribly wrong.",
97
+ "I was able to get your OAuth token, but was unable to use it."
54
98
  end
55
-
56
- # Reset the token for later.
57
-
58
- config.token = auth.token
59
99
  end
60
100
  end
Binary file
@@ -20,7 +20,7 @@ int key_exists_p(
20
20
  NULL, strlen(service), service, strlen(login), login, &len, &buf, item
21
21
  );
22
22
 
23
- if (ret == 0) {
23
+ if (ret == errSecSuccess) {
24
24
  return 0;
25
25
  } else {
26
26
  if (ret != errSecItemNotFound) {
@@ -45,7 +45,7 @@ int main(int argc, char **argv) {
45
45
  UInt32 len;
46
46
  SecKeychainItemRef item;
47
47
 
48
- if (password != NULL) {
48
+ if (password != NULL && strlen(password) != 0) {
49
49
  if (key_exists_p(service, login, &item) == 0) {
50
50
  SecKeychainItemDelete(item);
51
51
  }
@@ -59,7 +59,13 @@ int main(int argc, char **argv) {
59
59
  REPORT_KEYCHAIN_ERROR(create_key);
60
60
  return 1;
61
61
  }
62
-
62
+ } else if (password != NULL && strlen(password) == 0) {
63
+ if (key_exists_p(service, login, &item) == 0) {
64
+ OSStatus ret = SecKeychainItemDelete(item);
65
+ if (ret != errSecSuccess) {
66
+ REPORT_KEYCHAIN_ERROR(ret);
67
+ }
68
+ }
63
69
  } else {
64
70
  OSStatus find_key = SecKeychainFindGenericPassword(
65
71
  NULL, strlen(service), service, strlen(login), login, &len, &buf, &item);
@@ -96,13 +96,6 @@ class BoxenConfigTest < Boxen::Test
96
96
  assert_equal "foo", @config.name
97
97
  end
98
98
 
99
- def test_password
100
- assert_nil @config.password
101
-
102
- @config.password = "foo"
103
- assert_equal "foo", @config.password
104
- end
105
-
106
99
  def test_pretend?
107
100
  refute @config.pretend?
108
101
 
@@ -346,11 +339,10 @@ class BoxenConfigTest < Boxen::Test
346
339
  end
347
340
 
348
341
  def test_api
349
- @config.login = login = "someuser"
350
- @config.password = pass = "s3kr!7"
342
+ @config.token = token = "s3kr!7"
351
343
 
352
344
  api = Object.new
353
- Octokit::Client.expects(:new).with(:login => login, :password => pass).once.returns(api)
345
+ Octokit::Client.expects(:new).with(:login => token, :password => 'x-oauth-basic').once.returns(api)
354
346
 
355
347
  assert_equal api, @config.api
356
348
  assert_equal api, @config.api # This extra call plus the `once` on the expectation is for the ivar cache.
@@ -12,7 +12,7 @@ class BoxenFlagsTest < Boxen::Test
12
12
  expects(:homedir=).with "homedir"
13
13
  expects(:logfile=).with "logfile"
14
14
  expects(:login=).with "login"
15
- expects(:password=).with "password"
15
+ expects(:token=).with "token"
16
16
  expects(:pretend=).with true
17
17
  expects(:profile=).with true
18
18
  expects(:future_parser=).with true
@@ -26,10 +26,10 @@ class BoxenFlagsTest < Boxen::Test
26
26
  # Do our best to frob every switch.
27
27
 
28
28
  flags = Boxen::Flags.new "--debug", "--help", "--login", "login",
29
- "--no-fde", "--no-pull", "--no-issue", "--noop", "--password", "password",
29
+ "--no-fde", "--no-pull", "--no-issue", "--noop",
30
30
  "--pretend", "--profile", "--future-parser", "--report", "--projects",
31
31
  "--user", "user", "--homedir", "homedir", "--srcdir", "srcdir",
32
- "--logfile", "logfile"
32
+ "--logfile", "logfile", "--token", "token"
33
33
 
34
34
  assert_same config, flags.apply(config)
35
35
  end
@@ -141,14 +141,14 @@ class BoxenFlagsTest < Boxen::Test
141
141
  assert_equal %w(foo), config.args
142
142
  end
143
143
 
144
- def test_password
145
- assert_nil flags.password
146
- assert_equal "foo", flags("--password", "foo").password
144
+ def test_token
145
+ assert_nil flags.token
146
+ assert_equal "foo", flags("--token", "foo").token
147
147
  end
148
148
 
149
- def test_password_missing_value
149
+ def test_token_missing_value
150
150
  ex = assert_raises Boxen::Error do
151
- flags "--password"
151
+ flags "--token"
152
152
  end
153
153
 
154
154
  assert_match "missing argument", ex.message
@@ -31,6 +31,7 @@ class BoxenHookGitHubIssueTest < Boxen::Test
31
31
  @config.stubs(:stealth?).returns(true)
32
32
  @config.stubs(:pretend?).returns(true)
33
33
  @checkout.stubs(:master?).returns(false)
34
+ @config.stubs(:login).returns(nil)
34
35
 
35
36
  refute @hook.perform?
36
37
 
@@ -44,6 +45,12 @@ class BoxenHookGitHubIssueTest < Boxen::Test
44
45
  refute @hook.perform?
45
46
 
46
47
  @checkout.stubs(:master?).returns(true)
48
+ refute @hook.perform?
49
+
50
+ @config.stubs(:login).returns('')
51
+ refute @hook.perform?
52
+
53
+ @config.stubs(:login).returns('somelogin')
47
54
  assert @hook.perform?
48
55
  end
49
56
 
@@ -3,26 +3,22 @@ require "boxen/keychain"
3
3
 
4
4
  class BoxenKeychainTest < Boxen::Test
5
5
  def setup
6
- @keychain = Boxen::Keychain.new('test')
6
+ @keychain = Boxen::Keychain.new('test') if osx?
7
7
  end
8
8
 
9
- def test_get_password
10
- @keychain.expects(:get).with('GitHub Password').returns('foobar')
11
- assert_equal 'foobar', @keychain.password
12
- end
13
-
14
- def test_set_password
15
- @keychain.expects(:set).with('GitHub Password', 'foobar').returns('foobar')
16
- assert_equal 'foobar', @keychain.password=('foobar')
9
+ def osx?
10
+ RUBY_PLATFORM.downcase.include?("darwin")
17
11
  end
18
12
 
19
13
  def test_get_token
14
+ return skip("Keychain helper is OSX only") unless osx?
20
15
  @keychain.expects(:get).with('GitHub API Token').returns('foobar')
21
16
  assert_equal 'foobar', @keychain.token
22
17
  end
23
18
 
24
19
  def test_set_token
20
+ return skip("Keychain helper is OSX only") unless osx?
25
21
  @keychain.expects(:set).with('GitHub API Token', 'foobar').returns('foobar')
26
22
  assert_equal 'foobar', @keychain.token=('foobar')
27
23
  end
28
- end
24
+ end
@@ -0,0 +1,41 @@
1
+ require 'boxen/test'
2
+ require 'boxen/preflight/creds'
3
+
4
+ class BoxenPreflightCredsTest < Boxen::Test
5
+ def setup
6
+ @config = Boxen::Config.new do |c|
7
+ c.user = 'mojombo'
8
+ c.token = 'sekr3t!'
9
+ end
10
+ end
11
+
12
+ def test_basic
13
+ preflight = Boxen::Preflight::Creds.new @config
14
+
15
+ error = Octokit::Unauthorized.new
16
+ @config.api.stubs(:user).raises(error)
17
+
18
+ refute preflight.ok?
19
+ end
20
+
21
+ def test_basic_with_otp_challenge
22
+ preflight = Boxen::Preflight::Creds.new @config
23
+
24
+ blank_opt = {:headers => {}}
25
+ good_otp = {:headers => {"X-GitHub-OTP" => "123456"}}
26
+
27
+ error = Octokit::Unauthorized.new
28
+ error.stubs(:message).returns("OTP")
29
+
30
+ preflight.tmp_api.expects(:authorizations).with(blank_opt).raises(error)
31
+ preflight.tmp_api.expects(:authorizations).with(good_otp).returns([])
32
+ preflight.tmp_api.expects(:create_authorization).raises(error)
33
+
34
+ preflight.expects(:warn)
35
+ HighLine.any_instance.expects(:ask).returns("123456")
36
+
37
+ preflight.get_tokens
38
+ assert_equal "123456", preflight.otp
39
+ end
40
+
41
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boxen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.2
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-07-19 00:00:00.000000000 Z
13
+ date: 2013-09-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: ansi
@@ -105,7 +105,7 @@ dependencies:
105
105
  requirements:
106
106
  - - ~>
107
107
  - !ruby/object:Gem::Version
108
- version: '1.15'
108
+ version: 2.0.0
109
109
  type: :runtime
110
110
  prerelease: false
111
111
  version_requirements: !ruby/object:Gem::Requirement
@@ -113,7 +113,7 @@ dependencies:
113
113
  requirements:
114
114
  - - ~>
115
115
  - !ruby/object:Gem::Version
116
- version: '1.15'
116
+ version: 2.0.0
117
117
  - !ruby/object:Gem::Dependency
118
118
  name: puppet
119
119
  requirement: !ruby/object:Gem::Requirement
@@ -223,6 +223,7 @@ files:
223
223
  - test/boxen_keychain_test.rb
224
224
  - test/boxen_postflight_active_test.rb
225
225
  - test/boxen_postflight_env_test.rb
226
+ - test/boxen_preflight_creds_test.rb
226
227
  - test/boxen_preflight_etc_my_cnf_test.rb
227
228
  - test/boxen_preflight_homebrew_test.rb
228
229
  - test/boxen_preflight_rvm_test.rb
@@ -271,6 +272,7 @@ test_files:
271
272
  - test/boxen_keychain_test.rb
272
273
  - test/boxen_postflight_active_test.rb
273
274
  - test/boxen_postflight_env_test.rb
275
+ - test/boxen_preflight_creds_test.rb
274
276
  - test/boxen_preflight_etc_my_cnf_test.rb
275
277
  - test/boxen_preflight_homebrew_test.rb
276
278
  - test/boxen_preflight_rvm_test.rb
@@ -282,3 +284,4 @@ test_files:
282
284
  - test/fixtures/repo/modules/projects/manifests/first-project.pp
283
285
  - test/fixtures/repo/modules/projects/manifests/second-project.pp
284
286
  - test/system_timer.rb
287
+ has_rdoc: