Skip to content

Commit

Permalink
#272 - Fix CSRF token request data parameter not being used when meth…
Browse files Browse the repository at this point in the history
…od override is used
  • Loading branch information
ellmetha committed Oct 25, 2024
1 parent a7f4f9b commit 10fba3c
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 52 deletions.
86 changes: 43 additions & 43 deletions spec/marten/handlers/concerns/request_forgery_protection_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,49 @@ describe Marten::Handlers::RequestForgeryProtection do
response.content.should eq "OK_#{unsafe_method}"
response.status.should eq 200
end

it "allows #{unsafe_method} requests if the csrftoken data parameter is specified and matches the CSRF token cookie" do
token = Marten::Handlers::RequestForgeryProtectionSpec::EXAMPLE_MASKED_SECRET_1

raw_request = ::HTTP::Request.new(
method: unsafe_method,
resource: "/test/xyz",
headers: HTTP::Headers{"Host" => "example.com", "Content-Type" => "application/x-www-form-urlencoded"},
body: "foo=bar&csrftoken=#{token}"
)
raw_request.cookies["csrftoken"] = token
request = Marten::HTTP::Request.new(raw_request)

handler = Marten::Handlers::RequestForgeryProtectionSpec::TestHandler.new(request)
response = handler.process_dispatch

response.content.should eq "OK_#{unsafe_method}"
response.status.should eq 200
end

it "allows #{unsafe_method} requests if the csrftoken data parameter is specified and matches the sessions CSRF token" do
session_store = Marten::HTTP::Session::Store::Cookie.new("sessionkey")
Marten.settings.csrf.use_session = true

token = Marten::Handlers::RequestForgeryProtectionSpec::EXAMPLE_MASKED_SECRET_1

session_store["csrftoken"] = token

raw_request = ::HTTP::Request.new(
method: unsafe_method,
resource: "/test/xyz",
headers: HTTP::Headers{"Host" => "example.com", "Content-Type" => "application/x-www-form-urlencoded"},
body: "foo=bar&csrftoken=#{token}"
)
request = Marten::HTTP::Request.new(raw_request)
request.session = session_store

handler = Marten::Handlers::RequestForgeryProtectionSpec::TestHandler.new(request)
response = handler.process_dispatch

response.content.should eq "OK_#{unsafe_method}"
response.status.should eq 200
end
end

it "allows unsafe requests if CSRF protection is disabled and requests does not contain the CSRF token" do
Expand Down Expand Up @@ -159,49 +202,6 @@ describe Marten::Handlers::RequestForgeryProtection do
response.cookies["csrftoken"].should eq csrf_token
end

it "allows unsafe requests if the csrftoken POST parameter is specified and matches the CSRF token cookie" do
token = Marten::Handlers::RequestForgeryProtectionSpec::EXAMPLE_MASKED_SECRET_1

raw_request = ::HTTP::Request.new(
method: "POST",
resource: "/test/xyz",
headers: HTTP::Headers{"Host" => "example.com", "Content-Type" => "application/x-www-form-urlencoded"},
body: "foo=bar&csrftoken=#{token}"
)
raw_request.cookies["csrftoken"] = token
request = Marten::HTTP::Request.new(raw_request)

handler = Marten::Handlers::RequestForgeryProtectionSpec::TestHandler.new(request)
response = handler.process_dispatch

response.content.should eq "OK_POST"
response.status.should eq 200
end

it "allows unsafe requests if the csrftoken POST parameter is specified and matches the sessions CSRF token" do
session_store = Marten::HTTP::Session::Store::Cookie.new("sessionkey")
Marten.settings.csrf.use_session = true

token = Marten::Handlers::RequestForgeryProtectionSpec::EXAMPLE_MASKED_SECRET_1

session_store["csrftoken"] = token

raw_request = ::HTTP::Request.new(
method: "POST",
resource: "/test/xyz",
headers: HTTP::Headers{"Host" => "example.com", "Content-Type" => "application/x-www-form-urlencoded"},
body: "foo=bar&csrftoken=#{token}"
)
request = Marten::HTTP::Request.new(raw_request)
request.session = session_store

handler = Marten::Handlers::RequestForgeryProtectionSpec::TestHandler.new(request)
response = handler.process_dispatch

response.content.should eq "OK_POST"
response.status.should eq 200
end

it "allows unsafe requests if the X-CSRF-Token header is specified and matches the CSRF token cookie" do
token = Marten::Handlers::RequestForgeryProtectionSpec::EXAMPLE_MASKED_SECRET_1

Expand Down
18 changes: 9 additions & 9 deletions src/marten/handlers/concerns/request_forgery_protection.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module Marten
# HTTP response if the token value was explicitly requested through the use of the `#get_csrf_token` method
# (otherwise no cookie is set). For each unsafe HTTP method (ie. methods that are not `GET`, `HEAD`, `OPTIONS` or
# `TRACE`), the module will verify that the CSRF token cookie is available and that a `csrftoken` field is present
# in the `POST` data hash, or that a `X-CSRF-Token` header is defined. These two token will be verified and they
# in the request data hash, or that a `X-CSRF-Token` header is defined. These two token will be verified and they
# must match; otherwise a 403 error is returned to the user. In addition to that, the module will also verify that
# the HTTP request host is either part of the allowed hosts (`Marten.settins.allowed_hosts` setting) or that the
# value of the `Origin` header matches the configured trusted origins (`Marten.settings.csrf.trusted_origins`
Expand Down Expand Up @@ -70,13 +70,13 @@ module Marten
returned_csrf_token
end

private CSRF_SAFE_HTTP_METHODS = %w(get head options trace)
private CSRF_SECRET_SIZE = 32
private CSRF_TOKEN_ALLOWED_CHARS = ["-", "_"] + ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
private CSRF_TOKEN_HEADER_NAME = "X-CSRF-Token"
private CSRF_TOKEN_INVALID_CHARS_RE = /[^a-zA-Z0-9-_]/
private CSRF_TOKEN_POST_DATA_NAME = "csrftoken"
private CSRF_TOKEN_SIZE = 64
private CSRF_SAFE_HTTP_METHODS = %w(get head options trace)
private CSRF_SECRET_SIZE = 32
private CSRF_TOKEN_ALLOWED_CHARS = ["-", "_"] + ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
private CSRF_TOKEN_HEADER_NAME = "X-CSRF-Token"
private CSRF_TOKEN_INVALID_CHARS_RE = /[^a-zA-Z0-9-_]/
private CSRF_TOKEN_REQUEST_DATA_NAME = "csrftoken"
private CSRF_TOKEN_SIZE = 64

private getter csrf_token
private getter csrf_token_update_required
Expand Down Expand Up @@ -195,7 +195,7 @@ module Marten
end

request_csrf_token = nil
request_csrf_token = request.data.fetch(CSRF_TOKEN_POST_DATA_NAME, nil) if request.post?
request_csrf_token = request.data.fetch(CSRF_TOKEN_REQUEST_DATA_NAME, nil)
request_csrf_token = request.headers[CSRF_TOKEN_HEADER_NAME]? if request_csrf_token.nil?

return reject("CSRF token is missing") if request_csrf_token.nil?
Expand Down

0 comments on commit 10fba3c

Please sign in to comment.