diff -Nru gist-5.0.0/debian/changelog gist-5.0.0/debian/changelog --- gist-5.0.0/debian/changelog 2020-02-26 02:35:35.000000000 +0200 +++ gist-5.0.0/debian/changelog 2021-09-17 09:03:16.000000000 +0300 @@ -1,3 +1,17 @@ +gist (5.0.0-4ubuntu1) focal; urgency=medium + + * Update workflows for authentication and authorization, + due to GitHub endpoint deprecations (LP: #1940907): + - d/patches/auth-header: Add patch. + Use header-based authentication, instead of query-parameter-based. + - d/patches/auth-oauth: Add patch. + Use web OAuth flow, instead of removed Authorizations endpoints. + - d/patches/webmock: Remove patch. + Upstream change (in auth-oauth patch) incorporates the diff. + - d/patches/series: Add two patches, remove one patch. + + -- Valters Jansons Fri, 17 Sep 2021 09:03:16 +0300 + gist (5.0.0-4) unstable; urgency=medium * Team upload. diff -Nru gist-5.0.0/debian/patches/auth-header gist-5.0.0/debian/patches/auth-header --- gist-5.0.0/debian/patches/auth-header 1970-01-01 03:00:00.000000000 +0300 +++ gist-5.0.0/debian/patches/auth-header 2021-09-17 08:44:18.000000000 +0300 @@ -0,0 +1,173 @@ +Description: Supply access token through Authorization header + Must specify access token via Authorization header. Instead of + through query parameter, since that method is no longer valid. + . + https://developer.github.com/changes/2019-11-05-deprecated-passwords-and-authorizations-api/#authenticating-using-query-parameters +Origin: https://github.com/defunkt/gist/commit/635b1437a513e9a13367827ee3f74fbbdaa54aa8 +Author: Andrew Mayorov +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1940907 +Applied-Upstream: 5.1.0 + +--- +--- a/build/gist ++++ b/build/gist +@@ -1442,9 +1442,9 @@ + + url = "#{base_path}/gists" + url << "/" << CGI.escape(existing_gist) if existing_gist.to_s != '' +- url << "?access_token=" << CGI.escape(access_token) if access_token.to_s != '' + + request = Net::HTTP::Post.new(url) ++ request['Authorization'] = "token #{access_token}" if access_token.to_s != '' + request.body = JSON.dump(json) + request.content_type = 'application/json' + +@@ -1480,9 +1480,10 @@ + if user == "" + access_token = auth_token() + if access_token.to_s != '' +- url << "/gists?access_token=" << CGI.escape(access_token) ++ url << "/gists" + + request = Net::HTTP::Get.new(url) ++ request['Authorization'] = "token #{access_token}" + response = http(api_url, request) + + pretty_gist(response) +@@ -1507,8 +1508,8 @@ + if user == "" + access_token = auth_token() + if access_token.to_s != '' +- url << "/gists?per_page=100&access_token=" << CGI.escape(access_token) +- get_gist_pages(url) ++ url << "/gists?per_page=100" ++ get_gist_pages(url, access_token) + else + raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists." + end +@@ -1524,11 +1525,9 @@ + url = "#{base_path}/gists/#{id}" + + access_token = auth_token() +- if access_token.to_s != '' +- url << "?access_token=" << CGI.escape(access_token) +- end + + request = Net::HTTP::Get.new(url) ++ request['Authorization'] = "token #{access_token}" if access_token.to_s != '' + response = http(api_url, request) + + if response.code == '200' +@@ -1554,9 +1553,8 @@ + + access_token = auth_token() + if access_token.to_s != '' +- url << "?access_token=" << CGI.escape(access_token) +- + request = Net::HTTP::Delete.new(url) ++ request["Authorization"] = "token #{access_token}" + response = http(api_url, request) + else + raise Error, "Not authenticated. Use 'gist --login' to login." +@@ -1569,9 +1567,10 @@ + end + end + +- def get_gist_pages(url) ++ def get_gist_pages(url, access_token = "") + + request = Net::HTTP::Get.new(url) ++ request['Authorization'] = "token #{access_token}" if access_token.to_s != '' + response = http(api_url, request) + pretty_gist(response) + +@@ -1579,7 +1578,7 @@ + + if link_header + links = Hash[ link_header.gsub(/(<|>|")/, "").split(',').map { |link| link.split('; rel=') } ].invert +- get_gist_pages(links['next']) if links['next'] ++ get_gist_pages(links['next'], access_token) if links['next'] + end + + end +--- a/lib/gist.rb ++++ b/lib/gist.rb +@@ -136,9 +136,9 @@ + + url = "#{base_path}/gists" + url << "/" << CGI.escape(existing_gist) if existing_gist.to_s != '' +- url << "?access_token=" << CGI.escape(access_token) if access_token.to_s != '' + + request = Net::HTTP::Post.new(url) ++ request['Authorization'] = "token #{access_token}" if access_token.to_s != '' + request.body = JSON.dump(json) + request.content_type = 'application/json' + +@@ -174,9 +174,10 @@ + if user == "" + access_token = auth_token() + if access_token.to_s != '' +- url << "/gists?access_token=" << CGI.escape(access_token) ++ url << "/gists" + + request = Net::HTTP::Get.new(url) ++ request['Authorization'] = "token #{access_token}" + response = http(api_url, request) + + pretty_gist(response) +@@ -201,8 +202,8 @@ + if user == "" + access_token = auth_token() + if access_token.to_s != '' +- url << "/gists?per_page=100&access_token=" << CGI.escape(access_token) +- get_gist_pages(url) ++ url << "/gists?per_page=100" ++ get_gist_pages(url, access_token) + else + raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists." + end +@@ -218,11 +219,9 @@ + url = "#{base_path}/gists/#{id}" + + access_token = auth_token() +- if access_token.to_s != '' +- url << "?access_token=" << CGI.escape(access_token) +- end + + request = Net::HTTP::Get.new(url) ++ request['Authorization'] = "token #{access_token}" if access_token.to_s != '' + response = http(api_url, request) + + if response.code == '200' +@@ -248,9 +247,8 @@ + + access_token = auth_token() + if access_token.to_s != '' +- url << "?access_token=" << CGI.escape(access_token) +- + request = Net::HTTP::Delete.new(url) ++ request["Authorization"] = "token #{access_token}" + response = http(api_url, request) + else + raise Error, "Not authenticated. Use 'gist --login' to login." +@@ -263,9 +261,10 @@ + end + end + +- def get_gist_pages(url) ++ def get_gist_pages(url, access_token = "") + + request = Net::HTTP::Get.new(url) ++ request['Authorization'] = "token #{access_token}" if access_token.to_s != '' + response = http(api_url, request) + pretty_gist(response) + +@@ -273,7 +272,7 @@ + + if link_header + links = Hash[ link_header.gsub(/(<|>|")/, "").split(',').map { |link| link.split('; rel=') } ].invert +- get_gist_pages(links['next']) if links['next'] ++ get_gist_pages(links['next'], access_token) if links['next'] + end + + end diff -Nru gist-5.0.0/debian/patches/auth-oauth gist-5.0.0/debian/patches/auth-oauth --- gist-5.0.0/debian/patches/auth-oauth 1970-01-01 03:00:00.000000000 +0300 +++ gist-5.0.0/debian/patches/auth-oauth 2021-09-17 09:03:16.000000000 +0300 @@ -0,0 +1,296 @@ +Description: Use new Oauth flow + The username/password exchange mechanism is (rightfully) + deprecated, the device flow is now in beta, and seems to be + the perfect replacement. + . + As of November 2020, the password flow has been disabled, + leaving login functionality as unusable without the patch. + . + https://developer.github.com/changes/2020-02-14-deprecating-oauth-auth-endpoint/ +Origin: https://github.com/defunkt/gist/commit/894693a22e025320d1007a21856d37ee7a8c7831 +Author: Conrad Irwin +Bug: https://github.com/defunkt/gist/issues/315 +Bug-Ubuntu: https://bugs.launchpad.net/bugs/1940907 +Applied-Upstream: 6.0.0 + +--- +--- a/build/gist ++++ b/build/gist +@@ -1329,12 +1329,16 @@ + } + + GITHUB_API_URL = URI("https://api.github.com/") ++ GITHUB_URL = URI("https://github.com/") + GIT_IO_URL = URI("https://git.io") + + GITHUB_BASE_PATH = "" + GHE_BASE_PATH = "/api/v3" + ++ GITHUB_CLIENT_ID = '4f7ec0d4eab38e74384e' ++ + URL_ENV_NAME = "GITHUB_URL" ++ CLIENT_ID_ENV_NAME = "GIST_CLIENT_ID" + + USER_AGENT = "gist/#{VERSION} (Net::HTTP, #{RUBY_DESCRIPTION})" + +@@ -1642,15 +1646,71 @@ + + # Log the user into gist. + # ++ def login!(credentials={}) ++ if (login_url == GITHUB_URL || ENV.key?(CLIENT_ID_ENV_NAME)) && credentials.empty? && !ENV.key?('GIST_USE_USERNAME_AND_PASSWORD') ++ device_flow_login! ++ else ++ access_token_login!(credentials) ++ end ++ end ++ ++ def device_flow_login! ++ puts "Requesting login parameters..." ++ request = Net::HTTP::Post.new("/login/device/code") ++ request.body = JSON.dump({ ++ :scope => 'gist', ++ :client_id => client_id, ++ }) ++ request.content_type = 'application/json' ++ request['accept'] = "application/json" ++ response = http(login_url, request) ++ ++ if response.code != '200' ++ raise Error, "HTTP #{response.code}: #{response.body}" ++ end ++ ++ body = JSON.parse(response.body) ++ ++ puts "Please sign in at #{body['verification_uri']}" ++ puts " and enter code: #{body['user_code']}" ++ device_code = body['device_code'] ++ interval = body['interval'] ++ ++ loop do ++ sleep(interval.to_i) ++ request = Net::HTTP::Post.new("/login/oauth/access_token") ++ request.body = JSON.dump({ ++ :client_id => client_id, ++ :grant_type => 'urn:ietf:params:oauth:grant-type:device_code', ++ :device_code => device_code ++ }) ++ request.content_type = 'application/json' ++ request['Accept'] = 'application/json' ++ response = http(login_url, request) ++ if response.code != '200' ++ raise Error, "HTTP #{response.code}: #{response.body}" ++ end ++ body = JSON.parse(response.body) ++ break unless body['error'] == 'authorization_pending' ++ end ++ ++ if body['error'] ++ raise Error, body['error_description'] ++ end ++ ++ AuthTokenFile.write JSON.parse(response.body)['access_token'] ++ ++ puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/connections/applications/#{client_id}" ++ end ++ ++ # Logs the user into gist. ++ # + # This method asks the user for a username and password, and tries to obtain + # and OAuth2 access token, which is then stored in ~/.gist + # + # @raise [Gist::Error] if something went wrong +- # @param [Hash] credentials login details +- # @option credentials [String] :username +- # @option credentials [String] :password + # @see http://developer.github.com/v3/oauth/ +- def login!(credentials={}) ++ def access_token_login!(credentials={}) + puts "Obtaining OAuth2 access_token from github." + loop do + print "GitHub username: " +@@ -1861,11 +1921,19 @@ + ENV.key?(URL_ENV_NAME) ? GHE_BASE_PATH : GITHUB_BASE_PATH + end + ++ def login_url ++ ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_URL ++ end ++ + # Get the API URL + def api_url + ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_API_URL + end + ++ def client_id ++ ENV.key?(CLIENT_ID_ENV_NAME) ? URI(ENV[CLIENT_ID_ENV_NAME]) : GITHUB_CLIENT_ID ++ end ++ + def legacy_private_gister? + return unless which('git') + `git config --global gist.private` =~ /\Ayes|1|true|on\z/i +--- a/lib/gist.rb ++++ b/lib/gist.rb +@@ -23,12 +23,16 @@ + } + + GITHUB_API_URL = URI("https://api.github.com/") ++ GITHUB_URL = URI("https://github.com/") + GIT_IO_URL = URI("https://git.io") + + GITHUB_BASE_PATH = "" + GHE_BASE_PATH = "/api/v3" + ++ GITHUB_CLIENT_ID = '4f7ec0d4eab38e74384e' ++ + URL_ENV_NAME = "GITHUB_URL" ++ CLIENT_ID_ENV_NAME = "GIST_CLIENT_ID" + + USER_AGENT = "gist/#{VERSION} (Net::HTTP, #{RUBY_DESCRIPTION})" + +@@ -336,15 +340,71 @@ + + # Log the user into gist. + # ++ def login!(credentials={}) ++ if (login_url == GITHUB_URL || ENV.key?(CLIENT_ID_ENV_NAME)) && credentials.empty? && !ENV.key?('GIST_USE_USERNAME_AND_PASSWORD') ++ device_flow_login! ++ else ++ access_token_login!(credentials) ++ end ++ end ++ ++ def device_flow_login! ++ puts "Requesting login parameters..." ++ request = Net::HTTP::Post.new("/login/device/code") ++ request.body = JSON.dump({ ++ :scope => 'gist', ++ :client_id => client_id, ++ }) ++ request.content_type = 'application/json' ++ request['accept'] = "application/json" ++ response = http(login_url, request) ++ ++ if response.code != '200' ++ raise Error, "HTTP #{response.code}: #{response.body}" ++ end ++ ++ body = JSON.parse(response.body) ++ ++ puts "Please sign in at #{body['verification_uri']}" ++ puts " and enter code: #{body['user_code']}" ++ device_code = body['device_code'] ++ interval = body['interval'] ++ ++ loop do ++ sleep(interval.to_i) ++ request = Net::HTTP::Post.new("/login/oauth/access_token") ++ request.body = JSON.dump({ ++ :client_id => client_id, ++ :grant_type => 'urn:ietf:params:oauth:grant-type:device_code', ++ :device_code => device_code ++ }) ++ request.content_type = 'application/json' ++ request['Accept'] = 'application/json' ++ response = http(login_url, request) ++ if response.code != '200' ++ raise Error, "HTTP #{response.code}: #{response.body}" ++ end ++ body = JSON.parse(response.body) ++ break unless body['error'] == 'authorization_pending' ++ end ++ ++ if body['error'] ++ raise Error, body['error_description'] ++ end ++ ++ AuthTokenFile.write JSON.parse(response.body)['access_token'] ++ ++ puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/connections/applications/#{client_id}" ++ end ++ ++ # Logs the user into gist. ++ # + # This method asks the user for a username and password, and tries to obtain + # and OAuth2 access token, which is then stored in ~/.gist + # + # @raise [Gist::Error] if something went wrong +- # @param [Hash] credentials login details +- # @option credentials [String] :username +- # @option credentials [String] :password + # @see http://developer.github.com/v3/oauth/ +- def login!(credentials={}) ++ def access_token_login!(credentials={}) + puts "Obtaining OAuth2 access_token from github." + loop do + print "GitHub username: " +@@ -555,11 +615,19 @@ + ENV.key?(URL_ENV_NAME) ? GHE_BASE_PATH : GITHUB_BASE_PATH + end + ++ def login_url ++ ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_URL ++ end ++ + # Get the API URL + def api_url + ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_API_URL + end + ++ def client_id ++ ENV.key?(CLIENT_ID_ENV_NAME) ? URI(ENV[CLIENT_ID_ENV_NAME]) : GITHUB_CLIENT_ID ++ end ++ + def legacy_private_gister? + return unless which('git') + `git config --global gist.private` =~ /\Ayes|1|true|on\z/i +--- a/spec/ghe_spec.rb ++++ b/spec/ghe_spec.rb +@@ -5,10 +5,10 @@ + MOCK_USER = 'foo' + MOCK_PASSWORD = 'bar' + +- MOCK_AUTHZ_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_USER}:#{MOCK_PASSWORD}@#{MOCK_GHE_HOST}/api/v3/" ++ MOCK_AUTHZ_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_GHE_HOST}/api/v3/" + MOCK_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_GHE_HOST}/api/v3/" +- MOCK_AUTHZ_GITHUB_URL = "https://#{MOCK_USER}:#{MOCK_PASSWORD}@api.github.com/" + MOCK_GITHUB_URL = "https://api.github.com/" ++ MOCK_LOGIN_URL = "https://github.com/" + + before do + @saved_env = ENV[Gist::URL_ENV_NAME] +@@ -20,8 +20,15 @@ + # stub requests for /authorizations + stub_request(:post, /#{MOCK_AUTHZ_GHE_URL}authorizations/). + to_return(:status => 201, :body => '{"token": "asdf"}') +- stub_request(:post, /#{MOCK_AUTHZ_GITHUB_URL}authorizations/). ++ stub_request(:post, /#{MOCK_GITHUB_URL}authorizations/). ++ with(headers: {'Authorization'=>'Basic Zm9vOmJhcg=='}). + to_return(:status => 201, :body => '{"token": "asdf"}') ++ ++ stub_request(:post, /#{MOCK_LOGIN_URL}login\/device\/code/). ++ to_return(:status => 200, :body => '{"interval": "0.1", "user_code":"XXXX-XXXX", "device_code": "xxxx", "verification_uri": "https://github.com/login/device"}') ++ ++ stub_request(:post, /#{MOCK_LOGIN_URL}login\/oauth\/access_token/). ++ to_return(:status => 200, :body => '{"access_token":"zzzz"}') + end + + after do +@@ -48,7 +55,7 @@ + + Gist.login! + +- assert_requested(:post, /#{MOCK_AUTHZ_GITHUB_URL}authorizations/) ++ assert_requested(:post, /#{MOCK_LOGIN_URL}login\/oauth\/access_token/) + end + + it "should access to #{MOCK_GHE_HOST} when $#{Gist::URL_ENV_NAME} was set" do +@@ -65,7 +72,7 @@ + $stdin = StringIO.new "#{MOCK_USER}_wrong\n#{MOCK_PASSWORD}_wrong\n" + Gist.login! :username => MOCK_USER, :password => MOCK_PASSWORD + +- assert_requested(:post, /#{MOCK_AUTHZ_GITHUB_URL}authorizations/) ++ assert_requested(:post, /#{MOCK_GITHUB_URL}authorizations/) + end + + end diff -Nru gist-5.0.0/debian/patches/series gist-5.0.0/debian/patches/series --- gist-5.0.0/debian/patches/series 2020-02-26 02:35:35.000000000 +0200 +++ gist-5.0.0/debian/patches/series 2021-09-17 09:00:34.000000000 +0300 @@ -1,4 +1,5 @@ rename -webmock gemspec-no-git mochafix +auth-header +auth-oauth diff -Nru gist-5.0.0/debian/patches/webmock gist-5.0.0/debian/patches/webmock --- gist-5.0.0/debian/patches/webmock 2020-02-26 02:35:35.000000000 +0200 +++ gist-5.0.0/debian/patches/webmock 1970-01-01 03:00:00.000000000 +0300 @@ -1,25 +0,0 @@ -From: Christian Hofstaedtler -Date: Tue, 11 Jul 2017 19:57:07 +0000 -Subject: Support building with current webmock - -Bug-Debian: https://bugs.debian.org/867646 ---- - spec/ghe_spec.rb | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/spec/ghe_spec.rb b/spec/ghe_spec.rb -index 5d88a2f..743cc3e 100644 ---- a/spec/ghe_spec.rb -+++ b/spec/ghe_spec.rb -@@ -5,9 +5,9 @@ describe '...' do - MOCK_USER = 'foo' - MOCK_PASSWORD = 'bar' - -- MOCK_AUTHZ_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_USER}:#{MOCK_PASSWORD}@#{MOCK_GHE_HOST}/api/v3/" -+ MOCK_AUTHZ_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_GHE_HOST}/api/v3/" - MOCK_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_GHE_HOST}/api/v3/" -- MOCK_AUTHZ_GITHUB_URL = "https://#{MOCK_USER}:#{MOCK_PASSWORD}@api.github.com/" -+ MOCK_AUTHZ_GITHUB_URL = "https://api.github.com/" - MOCK_GITHUB_URL = "https://api.github.com/" - - before do