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 <conrad.irwin@gmail.com>
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
