http basic auth failures respect request.accept for json/xml #6394

Closed
wants to merge 2 commits into
from
@@ -4,6 +4,19 @@
module ActionController
# Makes it dead easy to do HTTP Basic, Digest and Token authentication.
module HttpAuthentication
+ module AccessDeniedResponder
+ def respond_with_access_denied(controller, type, header, message_override=nil)
+ controller.headers["WWW-Authenticate"] = header
+ controller.status = 401
+ error_message = message_override || "HTTP #{type}: Access denied.\n"
+ controller.__send__(:respond_to) do |format|
+ format.json { controller.__send__(:render, :json => {:error => error_message.strip}) }
+ format.xml { controller.__send__(:render, :xml => "<error>#{error_message.strip}</error>") }
+ format.any { controller.__send__(:render, :text => error_message) }
+ end
+ end
+ end
+
# Makes it dead easy to do HTTP \Basic authentication.
#
# === Simple \Basic example
@@ -63,6 +76,7 @@ module HttpAuthentication
# end
module Basic
extend self
+ extend AccessDeniedResponder
module ControllerMethods
extend ActiveSupport::Concern
@@ -109,9 +123,7 @@ def encode_credentials(user_name, password)
end
def authentication_request(controller, realm)
- controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.gsub(/"/, "")}")
- controller.response_body = "HTTP Basic: Access denied.\n"
- controller.status = 401
+ respond_with_access_denied(controller, "Basic", %(Basic realm="#{realm.gsub(/"/, "")}"))
end
end
@@ -159,6 +171,7 @@ def authentication_request(controller, realm)
# variables, and check for HTTP_AUTHORIZATION, amongst others.
module Digest
extend self
+ extend AccessDeniedResponder
module ControllerMethods
def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure)
@@ -239,14 +252,12 @@ def authentication_header(controller, realm)
secret_key = secret_token(controller.request)
nonce = self.nonce(secret_key)
opaque = opaque(secret_key)
- controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
+ %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}")
end
def authentication_request(controller, realm, message = nil)
message ||= "HTTP Digest: Access denied.\n"
- authentication_header(controller, realm)
- controller.response_body = message
- controller.status = 401
+ respond_with_access_denied(controller, "Digest", authentication_header(controller, realm), message)
end
def secret_token(request)
@@ -386,6 +397,7 @@ def opaque(secret_key)
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
extend self
+ extend AccessDeniedResponder
module ControllerMethods
def authenticate_or_request_with_http_token(realm = "Application", &login_procedure)
@@ -460,8 +472,7 @@ def encode_credentials(token, options = {})
#
# Returns nothing.
def authentication_request(controller, realm)
- controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}")
- controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
+ respond_with_access_denied(controller, "Token", %(Token realm="#{realm.gsub(/"/, "")}"))
end
end
end
@@ -93,6 +93,33 @@ def test_encode_credentials_has_no_newline
assert_no_match(/\n/, result)
end
+ test "unsuccessful authentication request with application/json Accept header" do
+ @request.env["HTTP_ACCEPT"] = "application/json"
+ get :index
+
+ assert_response :unauthorized
+ assert_equal '{"error":"HTTP Basic: Access denied."}', @response.body
+ assert @response.headers['Content-Type'].start_with? 'application/json'
+ end
+
+ test "unsuccessful authentication request with text/xml Accept header" do
+ @request.env["HTTP_ACCEPT"] = "application/xml"
+ get :index
+
+ assert_response :unauthorized
+ assert_equal '<error>HTTP Basic: Access denied.</error>', @response.body
+ assert @response.headers['Content-Type'].start_with? 'application/xml'
+ end
+
+ test "unsuccessful authentication request defaults to plain text/html output with typical accept header" do
+ @request.env["HTTP_ACCEPT"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
+ get :index
+
+ assert_response :unauthorized
+ assert_equal "HTTP Basic: Access denied.\n", @response.body
+ assert @response.headers['Content-Type'].start_with? 'text/html'
+ end
+
test "authentication request without credential" do
get :display