boxen 1.5.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: