gist 4.6.2 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8dcb4ad16178e41476596aa05e8819ed3431e5f2
4
- data.tar.gz: 6698bd0bda9e583700e5bec695d7e6b5c1b6fba5
2
+ SHA256:
3
+ metadata.gz: 78e3d009d0e3e3a535d20e8d7e0582705c6ea61308958c3f59a4d4a2e365fa3c
4
+ data.tar.gz: 23df778298090151ce7e2b8f3dbb8f97786bc7d576f218be1aeb454123090e91
5
5
  SHA512:
6
- metadata.gz: 7ca4af1b0e24281a929ea6a39e5e67a3b2a2c5bc7aed06039adac829665f5e5d013061a65b097a3be0a68bc623c43299f5f9b148b2a28113644aff267cd86036
7
- data.tar.gz: c4bde01c49fe0182422071f8afa445b3da2af1d829b7d4f36df957eef059ddb097f4e248f232c5bebf06888e22327d7b44bb18f93cb2456965f9f03e8f031f32
6
+ metadata.gz: 70506cc45562b725085e2f10d5627a3ac4c700fa6e599e39a6625629fbd2c63903b2d42a4a95d9642f0ac01893bd8de27d72acb7e4622eaa44ab4ea96b08840c
7
+ data.tar.gz: b3476f46cb844603a169e3a67fae3bb37e5399494ddec6c78178f4bb664a90cda642c5be780ea007d12e96cc96312f695b86827872bc4947603c092a4e3d6774
data/README.md CHANGED
@@ -21,6 +21,11 @@ upload content to https://gist.github.com/.
21
21
 
22
22
  brew install gist
23
23
 
24
+ ‌For FreeBSD, gist lives in ports
25
+
26
+ pkg install gist
27
+
28
+
24
29
  ## Command
25
30
 
26
31
  ‌To upload the contents of `a.rb` just:
@@ -79,12 +84,29 @@ To read a gist and print it to STDOUT
79
84
 
80
85
  ## Login
81
86
 
82
- If you want to associate your gists with your GitHub account, you need to login
83
- with gist. It doesn't store your username and password, it just uses them to get
84
- an OAuth2 token (with the "gist" permission).
87
+ Before you use `gist` for the first time you will need to log in. There are two supported login flows:
88
+
89
+ 1. The Github device-code Oauth flow. This is the default for authenticating to github.com, and can be enabled for Github Enterprise by creating an Oauth app, and exporting the environment variable `GIST_CLIENT_ID` with the client id of the Oauth app.
90
+ 2. The (deprecated) username and password token exchange flow. This is the default for GitHub Enterprise, and can be used to log into github.com by exporting the environment variable `GIST_USE_USERNAME_AND_PASSWORD`.
91
+
92
+ ### The device-code flow
93
+
94
+ This flow allows you to obtain a token by logging into GitHub in the browser and typing a verification code. This is the preferred mechanism.
95
+
96
+ gist --login
97
+ Requesting login parameters...
98
+ Please sign in at https://github.com/login/device
99
+ and enter code: XXXX-XXXX
100
+ Success! https://github.com/settings/connections/applications/4f7ec0d4eab38e74384e
101
+
102
+ The returned access_token is stored in `~/.gist` and used for all future gisting. If you need to you can revoke access from https://github.com/settings/connections/applications/4f7ec0d4eab38e74384e.
103
+
104
+ ### The username-password flow
105
+
106
+ This flow asks for your GitHub username and password (and 2FA code), and exchanges them for a token with the "gist" permission (your username and password are not stored). This mechanism is deprecated by GitHub, but may still work with GitHub Enterprise.
85
107
 
86
108
  gist --login
87
- Obtaining OAuth2 access_token from github.
109
+ Obtaining OAuth2 access_token from GitHub.
88
110
  GitHub username: ConradIrwin
89
111
  GitHub password:
90
112
  2-factor auth code:
@@ -94,22 +116,21 @@ This token is stored in `~/.gist` and used for all future gisting. If you need t
94
116
  you can revoke it from https://github.com/settings/tokens, or just delete the
95
117
  file.
96
118
 
97
- ‌After you've done this, you can still upload gists anonymously with `-a`.
98
-
99
- gist -a a.rb
100
-
101
119
  #### Password-less login
102
120
 
103
121
  If you have a complicated authorization requirement you can manually create a
104
- token file by pasting a Github token with only the `gist` permission into a
105
- file called `~/.gist`. You can create one from https://github.com/settings/tokens
122
+ token file by pasting a GitHub token with `gist` scope (and maybe the `user:email`
123
+ for GitHub Enterprise) into a file called `~/.gist`. You can create one from
124
+ https://github.com/settings/tokens
106
125
 
107
126
  This file should contain only the token (~40 hex characters), and to make it
108
- easier to edit, can optionally have a final newline (\n or \r\n).
127
+ easier to edit, can optionally have a final newline (`\n` or `\r\n`).
109
128
 
110
129
  For example, one way to create this file would be to run:
111
130
 
112
- echo MY_SECRET_TOKEN > ~/.gist
131
+ (umask 0077 && echo MY_SECRET_TOKEN > ~/.gist)
132
+
133
+ The `umask` ensures that the file is only accessible from your user account.
113
134
 
114
135
  ### GitHub Enterprise
115
136
 
@@ -119,7 +140,7 @@ you need to export the `GITHUB_URL` environment variable (usually done in your `
119
140
  export GITHUB_URL=http://github.internal.example.com/
120
141
 
121
142
  Once you've done this and restarted your terminal (or run `source ~/.bashrc`), gist will
122
- automatically use github enterprise instead of the public github.com
143
+ automatically use GitHub Enterprise instead of the public github.com
123
144
 
124
145
  Your token for GitHub Enterprise will be stored in `.gist.<protocol>.<server.name>[.<port>]` (e.g.
125
146
  `~/.gist.http.github.internal.example.com` for the GITHUB_URL example above) instead of `~/.gist`.
@@ -150,11 +171,10 @@ If you need more advanced features you can also pass:
150
171
  * `:public` if you want your gist to have a guessable url.
151
172
  * `:description` to add a description to your gist.
152
173
  * `:update` to update an existing gist (can be a URL or an id).
153
- * `:anonymous` to submit an anonymous gist (default is false).
154
174
  * `:copy` to copy the resulting URL to the clipboard (default is false).
155
175
  * `:open` to open the resulting URL in a browser (default is false).
156
176
 
157
- NOTE: The access_token must have the "gist" scope.
177
+ NOTE: The access_token must have the `gist` scope and may also require the `user:email` scope.
158
178
 
159
179
  ‌If you want to upload multiple files in the same gist, you can:
160
180
 
data/bin/gist CHANGED
@@ -24,16 +24,12 @@ specified STDIN will be read. The default filename for STDIN is "a.rb", and all
24
24
  filenames can be overridden by repeating the "-f" flag. The most useful reason
25
25
  to do this is to change the syntax highlighting.
26
26
 
27
- If you'd like your gists to be associated with your GitHub account, so that you
28
- can edit them and find them in future, first use `gist --login` to obtain an
29
- Oauth2 access token. This is stored and used by gist in the future.
27
+ All gists must to be associated with a GitHub account, so you will need to login with
28
+ `gist --login` to obtain an OAuth2 access token. This is stored and used by gist in the future.
30
29
 
31
30
  Private gists do not have guessable URLs and can be created with "-p", you can
32
31
  also set the description at the top of the gist by passing "-d".
33
32
 
34
- Anonymous gists are not associated with your GitHub account, they can be created
35
- with "-a" even after you have used "gist --login".
36
-
37
33
  If you would like to shorten the resulting gist URL, use the -s flag. This will
38
34
  use GitHub's URL shortener, git.io. You can also use -R to get the link to the
39
35
  raw gist.
@@ -50,7 +46,7 @@ original gist with the same GitHub account.
50
46
  If you want to skip empty files, use the --skip-empty flag. If all files are
51
47
  empty no gist will be created.
52
48
 
53
- Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-R] [-d DESC] [-a] [-u URL]
49
+ Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-R] [-d DESC] [-u URL]
54
50
  [--skip-empty] [-P] [-f NAME|-t EXT]* FILE*
55
51
  #{executable_name} --login
56
52
  #{executable_name} [-l|-r]
@@ -92,10 +88,6 @@ Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-R] [-d DESC] [-a] [-u URL]
92
88
  options[:update] = update
93
89
  end
94
90
 
95
- opts.on("-a", "--anonymous", "Create an anonymous gist.") do
96
- options[:anonymous] = true
97
- end
98
-
99
91
  opts.on("-c", "--copy", "Copy the resulting URL to the clipboard") do
100
92
  options[:copy] = true
101
93
  end
@@ -148,6 +140,12 @@ Usage: #{executable_name} [-o|-c|-e] [-p] [-s] [-R] [-d DESC] [-a] [-u URL]
148
140
  end.parse!
149
141
 
150
142
  begin
143
+ if Gist.auth_token.nil?
144
+ puts 'Please log in with `gist --login`. ' \
145
+ '(GitHub now requires credentials to gist https://bit.ly/2GBBxKw)'
146
+ exit(1)
147
+ end
148
+
151
149
  options[:output] = if options[:embed] && options[:shorten]
152
150
  raise Gist::Error, "--embed does not make sense with --shorten"
153
151
  elsif options[:embed]
data/gist.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
9
9
  s.email = ['conrad.irwin@gmail.com', 'rkingist@sharpsaw.org']
10
10
  s.authors = ['Conrad Irwin', '☈king']
11
11
  s.license = 'MIT'
12
- s.files = `git ls-files`.split("\n")
12
+ s.files = `git ls-files`.split("\n") - Dir.glob("build/*") - [".gitignore"]
13
13
  s.require_paths = ["lib"]
14
14
 
15
15
  s.executables << 'gist'
data/lib/gist.rb CHANGED
@@ -12,7 +12,7 @@ end
12
12
  module Gist
13
13
  extend self
14
14
 
15
- VERSION = '4.6.2'
15
+ VERSION = '6.0.0'
16
16
 
17
17
  # A list of clipboard commands with copy and paste support.
18
18
  CLIPBOARD_COMMANDS = {
@@ -23,12 +23,16 @@ module Gist
23
23
  }
24
24
 
25
25
  GITHUB_API_URL = URI("https://api.github.com/")
26
+ GITHUB_URL = URI("https://github.com/")
26
27
  GIT_IO_URL = URI("https://git.io")
27
28
 
28
29
  GITHUB_BASE_PATH = ""
29
30
  GHE_BASE_PATH = "/api/v3"
30
31
 
32
+ GITHUB_CLIENT_ID = '4f7ec0d4eab38e74384e'
33
+
31
34
  URL_ENV_NAME = "GITHUB_URL"
35
+ CLIENT_ID_ENV_NAME = "GIST_CLIENT_ID"
32
36
 
33
37
  USER_AGENT = "gist/#{VERSION} (Net::HTTP, #{RUBY_DESCRIPTION})"
34
38
 
@@ -44,7 +48,7 @@ module Gist
44
48
  module AuthTokenFile
45
49
  def self.filename
46
50
  if ENV.key?(URL_ENV_NAME)
47
- File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/:/, '.').gsub(/[^a-z0-9.]/, '')}"
51
+ File.expand_path "~/.gist.#{ENV[URL_ENV_NAME].gsub(/:/, '.').gsub(/[^a-z0-9.-]/, '')}"
48
52
  else
49
53
  File.expand_path "~/.gist"
50
54
  end
@@ -108,6 +112,13 @@ module Gist
108
112
  #
109
113
  # @see http://developer.github.com/v3/gists/
110
114
  def multi_gist(files, options={})
115
+ if options[:anonymous]
116
+ raise 'Anonymous gists are no longer supported. Please log in with `gist --login`. ' \
117
+ '(GitHub now requires credentials to gist https://bit.ly/2GBBxKw)'
118
+ else
119
+ access_token = (options[:access_token] || auth_token())
120
+ end
121
+
111
122
  json = {}
112
123
 
113
124
  json[:description] = options[:description] if options[:description]
@@ -126,17 +137,12 @@ module Gist
126
137
  return if json[:files].empty? && options[:skip_empty]
127
138
 
128
139
  existing_gist = options[:update].to_s.split("/").last
129
- if options[:anonymous]
130
- access_token = nil
131
- else
132
- access_token = (options[:access_token] || auth_token())
133
- end
134
140
 
135
141
  url = "#{base_path}/gists"
136
142
  url << "/" << CGI.escape(existing_gist) if existing_gist.to_s != ''
137
- url << "?access_token=" << CGI.escape(access_token) if access_token.to_s != ''
138
143
 
139
144
  request = Net::HTTP::Post.new(url)
145
+ request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
140
146
  request.body = JSON.dump(json)
141
147
  request.content_type = 'application/json'
142
148
 
@@ -172,9 +178,10 @@ module Gist
172
178
  if user == ""
173
179
  access_token = auth_token()
174
180
  if access_token.to_s != ''
175
- url << "/gists?access_token=" << CGI.escape(access_token)
181
+ url << "/gists"
176
182
 
177
183
  request = Net::HTTP::Get.new(url)
184
+ request['Authorization'] = "token #{access_token}"
178
185
  response = http(api_url, request)
179
186
 
180
187
  pretty_gist(response)
@@ -197,30 +204,21 @@ module Gist
197
204
  url = "#{base_path}"
198
205
 
199
206
  if user == ""
200
- access_token = auth_token()
201
- if access_token.to_s != ''
202
- url << "/gists?per_page=100&access_token=" << CGI.escape(access_token)
203
- get_gist_pages(url)
204
- else
205
- raise Error, "Not authenticated. Use 'gist --login' to login or 'gist -l username' to view public gists."
206
- end
207
-
207
+ url << "/gists?per_page=100"
208
208
  else
209
209
  url << "/users/#{user}/gists?per_page=100"
210
- get_gist_pages(url)
211
210
  end
212
211
 
212
+ get_gist_pages(url, auth_token())
213
213
  end
214
214
 
215
215
  def read_gist(id, file_name=nil)
216
216
  url = "#{base_path}/gists/#{id}"
217
217
 
218
218
  access_token = auth_token()
219
- if access_token.to_s != ''
220
- url << "?access_token=" << CGI.escape(access_token)
221
- end
222
219
 
223
220
  request = Net::HTTP::Get.new(url)
221
+ request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
224
222
  response = http(api_url, request)
225
223
 
226
224
  if response.code == '200'
@@ -246,9 +244,8 @@ module Gist
246
244
 
247
245
  access_token = auth_token()
248
246
  if access_token.to_s != ''
249
- url << "?access_token=" << CGI.escape(access_token)
250
-
251
247
  request = Net::HTTP::Delete.new(url)
248
+ request["Authorization"] = "token #{access_token}"
252
249
  response = http(api_url, request)
253
250
  else
254
251
  raise Error, "Not authenticated. Use 'gist --login' to login."
@@ -261,9 +258,10 @@ module Gist
261
258
  end
262
259
  end
263
260
 
264
- def get_gist_pages(url)
261
+ def get_gist_pages(url, access_token = "")
265
262
 
266
263
  request = Net::HTTP::Get.new(url)
264
+ request['Authorization'] = "token #{access_token}" if access_token.to_s != ''
267
265
  response = http(api_url, request)
268
266
  pretty_gist(response)
269
267
 
@@ -271,7 +269,7 @@ module Gist
271
269
 
272
270
  if link_header
273
271
  links = Hash[ link_header.gsub(/(<|>|")/, "").split(',').map { |link| link.split('; rel=') } ].invert
274
- get_gist_pages(links['next']) if links['next']
272
+ get_gist_pages(links['next'], access_token) if links['next']
275
273
  end
276
274
 
277
275
  end
@@ -335,16 +333,72 @@ module Gist
335
333
 
336
334
  # Log the user into gist.
337
335
  #
336
+ def login!(credentials={})
337
+ if (login_url == GITHUB_URL || ENV.key?(CLIENT_ID_ENV_NAME)) && credentials.empty? && !ENV.key?('GIST_USE_USERNAME_AND_PASSWORD')
338
+ device_flow_login!
339
+ else
340
+ access_token_login!(credentials)
341
+ end
342
+ end
343
+
344
+ def device_flow_login!
345
+ puts "Requesting login parameters..."
346
+ request = Net::HTTP::Post.new("/login/device/code")
347
+ request.body = JSON.dump({
348
+ :scope => 'gist',
349
+ :client_id => client_id,
350
+ })
351
+ request.content_type = 'application/json'
352
+ request['accept'] = "application/json"
353
+ response = http(login_url, request)
354
+
355
+ if response.code != '200'
356
+ raise Error, "HTTP #{response.code}: #{response.body}"
357
+ end
358
+
359
+ body = JSON.parse(response.body)
360
+
361
+ puts "Please sign in at #{body['verification_uri']}"
362
+ puts " and enter code: #{body['user_code']}"
363
+ device_code = body['device_code']
364
+ interval = body['interval']
365
+
366
+ loop do
367
+ sleep(interval.to_i)
368
+ request = Net::HTTP::Post.new("/login/oauth/access_token")
369
+ request.body = JSON.dump({
370
+ :client_id => client_id,
371
+ :grant_type => 'urn:ietf:params:oauth:grant-type:device_code',
372
+ :device_code => device_code
373
+ })
374
+ request.content_type = 'application/json'
375
+ request['Accept'] = 'application/json'
376
+ response = http(login_url, request)
377
+ if response.code != '200'
378
+ raise Error, "HTTP #{response.code}: #{response.body}"
379
+ end
380
+ body = JSON.parse(response.body)
381
+ break unless body['error'] == 'authorization_pending'
382
+ end
383
+
384
+ if body['error']
385
+ raise Error, body['error_description']
386
+ end
387
+
388
+ AuthTokenFile.write JSON.parse(response.body)['access_token']
389
+
390
+ puts "Success! #{ENV[URL_ENV_NAME] || "https://github.com/"}settings/connections/applications/#{client_id}"
391
+ end
392
+
393
+ # Logs the user into gist.
394
+ #
338
395
  # This method asks the user for a username and password, and tries to obtain
339
396
  # and OAuth2 access token, which is then stored in ~/.gist
340
397
  #
341
398
  # @raise [Gist::Error] if something went wrong
342
- # @param [Hash] credentials login details
343
- # @option credentials [String] :username
344
- # @option credentials [String] :password
345
399
  # @see http://developer.github.com/v3/oauth/
346
- def login!(credentials={})
347
- puts "Obtaining OAuth2 access_token from github."
400
+ def access_token_login!(credentials={})
401
+ puts "Obtaining OAuth2 access_token from GitHub."
348
402
  loop do
349
403
  print "GitHub username: "
350
404
  username = credentials[:username] || $stdin.gets.strip
@@ -400,7 +454,11 @@ module Gist
400
454
  env = ENV['http_proxy'] || ENV['HTTP_PROXY']
401
455
  connection = if env
402
456
  proxy = URI(env)
403
- Net::HTTP::Proxy(proxy.host, proxy.port).new(uri.host, uri.port)
457
+ if proxy.user
458
+ Net::HTTP::Proxy(proxy.host, proxy.port, proxy.user, proxy.password).new(uri.host, uri.port)
459
+ else
460
+ Net::HTTP::Proxy(proxy.host, proxy.port).new(uri.host, uri.port)
461
+ end
404
462
  else
405
463
  Net::HTTP.new(uri.host, uri.port)
406
464
  end
@@ -554,11 +612,19 @@ Could not find copy command, tried:
554
612
  ENV.key?(URL_ENV_NAME) ? GHE_BASE_PATH : GITHUB_BASE_PATH
555
613
  end
556
614
 
615
+ def login_url
616
+ ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_URL
617
+ end
618
+
557
619
  # Get the API URL
558
620
  def api_url
559
621
  ENV.key?(URL_ENV_NAME) ? URI(ENV[URL_ENV_NAME]) : GITHUB_API_URL
560
622
  end
561
623
 
624
+ def client_id
625
+ ENV.key?(CLIENT_ID_ENV_NAME) ? URI(ENV[CLIENT_ID_ENV_NAME]) : GITHUB_CLIENT_ID
626
+ end
627
+
562
628
  def legacy_private_gister?
563
629
  return unless which('git')
564
630
  `git config --global gist.private` =~ /\Ayes|1|true|on\z/i
data/spec/ghe_spec.rb CHANGED
@@ -5,10 +5,10 @@ describe '...' do
5
5
  MOCK_USER = 'foo'
6
6
  MOCK_PASSWORD = 'bar'
7
7
 
8
- MOCK_AUTHZ_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_USER}:#{MOCK_PASSWORD}@#{MOCK_GHE_HOST}/api/v3/"
8
+ MOCK_AUTHZ_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_GHE_HOST}/api/v3/"
9
9
  MOCK_GHE_URL = "#{MOCK_GHE_PROTOCOL}://#{MOCK_GHE_HOST}/api/v3/"
10
- MOCK_AUTHZ_GITHUB_URL = "https://#{MOCK_USER}:#{MOCK_PASSWORD}@api.github.com/"
11
10
  MOCK_GITHUB_URL = "https://api.github.com/"
11
+ MOCK_LOGIN_URL = "https://github.com/"
12
12
 
13
13
  before do
14
14
  @saved_env = ENV[Gist::URL_ENV_NAME]
@@ -20,8 +20,15 @@ describe '...' do
20
20
  # stub requests for /authorizations
21
21
  stub_request(:post, /#{MOCK_AUTHZ_GHE_URL}authorizations/).
22
22
  to_return(:status => 201, :body => '{"token": "asdf"}')
23
- stub_request(:post, /#{MOCK_AUTHZ_GITHUB_URL}authorizations/).
23
+ stub_request(:post, /#{MOCK_GITHUB_URL}authorizations/).
24
+ with(headers: {'Authorization'=>'Basic Zm9vOmJhcg=='}).
24
25
  to_return(:status => 201, :body => '{"token": "asdf"}')
26
+
27
+ stub_request(:post, /#{MOCK_LOGIN_URL}login\/device\/code/).
28
+ to_return(:status => 200, :body => '{"interval": "0.1", "user_code":"XXXX-XXXX", "device_code": "xxxx", "verification_uri": "https://github.com/login/device"}')
29
+
30
+ stub_request(:post, /#{MOCK_LOGIN_URL}login\/oauth\/access_token/).
31
+ to_return(:status => 200, :body => '{"access_token":"zzzz"}')
25
32
  end
26
33
 
27
34
  after do
@@ -48,7 +55,7 @@ describe '...' do
48
55
 
49
56
  Gist.login!
50
57
 
51
- assert_requested(:post, /#{MOCK_AUTHZ_GITHUB_URL}authorizations/)
58
+ assert_requested(:post, /#{MOCK_LOGIN_URL}login\/oauth\/access_token/)
52
59
  end
53
60
 
54
61
  it "should access to #{MOCK_GHE_HOST} when $#{Gist::URL_ENV_NAME} was set" do
@@ -65,7 +72,7 @@ describe '...' do
65
72
  $stdin = StringIO.new "#{MOCK_USER}_wrong\n#{MOCK_PASSWORD}_wrong\n"
66
73
  Gist.login! :username => MOCK_USER, :password => MOCK_PASSWORD
67
74
 
68
- assert_requested(:post, /#{MOCK_AUTHZ_GITHUB_URL}authorizations/)
75
+ assert_requested(:post, /#{MOCK_GITHUB_URL}authorizations/)
69
76
  end
70
77
 
71
78
  end