From 55b58054cb080618062ec69c5a4080a08bb4d7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Mon, 22 Dec 2014 15:14:47 -0800 Subject: [PATCH] Stop trying to re-use existing Access Tokens for hub Due to upcoming GitHub API changes, the "token" field will not be accessible anymore, which will make it impossible for hub to re-use an existing token that might have been generated for another computer. Now, a new Access Token will be created every time hub needs to exchange the username/password credentials for an OAuth token. https://developer.github.com/changes/2014-12-08-removing-authorizations-token/ --- features/authentication.feature | 145 ++------------------------------ lib/hub/github_api.rb | 37 ++++---- 2 files changed, 22 insertions(+), 160 deletions(-) diff --git a/features/authentication.feature b/features/authentication.feature index fb8cd21a..a3e7e519 100644 --- a/features/authentication.feature +++ b/features/authentication.feature @@ -5,10 +5,11 @@ Feature: OAuth authentication Scenario: Ask for username & password, create authorization Given the GitHub API server: """ - get('/authorizations') { '[]' } post('/authorizations') { assert_basic_auth 'mislav', 'kitty' - assert :scopes => ['repo'] + assert :scopes => ['repo'], + :note => 'hub', + :note_url => 'http://hub.github.com/' json :token => 'OTOKEN' } get('/user') { @@ -30,100 +31,12 @@ Feature: OAuth authentication And the file "../home/.config/hub" should contain "oauth_token: OTOKEN" And the file "../home/.config/hub" should have mode "0600" - Scenario: Ask for username & password, re-use existing authorization - Given the GitHub API server: - """ - get('/authorizations') { - assert_basic_auth 'mislav', 'kitty' - json [ - {:token => 'SKIPPD', :note_url => 'http://example.com'}, - {:token => 'OTOKEN', :note_url => 'http://hub.github.com/'} - ] - } - get('/user') { - json :login => 'mislav' - } - post('/user/repos') { - json :full_name => 'mislav/dotfiles' - } - """ - When I run `hub create` interactively - When I type "mislav" - And I type "kitty" - Then the output should contain "github.com password for mislav (never stored):" - And the exit status should be 0 - And the file "../home/.config/hub" should contain "oauth_token: OTOKEN" - - Scenario: Re-use existing authorization with an old URL - Given the GitHub API server: - """ - get('/authorizations') { - assert_basic_auth 'mislav', 'kitty' - json [ - {:token => 'OTOKEN', :note => 'hub', :note_url => 'http://defunkt.io/hub/'} - ] - } - post('/authorizations') { - status 422 - json :message => "Validation Failed", - :errors => [{:resource => "OauthAccess", :code => "already_exists", :field => "description"}] - } - get('/user') { - json :login => 'mislav' - } - post('/user/repos') { - json :full_name => 'mislav/dotfiles' - } - """ - When I run `hub create` interactively - When I type "mislav" - And I type "kitty" - Then the output should contain "github.com password for mislav (never stored):" - And the exit status should be 0 - And the file "../home/.config/hub" should contain "oauth_token: OTOKEN" - - Scenario: Re-use existing authorization found on page 3 - Given the GitHub API server: - """ - get('/authorizations') { - assert_basic_auth 'mislav', 'kitty' - page = (params[:page] || 1).to_i - if page < 3 - response.headers['Link'] = %(<#{url}?page=#{page+1}>; rel="next") - json [] - else - json [ - {:token => 'OTOKEN', :note => 'hub', :note_url => 'http://hub.github.com/'} - ] - end - } - post('/authorizations') { - status 422 - json :message => "Validation Failed", - :errors => [{:resource => "OauthAccess", :code => "already_exists", :field => "description"}] - } - get('/user') { - json :login => 'mislav' - } - post('/user/repos') { - json :full_name => 'mislav/dotfiles' - } - """ - When I run `hub create` interactively - When I type "mislav" - And I type "kitty" - Then the output should contain "github.com password for mislav (never stored):" - And the exit status should be 0 - And the file "../home/.config/hub" should contain "oauth_token: OTOKEN" - Scenario: Credentials from GITHUB_USER & GITHUB_PASSWORD Given the GitHub API server: """ - get('/authorizations') { + post('/authorizations') { assert_basic_auth 'mislav', 'kitty' - json [ - {:token => 'OTOKEN', :note_url => 'http://hub.github.com/'} - ] + json :token => 'OTOKEN' } get('/user') { json :login => 'mislav' @@ -141,7 +54,7 @@ Feature: OAuth authentication Scenario: Wrong password Given the GitHub API server: """ - get('/authorizations') { + post('/authorizations') { assert_basic_auth 'mislav', 'kitty' } """ @@ -155,17 +68,8 @@ Feature: OAuth authentication Scenario: Two-factor authentication, create authorization Given the GitHub API server: """ - get('/authorizations') { - assert_basic_auth 'mislav', 'kitty' - if request.env['HTTP_X_GITHUB_OTP'] != "112233" - response.headers['X-GitHub-OTP'] = "required;application" - halt 401 - end - json [ ] - } post('/authorizations') { assert_basic_auth 'mislav', 'kitty' - halt 412 unless params[:scopes] if request.env['HTTP_X_GITHUB_OTP'] != "112233" response.headers['X-GitHub-OTP'] = "required;application" halt 401 @@ -188,46 +92,9 @@ Feature: OAuth authentication And the exit status should be 0 And the file "../home/.config/hub" should contain "oauth_token: OTOKEN" - Scenario: Two-factor authentication, re-use existing authorization - Given the GitHub API server: - """ - token = 'OTOKEN' - post('/authorizations') { - assert_basic_auth 'mislav', 'kitty' - token << 'SMS' - status 412 - } - get('/authorizations') { - assert_basic_auth 'mislav', 'kitty' - if request.env['HTTP_X_GITHUB_OTP'] != "112233" - response.headers['X-GitHub-OTP'] = "required;application" - halt 401 - end - json [ { - :token => token, - :note_url => 'http://hub.github.com/' - } ] - } - get('/user') { - json :login => 'mislav' - } - post('/user/repos') { - json :full_name => 'mislav/dotfiles' - } - """ - When I run `hub create` interactively - When I type "mislav" - And I type "kitty" - And I type "112233" - Then the output should contain "github.com password for mislav (never stored):" - Then the output should contain "two-factor authentication code:" - And the exit status should be 0 - And the file "../home/.config/hub" should contain "oauth_token: OTOKENSMS" - Scenario: Special characters in username & password Given the GitHub API server: """ - get('/authorizations') { '[]' } post('/authorizations') { assert_basic_auth 'mislav@example.com', 'my pass@phrase ok?' json :token => 'OTOKEN' diff --git a/lib/hub/github_api.rb b/lib/hub/github_api.rb index 6457753f..ed229033 100644 --- a/lib/hub/github_api.rb +++ b/lib/hub/github_api.rb @@ -333,36 +333,31 @@ module Hub end end - def obtain_oauth_token host, user, two_factor_code = nil + def obtain_oauth_token host, user auth_url = URI.parse("https://%s@%s/authorizations" % [CGI.escape(user), host]) + auth_params = { + :scopes => ['repo'], + :note => 'hub', + :note_url => oauth_app_url + } + res = nil + two_factor_code = nil - # dummy request to trigger a 2FA SMS since a HTTP GET won't do it - post(auth_url) if !two_factor_code + loop do + res = post(auth_url, auth_params) do |req| + req['X-GitHub-OTP'] = two_factor_code if two_factor_code + end - # first try to fetch existing authorization - res = get_all(auth_url) do |req| - req['X-GitHub-OTP'] = two_factor_code if two_factor_code - end - unless res.success? - if !two_factor_code && res['X-GitHub-OTP'].to_s.include?('required') + if res.success? + break + elsif res.status == 401 && res['X-GitHub-OTP'].to_s.include?('required') two_factor_code = config.prompt_auth_code - return obtain_oauth_token(host, user, two_factor_code) else res.error! end end - if found = res.data.find {|auth| auth['note'] == 'hub' || auth['note_url'] == oauth_app_url } - found['token'] - else - # create a new authorization - res = post auth_url, - :scopes => %w[repo], :note => 'hub', :note_url => oauth_app_url do |req| - req['X-GitHub-OTP'] = two_factor_code if two_factor_code - end - res.error! unless res.success? - res.data['token'] - end + res.data['token'] end end -- GitLab