Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
张重言
rails
提交
306cc2b9
R
rails
项目概览
张重言
/
rails
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
rails
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
306cc2b9
编写于
1月 29, 2009
作者:
G
Gregg Kellogg
提交者:
Pratik Naik
1月 29, 2009
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Implement HTTP Digest authentication. [#1230 state:resolved] [Gregg Kellogg, Pratik Naik]
Signed-off-by:
N
Pratik Naik
<
pratiknaik@gmail.com
>
上级
e6493eb9
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
326 addition
and
25 deletion
+326
-25
actionpack/CHANGELOG
actionpack/CHANGELOG
+21
-0
actionpack/lib/action_controller/base.rb
actionpack/lib/action_controller/base.rb
+2
-2
actionpack/lib/action_controller/http_authentication.rb
actionpack/lib/action_controller/http_authentication.rb
+173
-23
actionpack/test/controller/http_digest_authentication_test.rb
...onpack/test/controller/http_digest_authentication_test.rb
+130
-0
未找到文件。
actionpack/CHANGELOG
浏览文件 @
306cc2b9
*
2.3.0
[
Edge
]*
*
Implement
HTTP
Digest
authentication
.
#
1230
[
Gregg
Kellogg
,
Pratik
Naik
]
Example
:
class
DummyDigestController
<
ActionController
::
Base
USERS
=
{
"lifo"
=>
'world'
}
before_filter
:
authenticate
def
index
render
:
text
=>
"Hello Secret"
end
private
def
authenticate
authenticate_or_request_with_http_digest
(
"Super Secret"
)
do
|
username
|
#
Return
the
user
's password
USERS[username]
end
end
end
* Improved i18n support for the number_to_human_size helper. Changes the storage_units translation data; update your translations accordingly. #1634 [Yaroslav Markin]
storage_units:
# %u is the storage unit, %n is the number (default: 2 MB)
...
...
actionpack/lib/action_controller/base.rb
浏览文件 @
306cc2b9
...
...
@@ -1344,8 +1344,8 @@ def process_cleanup
Base
.
class_eval
do
[
Filters
,
Layout
,
Benchmarking
,
Rescue
,
Flash
,
MimeResponds
,
Helpers
,
Cookies
,
Caching
,
Verification
,
Streaming
,
SessionManagement
,
HttpAuthentication
::
Basic
::
ControllerMethods
,
RecordIdentifier
,
RequestForgeryProtection
,
Translation
HttpAuthentication
::
Basic
::
ControllerMethods
,
HttpAuthentication
::
Digest
::
ControllerMethods
,
Re
cordIdentifier
,
Re
questForgeryProtection
,
Translation
].
each
do
|
mod
|
include
mod
end
...
...
actionpack/lib/action_controller/http_authentication.rb
浏览文件 @
306cc2b9
module
ActionController
module
HttpAuthentication
# Makes it dead easy to do HTTP Basic authentication.
#
#
# Simple Basic example:
#
#
# class PostsController < ApplicationController
# USER_NAME, PASSWORD = "dhh", "secret"
#
#
# before_filter :authenticate, :except => [ :index ]
#
#
# def index
# render :text => "Everyone can see me!"
# end
#
#
# def edit
# render :text => "I'm only accessible if you know the password"
# end
#
#
# private
# def authenticate
# authenticate_or_request_with_http_basic do |user_name, password|
# authenticate_or_request_with_http_basic do |user_name, password|
# user_name == USER_NAME && password == PASSWORD
# end
# end
# end
#
#
# Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
#
#
# Here is a more advanced Basic example where only Atom feeds and the XML API is protected by HTTP authentication,
# the regular HTML interface is protected by a session approach:
#
#
# class ApplicationController < ActionController::Base
# before_filter :set_account, :authenticate
#
#
# protected
# def set_account
# @account = Account.find_by_url_name(request.subdomains.first)
# end
#
#
# def authenticate
# case request.format
# when Mime::XML, Mime::ATOM
...
...
@@ -54,24 +54,48 @@ module HttpAuthentication
# end
# end
# end
#
#
#
# In your integration tests, you can do something like this:
#
#
# def test_access_granted_from_xml
# get(
# "/notes/1.xml", nil,
# "/notes/1.xml", nil,
# :authorization => ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password)
# )
#
#
# assert_equal 200, status
# end
#
#
#
# Simple Digest example:
#
# class PostsController < ApplicationController
# USERS = {"dhh" => "secret"}
#
# before_filter :authenticate, :except => [:index]
#
# def index
# render :text => "Everyone can see me!"
# end
#
# def edit
# render :text => "I'm only accessible if you know the password"
# end
#
# private
# def authenticate
# authenticate_or_request_with_http_digest(realm) do |username|
# USERS[username]
# end
# end
# end
#
# NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password so the framework can appropriately
# hash it to check the user's credentials. Returning +nil+ will cause authentication to fail.
#
# On shared hosts, Apache sometimes doesn't pass authentication headers to
# FCGI instances. If your environment matches this description and you cannot
# authenticate, try this rule in your Apache setup:
#
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module
Basic
extend
self
...
...
@@ -99,14 +123,14 @@ def authenticate(controller, &login_procedure)
def
user_name_and_password
(
request
)
decode_credentials
(
request
).
split
(
/:/
,
2
)
end
def
authorization
(
request
)
request
.
env
[
'HTTP_AUTHORIZATION'
]
||
request
.
env
[
'X-HTTP_AUTHORIZATION'
]
||
request
.
env
[
'X_HTTP_AUTHORIZATION'
]
||
request
.
env
[
'REDIRECT_X_HTTP_AUTHORIZATION'
]
end
def
decode_credentials
(
request
)
ActiveSupport
::
Base64
.
decode64
(
authorization
(
request
).
split
.
last
||
''
)
end
...
...
@@ -120,5 +144,131 @@ def authentication_request(controller, realm)
controller
.
__send__
:render
,
:text
=>
"HTTP Basic: Access denied.
\n
"
,
:status
=>
:unauthorized
end
end
module
Digest
extend
self
module
ControllerMethods
def
authenticate_or_request_with_http_digest
(
realm
=
"Application"
,
&
password_procedure
)
authenticate_with_http_digest
(
realm
,
&
password_procedure
)
||
request_http_digest_authentication
(
realm
)
end
# Authenticate with HTTP Digest, returns true or false
def
authenticate_with_http_digest
(
realm
=
"Application"
,
&
password_procedure
)
HttpAuthentication
::
Digest
.
authenticate
(
self
,
realm
,
&
password_procedure
)
end
# Render output including the HTTP Digest authentication header
def
request_http_digest_authentication
(
realm
=
"Application"
,
message
=
nil
)
HttpAuthentication
::
Digest
.
authentication_request
(
self
,
realm
,
message
)
end
end
# Returns false on a valid response, true otherwise
def
authenticate
(
controller
,
realm
,
&
password_procedure
)
authorization
(
controller
.
request
)
&&
validate_digest_response
(
controller
,
realm
,
&
password_procedure
)
end
def
authorization
(
request
)
request
.
env
[
'HTTP_AUTHORIZATION'
]
||
request
.
env
[
'X-HTTP_AUTHORIZATION'
]
||
request
.
env
[
'X_HTTP_AUTHORIZATION'
]
||
request
.
env
[
'REDIRECT_X_HTTP_AUTHORIZATION'
]
end
# Raises error unless the request credentials response value matches the expected value.
def
validate_digest_response
(
controller
,
realm
,
&
password_procedure
)
credentials
=
decode_credentials_header
(
controller
.
request
)
valid_nonce
=
validate_nonce
(
controller
.
request
,
credentials
[
:nonce
])
if
valid_nonce
&&
realm
==
credentials
[
:realm
]
&&
opaque
(
controller
.
request
.
session
.
session_id
)
==
credentials
[
:opaque
]
password
=
password_procedure
.
call
(
credentials
[
:username
])
expected
=
expected_response
(
controller
.
request
.
env
[
'REQUEST_METHOD'
],
controller
.
request
.
url
,
credentials
,
password
)
expected
==
credentials
[
:response
]
end
end
# Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+
def
expected_response
(
http_method
,
uri
,
credentials
,
password
)
ha1
=
::
Digest
::
MD5
.
hexdigest
([
credentials
[
:username
],
credentials
[
:realm
],
password
].
join
(
':'
))
ha2
=
::
Digest
::
MD5
.
hexdigest
([
http_method
.
to_s
.
upcase
,
uri
].
join
(
':'
))
::
Digest
::
MD5
.
hexdigest
([
ha1
,
credentials
[
:nonce
],
credentials
[
:nc
],
credentials
[
:cnonce
],
credentials
[
:qop
],
ha2
].
join
(
':'
))
end
def
encode_credentials
(
http_method
,
credentials
,
password
)
credentials
[
:response
]
=
expected_response
(
http_method
,
credentials
[
:uri
],
credentials
,
password
)
"Digest "
+
credentials
.
sort_by
{
|
x
|
x
[
0
].
to_s
}.
inject
([])
{
|
a
,
v
|
a
<<
"
#{
v
[
0
]
}
='
#{
v
[
1
]
}
'"
}.
join
(
', '
)
end
def
decode_credentials_header
(
request
)
decode_credentials
(
authorization
(
request
))
end
def
decode_credentials
(
header
)
header
.
to_s
.
gsub
(
/^Digest\s+/
,
''
).
split
(
','
).
inject
({})
do
|
hash
,
pair
|
key
,
value
=
pair
.
split
(
'='
,
2
)
hash
[
key
.
strip
.
to_sym
]
=
value
.
to_s
.
gsub
(
/^"|"$/
,
''
).
gsub
(
/'/
,
''
)
hash
end
end
def
authentication_header
(
controller
,
realm
)
session_id
=
controller
.
request
.
session
.
session_id
controller
.
headers
[
"WWW-Authenticate"
]
=
%(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(session_id)}", opaque="#{opaque(session_id)}")
end
def
authentication_request
(
controller
,
realm
,
message
=
nil
)
message
||=
"HTTP Digest: Access denied.
\n
"
authentication_header
(
controller
,
realm
)
controller
.
__send__
:render
,
:text
=>
message
,
:status
=>
:unauthorized
end
# Uses an MD5 digest based on time to generate a value to be used only once.
#
# A server-specified data string which should be uniquely generated each time a 401 response is made.
# It is recommended that this string be base64 or hexadecimal data.
# Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed.
#
# The contents of the nonce are implementation dependent.
# The quality of the implementation depends on a good choice.
# A nonce might, for example, be constructed as the base 64 encoding of
#
# => time-stamp H(time-stamp ":" ETag ":" private-key)
#
# where time-stamp is a server-generated time or other non-repeating value,
# ETag is the value of the HTTP ETag header associated with the requested entity,
# and private-key is data known only to the server.
# With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and
# reject the request if it did not match the nonce from that header or
# if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity.
# The inclusion of the ETag prevents a replay request for an updated version of the resource.
# (Note: including the IP address of the client in the nonce would appear to offer the server the ability
# to limit the reuse of the nonce to the same client that originally got it.
# However, that would break proxy farms, where requests from a single user often go through different proxies in the farm.
# Also, IP address spoofing is not that hard.)
#
# An implementation might choose not to accept a previously used nonce or a previously used digest, in order to
# protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for
# POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4
# of this document.
#
# The nonce is opaque to the client.
def
nonce
(
session_id
,
time
=
Time
.
now
)
t
=
time
.
to_i
hashed
=
[
t
,
session_id
]
digest
=
::
Digest
::
MD5
.
hexdigest
(
hashed
.
join
(
":"
))
Base64
.
encode64
(
"
#{
t
}
:
#{
digest
}
"
).
gsub
(
"
\n
"
,
''
)
end
def
validate_nonce
(
request
,
value
)
t
=
Base64
.
decode64
(
value
).
split
(
":"
).
first
.
to_i
nonce
(
request
.
session
.
session_id
,
t
)
==
value
&&
(
t
-
Time
.
now
.
to_i
).
abs
<=
10
*
60
end
# Opaque based on digest of session_id
def
opaque
(
session_id
)
Base64
.
encode64
(
::
Digest
::
MD5
::
hexdigest
(
session_id
)).
gsub
(
"
\n
"
,
''
)
end
end
end
end
actionpack/test/controller/http_digest_authentication_test.rb
0 → 100644
浏览文件 @
306cc2b9
require
'abstract_unit'
class
HttpDigestAuthenticationTest
<
ActionController
::
TestCase
class
DummyDigestController
<
ActionController
::
Base
before_filter
:authenticate
,
:only
=>
:index
before_filter
:authenticate_with_request
,
:only
=>
:display
USERS
=
{
'lifo'
=>
'world'
,
'pretty'
=>
'please'
}
def
index
render
:text
=>
"Hello Secret"
end
def
display
render
:text
=>
'Definitely Maybe'
end
private
def
authenticate
authenticate_or_request_with_http_digest
(
"SuperSecret"
)
do
|
username
|
# Return the password
USERS
[
username
]
end
end
def
authenticate_with_request
if
authenticate_with_http_digest
(
"SuperSecret"
)
{
|
username
|
USERS
[
username
]
}
@logged_in
=
true
else
request_http_digest_authentication
(
"SuperSecret"
,
"Authentication Failed"
)
end
end
end
AUTH_HEADERS
=
[
'HTTP_AUTHORIZATION'
,
'X-HTTP_AUTHORIZATION'
,
'X_HTTP_AUTHORIZATION'
,
'REDIRECT_X_HTTP_AUTHORIZATION'
]
tests
DummyDigestController
AUTH_HEADERS
.
each
do
|
header
|
test
"successful authentication with
#{
header
.
downcase
}
"
do
@request
.
env
[
header
]
=
encode_credentials
(
:username
=>
'lifo'
,
:password
=>
'world'
)
get
:index
assert_response
:success
assert_equal
'Hello Secret'
,
@response
.
body
,
"Authentication failed for request header
#{
header
}
"
end
end
AUTH_HEADERS
.
each
do
|
header
|
test
"unsuccessful authentication with
#{
header
.
downcase
}
"
do
@request
.
env
[
header
]
=
encode_credentials
(
:username
=>
'h4x0r'
,
:password
=>
'world'
)
get
:index
assert_response
:unauthorized
assert_equal
"HTTP Digest: Access denied.
\n
"
,
@response
.
body
,
"Authentication didn't fail for request header
#{
header
}
"
end
end
test
"authentication request without credential"
do
get
:display
assert_response
:unauthorized
assert_equal
"Authentication Failed"
,
@response
.
body
credentials
=
decode_credentials
(
@response
.
headers
[
'WWW-Authenticate'
])
assert_equal
'SuperSecret'
,
credentials
[
:realm
]
end
test
"authentication request with invalid password"
do
@request
.
env
[
'HTTP_AUTHORIZATION'
]
=
encode_credentials
(
:username
=>
'pretty'
,
:password
=>
'foo'
)
get
:display
assert_response
:unauthorized
assert_equal
"Authentication Failed"
,
@response
.
body
end
test
"authentication request with invalid nonce"
do
@request
.
env
[
'HTTP_AUTHORIZATION'
]
=
encode_credentials
(
:username
=>
'pretty'
,
:password
=>
'please'
,
:nonce
=>
"xxyyzz"
)
get
:display
assert_response
:unauthorized
assert_equal
"Authentication Failed"
,
@response
.
body
end
test
"authentication request with invalid opaque"
do
@request
.
env
[
'HTTP_AUTHORIZATION'
]
=
encode_credentials
(
:username
=>
'pretty'
,
:password
=>
'foo'
,
:opaque
=>
"xxyyzz"
)
get
:display
assert_response
:unauthorized
assert_equal
"Authentication Failed"
,
@response
.
body
end
test
"authentication request with invalid realm"
do
@request
.
env
[
'HTTP_AUTHORIZATION'
]
=
encode_credentials
(
:username
=>
'pretty'
,
:password
=>
'foo'
,
:realm
=>
"NotSecret"
)
get
:display
assert_response
:unauthorized
assert_equal
"Authentication Failed"
,
@response
.
body
end
test
"authentication request with valid credential"
do
@request
.
env
[
'HTTP_AUTHORIZATION'
]
=
encode_credentials
(
:username
=>
'pretty'
,
:password
=>
'please'
)
get
:display
assert_response
:success
assert
assigns
(
:logged_in
)
assert_equal
'Definitely Maybe'
,
@response
.
body
end
private
def
encode_credentials
(
options
)
options
.
reverse_merge!
(
:nc
=>
"00000001"
,
:cnonce
=>
"0a4f113b"
)
password
=
options
.
delete
(
:password
)
# Perform unautheticated get to retrieve digest parameters to use on subsequent request
get
:index
assert_response
:unauthorized
credentials
=
decode_credentials
(
@response
.
headers
[
'WWW-Authenticate'
])
credentials
.
merge!
(
options
)
credentials
.
merge!
(
:uri
=>
"http://
#{
@request
.
host
}#{
@request
.
env
[
'REQUEST_URI'
]
}
"
)
ActionController
::
HttpAuthentication
::
Digest
.
encode_credentials
(
"GET"
,
credentials
,
password
)
end
def
decode_credentials
(
header
)
ActionController
::
HttpAuthentication
::
Digest
.
decode_credentials
(
@response
.
headers
[
'WWW-Authenticate'
])
end
end
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录