Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
github
hub
提交
0b076c30
H
hub
项目概览
github
/
hub
9 个月 前同步成功
通知
3
Star
22523
Fork
2406
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
H
hub
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
0b076c30
编写于
12月 23, 2014
作者:
M
Mislav Marohnić
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #738 from github/no-token-reuse
Improve authentication process
上级
079246e3
4248b84e
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
131 addition
and
143 deletion
+131
-143
features/authentication.feature
features/authentication.feature
+95
-115
features/support/local_server.rb
features/support/local_server.rb
+1
-5
lib/hub/commands.rb
lib/hub/commands.rb
+1
-2
lib/hub/github_api.rb
lib/hub/github_api.rb
+34
-21
未找到文件。
features/authentication.feature
浏览文件 @
0b076c30
...
@@ -5,12 +5,15 @@ Feature: OAuth authentication
...
@@ -5,12 +5,15 @@ Feature: OAuth authentication
Scenario
:
Ask for username & password, create authorization
Scenario
:
Ask for username & password, create authorization
Given the GitHub API server
:
Given the GitHub API server
:
"""
"""
require 'rack/auth/basic'
require 'socket'
get('/authorizations') { '[]' }
require 'etc'
machine_id = "#{Etc.getlogin}@#{Socket.gethostname}"
post('/authorizations') {
post('/authorizations') {
auth = Rack::Auth::Basic::Request.new(env)
assert_basic_auth 'mislav', 'kitty'
halt 401 unless auth.credentials == %w[mislav kitty]
assert :scopes => ['repo'],
assert :scopes => ['repo']
:note => "hub for #{machine_id}",
:note_url => 'http://hub.github.com/'
json :token => 'OTOKEN'
json :token => 'OTOKEN'
}
}
get('/user') {
get('/user') {
...
@@ -32,50 +35,29 @@ Feature: OAuth authentication
...
@@ -32,50 +35,29 @@ Feature: OAuth authentication
And the file "../home/.config/hub" should contain "oauth_token
:
OTOKEN"
And the file "../home/.config/hub" should contain "oauth_token
:
OTOKEN"
And
the file
"../home/.config/hub"
should have mode
"0600"
And
the file
"../home/.config/hub"
should have mode
"0600"
Scenario
:
Ask for username & password, re-use existing authorizat
ion
Scenario
:
Rename & retry creating authorization if there's a token name collis
ion
Given the GitHub API server
:
Given the GitHub API server
:
"""
"""
require 'rack/auth/basic'
require 'socket'
get('/authorizations') {
require 'etc'
auth = Rack::Auth::Basic::Request.new(env)
machine_id = "#{Etc.getlogin}@#{Socket.gethostname}"
halt 401 unless auth.credentials == %w[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
:
"""
require 'rack/auth/basic'
get('/authorizations') {
auth = Rack::Auth::Basic::Request.new(env)
halt 401 unless auth.credentials == %w[mislav kitty]
json [
{:token => 'OTOKEN', :note => 'hub', :note_url => 'http://defunkt.io/hub/'}
]
}
post('/authorizations') {
post('/authorizations') {
status 422
assert_basic_auth 'mislav', 'kitty'
json :message => "Validation Failed",
if params[:note] == "hub for #{machine_id} 3"
:errors => [{:resource => "OauthAccess", :code => "already_exists", :field => "description"}]
json :token => 'OTOKEN'
else
status 422
json :message => 'Validation Failed',
:errors => [{
:resource => 'OauthAccess',
:code => 'already_exists',
:field => 'description'
}]
end
}
}
get('/user') {
get('/user') {
json :login => '
misla
v'
json :login => '
MiSlA
v'
}
}
post('/user/repos') {
post('/user/repos') {
json :full_name => 'mislav/dotfiles'
json :full_name => 'mislav/dotfiles'
...
@@ -84,54 +66,43 @@ Feature: OAuth authentication
...
@@ -84,54 +66,43 @@ Feature: OAuth authentication
When
I run `hub create` interactively
When
I run `hub create` interactively
When
I type
"mislav"
When
I type
"mislav"
And
I type
"kitty"
And
I type
"kitty"
Then the output should contain "github.com
password for mislav (never stored)
:
"
Then the output should contain "github.com
username
:
"
And
the exit status should be 0
And
the exit status should be 0
And the file "../home/.config/hub" should contain "oauth_token
:
OTOKEN"
And the file "../home/.config/hub" should contain "oauth_token
:
OTOKEN"
Scenario
:
Re-use existing authorization found on page 3
Scenario
:
Avoid getting caught up in infinite recursion while retrying token names
Given the GitHub API server
:
Given the GitHub API server
:
"""
"""
get('/authorizations') {
tries = 0
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') {
post('/authorizations') {
tries += 1
halt 400, json(:message => "too many tries") if tries >= 10
status 422
status 422
json :message => "Validation Failed",
json :message => 'Validation Failed',
:errors => [{:resource => "OauthAccess", :code => "already_exists", :field => "description"}]
:errors => [{
}
:resource => 'OauthAccess',
get('/user') {
:code => 'already_exists',
json :login => 'mislav'
:field => 'description'
}
}]
post('/user/repos') {
json :full_name => 'mislav/dotfiles'
}
}
"""
"""
When
I run `hub create` interactively
When
I run `hub create` interactively
When
I type
"mislav"
When
I type
"mislav"
And
I type
"kitty"
And
I type
"kitty"
Then the output should contain "github.com password for mislav (never stored)
:
"
Then the output should contain
:
And
the exit status should be 0
"""
And the file "../home/.config/hub" should contain "oauth_token
:
OTOKEN"
Error creating repository: Unprocessable Entity (HTTP 422)
Duplicate value for "description"
"""
And
the exit status should be 1
And
the file
"../home/.config/hub"
should not exist
Scenario
:
Credentials from GITHUB_USER & GITHUB_PASSWORD
Scenario
:
Credentials from GITHUB_USER & GITHUB_PASSWORD
Given the GitHub API server
:
Given the GitHub API server
:
"""
"""
require 'rack/auth/basic'
post('/authorizations') {
get('/authorizations') {
assert_basic_auth 'mislav', 'kitty'
auth = Rack::Auth::Basic::Request.new(env)
json :token => 'OTOKEN'
halt 401 unless auth.credentials == %w[mislav kitty]
json [
{:token => 'OTOKEN', :note_url => 'http://hub.github.com/'}
]
}
}
get('/user') {
get('/user') {
json :login => 'mislav'
json :login => 'mislav'
...
@@ -149,41 +120,54 @@ Feature: OAuth authentication
...
@@ -149,41 +120,54 @@ Feature: OAuth authentication
Scenario
:
Wrong password
Scenario
:
Wrong password
Given the GitHub API server
:
Given the GitHub API server
:
"""
"""
require 'rack/auth/basic'
post('/authorizations') {
get('/authorizations') {
assert_basic_auth 'mislav', 'kitty'
auth = Rack::Auth::Basic::Request.new(env)
halt 401 unless auth.credentials == %w[mislav kitty]
}
}
"""
"""
When
I run `hub create` interactively
When
I run `hub create` interactively
When
I type
"mislav"
When
I type
"mislav"
And
I type
"WRONG"
And
I type
"WRONG"
Then the stderr should contain "Error creating repository
:
Unauthorized
(HTTP
401)"
Then the stderr should contain exactly
:
"""
Error creating repository: Unauthorized (HTTP 401)
Bad credentials
"""
And
the exit status should be 1
And
the exit status should be 1
And
the file
"../home/.config/hub"
should not exist
And
the file
"../home/.config/hub"
should not exist
Scenario
:
Two-factor authentication, create authorization
Scenario
:
Personal access token used instead of password
Given the GitHub API server
:
Given the GitHub API server
:
"""
"""
require 'rack/auth/basic'
post('/authorizations') {
get('/authorizations') {
status 403
auth = Rack::Auth::Basic::Request.new(env)
json :message => "This API can only be accessed with username and password Basic Auth"
halt 401 unless auth.credentials == %w[mislav kitty]
if request.env['HTTP_X_GITHUB_OTP'] != "112233"
response.headers['X-GitHub-OTP'] = "required;application"
halt 401
end
json [ ]
}
}
"""
When
I run `hub create` interactively
When
I type
"mislav"
And
I type
"PERSONALACCESSTOKEN"
Then the stderr should contain exactly
:
"""
Error creating repository: Forbidden (HTTP 403)
This API can only be accessed with username and password Basic Auth
"""
And
the exit status should be 1
And
the file
"../home/.config/hub"
should not exist
Scenario
:
Two-factor authentication, create authorization
Given the GitHub API server
:
"""
post('/authorizations') {
post('/authorizations') {
auth = Rack::Auth::Basic::Request.new(env)
assert_basic_auth 'mislav', 'kitty'
halt 401 unless auth.credentials == %w[mislav kitty]
if request.env['HTTP_X_GITHUB_OTP'] == '112233'
halt 412 unless params[:scopes]
json :token => 'OTOKEN'
if request.env['HTTP_X_GITHUB_OTP'] != "112233"
else
response.headers['X-GitHub-OTP'] = "required;application"
response.headers['X-GitHub-OTP'] = 'required; app'
halt 401
status 401
json :message => "Must specify two-factor authentication OTP code."
end
end
json :token => 'OTOKEN'
}
}
get('/user') {
get('/user') {
json :login => 'mislav'
json :login => 'mislav'
...
@@ -198,28 +182,25 @@ Feature: OAuth authentication
...
@@ -198,28 +182,25 @@ Feature: OAuth authentication
And
I type
"112233"
And
I type
"112233"
Then the output should contain "github.com password for mislav (never stored)
:
"
Then the output should contain "github.com password for mislav (never stored)
:
"
Then the output should contain "two-factor authentication code
:
"
Then the output should contain "two-factor authentication code
:
"
And the output should not contain "warning
:
invalid
two-factor
code"
And
the exit status should be 0
And
the exit status should be 0
And the file "../home/.config/hub" should contain "oauth_token
:
OTOKEN"
And the file "../home/.config/hub" should contain "oauth_token
:
OTOKEN"
Scenario
:
Two-factor authentication, re-use existing authorization
Scenario
:
Retry entering two-factor authentication code
Given the GitHub API server
:
Given the GitHub API server
:
"""
"""
token = 'OTOKEN'
previous_otp_code = nil
post('/authorizations') {
post('/authorizations') {
assert_basic_auth 'mislav', 'kitty'
assert_basic_auth 'mislav', 'kitty'
token << 'SMS
'
if request.env['HTTP_X_GITHUB_OTP'] == '112233
'
status 412
halt 400 unless '666' == previous_otp_code
}
json :token => 'OTOKEN'
get('/authorizations') {
else
assert_basic_auth 'mislav', 'kitty'
previous_otp_code = request.env['HTTP_X_GITHUB_OTP']
if request.env['HTTP_X_GITHUB_OTP'] != "112233"
response.headers['X-GitHub-OTP'] = 'required; app'
response.headers['X-GitHub-OTP'] = "required;application"
status 401
halt 401
json :message => "Must specify two-factor authentication OTP code."
end
end
json [ {
:token => token,
:note_url => 'http://hub.github.com/'
} ]
}
}
get('/user') {
get('/user') {
json :login => 'mislav'
json :login => 'mislav'
...
@@ -231,16 +212,15 @@ Feature: OAuth authentication
...
@@ -231,16 +212,15 @@ Feature: OAuth authentication
When
I run `hub create` interactively
When
I run `hub create` interactively
When
I type
"mislav"
When
I type
"mislav"
And
I type
"kitty"
And
I type
"kitty"
And
I type
"666"
And
I type
"112233"
And
I type
"112233"
Then the output should contain "github.com password for mislav (never stored)
:
"
Then the output should contain "warning
:
invalid
two-factor
code"
Then the output should contain "two-factor authentication code
:
"
And
the exit status should be 0
And
the exit status should be 0
And the file "../home/.config/hub" should contain "oauth_token
:
OTOKEN
SMS
"
And the file "../home/.config/hub" should contain "oauth_token
:
OTOKEN"
Scenario
:
Special characters in username & password
Scenario
:
Special characters in username & password
Given the GitHub API server
:
Given the GitHub API server
:
"""
"""
get('/authorizations') { '[]' }
post('/authorizations') {
post('/authorizations') {
assert_basic_auth 'mislav@example.com', 'my pass@phrase ok?'
assert_basic_auth 'mislav@example.com', 'my pass@phrase ok?'
json :token => 'OTOKEN'
json :token => 'OTOKEN'
...
...
features/support/local_server.rb
浏览文件 @
0b076c30
...
@@ -70,11 +70,7 @@ module Hub
...
@@ -70,11 +70,7 @@ module Hub
require
'rack/auth/basic'
require
'rack/auth/basic'
auth
=
Rack
::
Auth
::
Basic
::
Request
.
new
(
env
)
auth
=
Rack
::
Auth
::
Basic
::
Request
.
new
(
env
)
if
auth
.
credentials
!=
expected
if
auth
.
credentials
!=
expected
halt
401
,
json
(
halt
401
,
json
(
:message
=>
"Bad credentials"
)
:message
=>
"expected %p; got %p"
%
[
expected
,
auth
.
credentials
]
)
end
end
end
end
...
...
lib/hub/commands.rb
浏览文件 @
0b076c30
...
@@ -1157,8 +1157,7 @@ help
...
@@ -1157,8 +1157,7 @@ help
def
display_api_exception
(
action
,
response
)
def
display_api_exception
(
action
,
response
)
$stderr
.
puts
"Error
#{
action
}
:
#{
response
.
message
.
strip
}
(HTTP
#{
response
.
status
}
)"
$stderr
.
puts
"Error
#{
action
}
:
#{
response
.
message
.
strip
}
(HTTP
#{
response
.
status
}
)"
if
422
==
response
.
status
and
response
.
error_message?
if
[
401
,
403
,
422
].
include?
(
response
.
status
)
&&
response
.
error_message?
# display validation errors
msg
=
response
.
error_message
msg
=
response
.
error_message
msg
=
msg
.
join
(
"
\n
"
)
if
msg
.
respond_to?
:join
msg
=
msg
.
join
(
"
\n
"
)
if
msg
.
respond_to?
:join
warn
msg
warn
msg
...
...
lib/hub/github_api.rb
浏览文件 @
0b076c30
...
@@ -333,36 +333,49 @@ module Hub
...
@@ -333,36 +333,49 @@ module Hub
end
end
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_url
=
URI
.
parse
(
"https://%s@%s/authorizations"
%
[
CGI
.
escape
(
user
),
host
])
auth_params
=
{
:scopes
=>
[
'repo'
],
:note
=>
"hub for
#{
local_user
}
@
#{
local_hostname
}
"
,
: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
loop
do
post
(
auth_url
)
if
!
two_factor_code
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
if
res
.
success?
res
=
get_all
(
auth_url
)
do
|
req
|
break
req
[
'X-GitHub-OTP'
]
=
two_factor_code
if
two_factor_code
elsif
res
.
status
==
401
&&
res
[
'X-GitHub-OTP'
].
to_s
.
include?
(
'required'
)
end
$stderr
.
puts
"warning: invalid two-factor code"
if
two_factor_code
unless
res
.
success?
if
!
two_factor_code
&&
res
[
'X-GitHub-OTP'
].
to_s
.
include?
(
'required'
)
two_factor_code
=
config
.
prompt_auth_code
two_factor_code
=
config
.
prompt_auth_code
return
obtain_oauth_token
(
host
,
user
,
two_factor_code
)
elsif
res
.
status
==
422
&&
'already_exists'
==
res
.
data
[
'errors'
][
0
][
'code'
]
if
auth_params
[
:note
]
=~
/ (\d+)$/
res
.
error!
if
$1
.
to_i
>=
9
auth_params
[
:note
].
succ!
else
auth_params
[
:note
]
+=
' 2'
end
else
else
res
.
error!
res
.
error!
end
end
end
end
if
found
=
res
.
data
.
find
{
|
auth
|
auth
[
'note'
]
==
'hub'
||
auth
[
'note_url'
]
==
oauth_app_url
}
res
.
data
[
'token'
]
found
[
'token'
]
end
else
# create a new authorization
def
local_user
res
=
post
auth_url
,
require
'etc'
:scopes
=>
%w[repo]
,
:note
=>
'hub'
,
:note_url
=>
oauth_app_url
do
|
req
|
Etc
.
getlogin
req
[
'X-GitHub-OTP'
]
=
two_factor_code
if
two_factor_code
end
end
res
.
error!
unless
res
.
success?
def
local_hostname
res
.
data
[
'token'
]
require
'socket'
end
Socket
.
gethostname
end
end
end
end
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录