Skip to content

Commit 9ae0521

Browse files
authored
Merge pull request rails#44610 from Shopify/http-basic-no-password
Better handle basic authentication without a password
2 parents b783e8a + 9ebfb14 commit 9ae0521

File tree

3 files changed

+41
-8
lines changed

3 files changed

+41
-8
lines changed

actionpack/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
* Fix `authenticate_with_http_basic` to allow for missing password.
2+
3+
Before Rails 7.0 it was possible to handle basic authentication with only a username.
4+
5+
```ruby
6+
authenticate_with_http_basic do |token, _|
7+
ApiClient.authenticate(token)
8+
end
9+
```
10+
11+
This ability is restored.
12+
13+
*Jean Boussier*
14+
115
* Fix `content_security_policy` returning invalid directives.
216

317
Directives such as `self`, `unsafe-eval` and few others were not

actionpack/lib/action_controller/metal/http_authentication.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ module ClassMethods
7474
#
7575
# See ActionController::HttpAuthentication::Basic for example usage.
7676
def http_basic_authenticate_with(name:, password:, realm: nil, **options)
77+
raise ArgumentError, "Expected name: to be a String, got #{name.class}" unless name.is_a?(String)
78+
raise ArgumentError, "Expected password: to be a String, got #{password.class}" unless name.is_a?(String)
7779
before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
7880
end
7981
end
@@ -82,8 +84,8 @@ def http_basic_authenticate_or_request_with(name:, password:, realm: nil, messag
8284
authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
8385
# This comparison uses & so that it doesn't short circuit and
8486
# uses `secure_compare` so that length information isn't leaked.
85-
ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
86-
ActiveSupport::SecurityUtils.secure_compare(given_password, password)
87+
ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) &
88+
ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password)
8789
end
8890
end
8991

@@ -107,7 +109,7 @@ def authenticate(request, &login_procedure)
107109
end
108110

109111
def has_basic_credentials?(request)
110-
request.authorization.present? && (auth_scheme(request).downcase == "basic") && user_name_and_password(request).length == 2
112+
request.authorization.present? && (auth_scheme(request).downcase == "basic")
111113
end
112114

113115
def user_name_and_password(request)

actionpack/test/controller/http_basic_authentication_test.rb

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ def search
3131
render plain: "All inline"
3232
end
3333

34+
def no_password
35+
username, password = authenticate_with_http_basic do |username, password|
36+
[username, password]
37+
end
38+
render plain: "Hello #{username} (password: #{password.inspect})"
39+
end
40+
3441
private
3542
def authenticate
3643
authenticate_or_request_with_http_basic do |username, password|
@@ -112,11 +119,6 @@ def test_encode_credentials_has_no_newline
112119
assert_no_match(/\n/, result)
113120
end
114121

115-
test "has_basic_credentials? should fail with credentials without colon" do
116-
@request.env["HTTP_AUTHORIZATION"] = "Basic #{::Base64.encode64("David Goliath")}"
117-
assert_not ActionController::HttpAuthentication::Basic.has_basic_credentials?(@request)
118-
end
119-
120122
test "successful authentication with uppercase authorization scheme" do
121123
@request.env["HTTP_AUTHORIZATION"] = "BASIC #{::Base64.encode64("lifo:world")}"
122124
get :index
@@ -142,6 +144,21 @@ def test_encode_credentials_has_no_newline
142144
assert_equal 'Basic realm="SuperSecret"', @response.headers["WWW-Authenticate"]
143145
end
144146

147+
test "authentication request with a missing password" do
148+
@request.env["HTTP_AUTHORIZATION"] = "Basic #{::Base64.encode64("David")}"
149+
get :search
150+
151+
assert_response :unauthorized
152+
end
153+
154+
test "authentication request with no required password" do
155+
@request.env["HTTP_AUTHORIZATION"] = "Basic #{::Base64.encode64("George")}"
156+
get :no_password
157+
158+
assert_response :success
159+
assert_equal "Hello George (password: nil)", @response.body
160+
end
161+
145162
test "authentication request with valid credential" do
146163
@request.env["HTTP_AUTHORIZATION"] = encode_credentials("pretty", "please")
147164
get :display

0 commit comments

Comments
 (0)