Skip to content

Commit

Permalink
Better handle basic authentication without a password
Browse files Browse the repository at this point in the history
rails#43209 immediately rejects
the request if no password is passed, but there are legitimate
uses for accepting authentication without a password.
  • Loading branch information
byroot committed Mar 4, 2022
1 parent b783e8a commit 9ebfb14
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 8 deletions.
14 changes: 14 additions & 0 deletions actionpack/CHANGELOG.md
@@ -1,3 +1,17 @@
* Fix `authenticate_with_http_basic` to allow for missing password.

Before Rails 7.0 it was possible to handle basic authentication with only a username.

```ruby
authenticate_with_http_basic do |token, _|
ApiClient.authenticate(token)
end
```

This ability is restored.

*Jean Boussier*

* Fix `content_security_policy` returning invalid directives.

Directives such as `self`, `unsafe-eval` and few others were not
Expand Down
8 changes: 5 additions & 3 deletions actionpack/lib/action_controller/metal/http_authentication.rb
Expand Up @@ -74,6 +74,8 @@ module ClassMethods
#
# See ActionController::HttpAuthentication::Basic for example usage.
def http_basic_authenticate_with(name:, password:, realm: nil, **options)
raise ArgumentError, "Expected name: to be a String, got #{name.class}" unless name.is_a?(String)
raise ArgumentError, "Expected password: to be a String, got #{password.class}" unless name.is_a?(String)
before_action(options) { http_basic_authenticate_or_request_with name: name, password: password, realm: realm }
end
end
Expand All @@ -82,8 +84,8 @@ def http_basic_authenticate_or_request_with(name:, password:, realm: nil, messag
authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
# This comparison uses & so that it doesn't short circuit and
# uses `secure_compare` so that length information isn't leaked.
ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
ActiveSupport::SecurityUtils.secure_compare(given_password, password)
ActiveSupport::SecurityUtils.secure_compare(given_name.to_s, name) &
ActiveSupport::SecurityUtils.secure_compare(given_password.to_s, password)
end
end

Expand All @@ -107,7 +109,7 @@ def authenticate(request, &login_procedure)
end

def has_basic_credentials?(request)
request.authorization.present? && (auth_scheme(request).downcase == "basic") && user_name_and_password(request).length == 2
request.authorization.present? && (auth_scheme(request).downcase == "basic")
end

def user_name_and_password(request)
Expand Down
27 changes: 22 additions & 5 deletions actionpack/test/controller/http_basic_authentication_test.rb
Expand Up @@ -31,6 +31,13 @@ def search
render plain: "All inline"
end

def no_password
username, password = authenticate_with_http_basic do |username, password|
[username, password]
end
render plain: "Hello #{username} (password: #{password.inspect})"
end

private
def authenticate
authenticate_or_request_with_http_basic do |username, password|
Expand Down Expand Up @@ -112,11 +119,6 @@ def test_encode_credentials_has_no_newline
assert_no_match(/\n/, result)
end

test "has_basic_credentials? should fail with credentials without colon" do
@request.env["HTTP_AUTHORIZATION"] = "Basic #{::Base64.encode64("David Goliath")}"
assert_not ActionController::HttpAuthentication::Basic.has_basic_credentials?(@request)
end

test "successful authentication with uppercase authorization scheme" do
@request.env["HTTP_AUTHORIZATION"] = "BASIC #{::Base64.encode64("lifo:world")}"
get :index
Expand All @@ -142,6 +144,21 @@ def test_encode_credentials_has_no_newline
assert_equal 'Basic realm="SuperSecret"', @response.headers["WWW-Authenticate"]
end

test "authentication request with a missing password" do
@request.env["HTTP_AUTHORIZATION"] = "Basic #{::Base64.encode64("David")}"
get :search

assert_response :unauthorized
end

test "authentication request with no required password" do
@request.env["HTTP_AUTHORIZATION"] = "Basic #{::Base64.encode64("George")}"
get :no_password

assert_response :success
assert_equal "Hello George (password: nil)", @response.body
end

test "authentication request with valid credential" do
@request.env["HTTP_AUTHORIZATION"] = encode_credentials("pretty", "please")
get :display
Expand Down

0 comments on commit 9ebfb14

Please sign in to comment.