controllers: don't allow GET requests with params in the body.
Don't allow GET requests to pass the request params in the body instead of in the URL. While Rails can handle GET params passed in the body, it goes against spec and it may cause problems if the response is a redirect and the client doesn't send the body params when following the redirect. This may be a breaking change for broken API clients who were sending GET params in the body instead of in the URL. This can happen when people use HTTP libraries incorrectly.
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
class PageRemovedError < StandardError; end
|
class PageRemovedError < StandardError; end
|
||||||
|
class RequestBodyNotAllowedError < StandardError; end
|
||||||
|
|
||||||
include Pundit::Authorization
|
include Pundit::Authorization
|
||||||
helper_method :search_params, :permitted_attributes
|
helper_method :search_params, :permitted_attributes
|
||||||
@@ -9,6 +10,7 @@ class ApplicationController < ActionController::Base
|
|||||||
self.responder = ApplicationResponder
|
self.responder = ApplicationResponder
|
||||||
|
|
||||||
skip_forgery_protection if: -> { SessionLoader.new(request).has_api_authentication? }
|
skip_forgery_protection if: -> { SessionLoader.new(request).has_api_authentication? }
|
||||||
|
before_action :check_get_body
|
||||||
before_action :reset_current_user
|
before_action :reset_current_user
|
||||||
before_action :set_current_user
|
before_action :set_current_user
|
||||||
before_action :normalize_search
|
before_action :normalize_search
|
||||||
@@ -120,6 +122,8 @@ class ApplicationController < ActionController::Base
|
|||||||
render_error_page(401, exception, message: exception.message, template: "sessions/new")
|
render_error_page(401, exception, message: exception.message, template: "sessions/new")
|
||||||
when ActionController::InvalidAuthenticityToken, ActionController::UnpermittedParameters, ActionController::InvalidCrossOriginRequest, ActionController::Redirecting::UnsafeRedirectError
|
when ActionController::InvalidAuthenticityToken, ActionController::UnpermittedParameters, ActionController::InvalidCrossOriginRequest, ActionController::Redirecting::UnsafeRedirectError
|
||||||
render_error_page(403, exception, message: exception.message)
|
render_error_page(403, exception, message: exception.message)
|
||||||
|
when RequestBodyNotAllowedError
|
||||||
|
render_error_page(403, exception, message: "Request body not allowed for #{request.method} request")
|
||||||
when ActiveSupport::MessageVerifier::InvalidSignature, # raised by `find_signed!`
|
when ActiveSupport::MessageVerifier::InvalidSignature, # raised by `find_signed!`
|
||||||
User::PrivilegeError,
|
User::PrivilegeError,
|
||||||
Pundit::NotAuthorizedError
|
Pundit::NotAuthorizedError
|
||||||
@@ -193,6 +197,10 @@ class ApplicationController < ActionController::Base
|
|||||||
request.variant = params[:variant].try(:to_sym)
|
request.variant = params[:variant].try(:to_sym)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_get_body
|
||||||
|
raise RequestBodyNotAllowedError if request.method.in?(%w[GET HEAD OPTIONS]) && request.body.size > 0
|
||||||
|
end
|
||||||
|
|
||||||
# allow api clients to force errors for testing purposes.
|
# allow api clients to force errors for testing purposes.
|
||||||
def cause_error
|
def cause_error
|
||||||
return unless params[:cause_error].present?
|
return unless params[:cause_error].present?
|
||||||
|
|||||||
@@ -10,6 +10,20 @@ class ApplicationControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_response 406
|
assert_response 406
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "return 403 Bad Request for a GET request with a body" do
|
||||||
|
get root_path, headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json" }, env: { RAW_POST_DATA: "tags=touhou" }
|
||||||
|
|
||||||
|
assert_response 403
|
||||||
|
assert_equal("ApplicationController::RequestBodyNotAllowedError", response.parsed_body["error"])
|
||||||
|
assert_equal("Request body not allowed for GET request", response.parsed_body["message"])
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return 200 OK for a POST request overriden to be a GET request" do
|
||||||
|
post root_path, headers: { "Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json", "X-Http-Method-Override": "GET" }, env: { RAW_POST_DATA: "tags=touhou" }
|
||||||
|
|
||||||
|
assert_response 200
|
||||||
|
end
|
||||||
|
|
||||||
context "on a RecordNotFound error" do
|
context "on a RecordNotFound error" do
|
||||||
should "return 404 Not Found even with a bad file extension" do
|
should "return 404 Not Found even with a bad file extension" do
|
||||||
get post_path("bad.json")
|
get post_path("bad.json")
|
||||||
|
|||||||
Reference in New Issue
Block a user