parent
126e5a033c
commit
cc44710654
4 changed files with 0 additions and 207 deletions
|
@ -1,72 +0,0 @@
|
||||||
require "../spec_helper"
|
|
||||||
|
|
||||||
describe "Kemal::Middleware::CSRF" do
|
|
||||||
it "sends GETs to next handler" do
|
|
||||||
handler = Kemal::Middleware::CSRF.new
|
|
||||||
request = HTTP::Request.new("GET", "/")
|
|
||||||
io_with_context = create_request_and_return_io(handler, request)
|
|
||||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
|
||||||
client_response.status_code.should eq 404
|
|
||||||
end
|
|
||||||
|
|
||||||
it "blocks POSTs without the token" do
|
|
||||||
handler = Kemal::Middleware::CSRF.new
|
|
||||||
request = HTTP::Request.new("POST", "/")
|
|
||||||
io_with_context = create_request_and_return_io(handler, request)
|
|
||||||
client_response = HTTP::Client::Response.from_io(io_with_context, decompress: false)
|
|
||||||
client_response.status_code.should eq 403
|
|
||||||
end
|
|
||||||
|
|
||||||
it "allows POSTs with the correct token in FORM submit" do
|
|
||||||
handler = Kemal::Middleware::CSRF.new
|
|
||||||
request = HTTP::Request.new("POST", "/",
|
|
||||||
body: "authenticity_token=cemal&hasan=lamec",
|
|
||||||
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded"})
|
|
||||||
io, context = process_request(handler, request)
|
|
||||||
client_response = HTTP::Client::Response.from_io(io, decompress: false)
|
|
||||||
client_response.status_code.should eq 403
|
|
||||||
|
|
||||||
current_token = context.session["csrf"]
|
|
||||||
|
|
||||||
handler = Kemal::Middleware::CSRF.new
|
|
||||||
request = HTTP::Request.new("POST", "/",
|
|
||||||
body: "authenticity_token=#{current_token}&hasan=lamec",
|
|
||||||
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded",
|
|
||||||
"Set-Cookie" => client_response.headers["Set-Cookie"]})
|
|
||||||
io, context = process_request(handler, request)
|
|
||||||
client_response = HTTP::Client::Response.from_io(io, decompress: false)
|
|
||||||
client_response.status_code.should eq 404
|
|
||||||
end
|
|
||||||
|
|
||||||
it "allows POSTs with the correct token in HTTP header" do
|
|
||||||
handler = Kemal::Middleware::CSRF.new
|
|
||||||
request = HTTP::Request.new("POST", "/",
|
|
||||||
body: "hasan=lamec",
|
|
||||||
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded"})
|
|
||||||
io, context = process_request(handler, request)
|
|
||||||
client_response = HTTP::Client::Response.from_io(io, decompress: false)
|
|
||||||
client_response.status_code.should eq 403
|
|
||||||
|
|
||||||
current_token = context.session["csrf"].as(String)
|
|
||||||
|
|
||||||
handler = Kemal::Middleware::CSRF.new
|
|
||||||
request = HTTP::Request.new("POST", "/",
|
|
||||||
body: "hasan=lamec",
|
|
||||||
headers: HTTP::Headers{"Content-Type" => "application/x-www-form-urlencoded",
|
|
||||||
"Set-Cookie" => client_response.headers["Set-Cookie"],
|
|
||||||
"x-csrf-token" => current_token})
|
|
||||||
io, context = process_request(handler, request)
|
|
||||||
client_response = HTTP::Client::Response.from_io(io, decompress: false)
|
|
||||||
client_response.status_code.should eq 404
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_request(handler, request)
|
|
||||||
io = IO::Memory.new
|
|
||||||
response = HTTP::Server::Response.new(io)
|
|
||||||
context = HTTP::Server::Context.new(request, response)
|
|
||||||
handler.call(context)
|
|
||||||
response.close
|
|
||||||
io.rewind
|
|
||||||
{io, context}
|
|
||||||
end
|
|
|
@ -1,86 +0,0 @@
|
||||||
require "./spec_helper"
|
|
||||||
|
|
||||||
describe "Session" do
|
|
||||||
it "can establish a session" do
|
|
||||||
sid = nil
|
|
||||||
existing = nil
|
|
||||||
get "/" do |env|
|
|
||||||
sess = env.session
|
|
||||||
existing = sess["token"]?
|
|
||||||
sess.delete("token")
|
|
||||||
sid = sess.id
|
|
||||||
sess["token"] = "abc"
|
|
||||||
"Hello"
|
|
||||||
end
|
|
||||||
|
|
||||||
# make first request without any cookies/session
|
|
||||||
request = HTTP::Request.new("GET", "/")
|
|
||||||
response = call_request_on_app(request)
|
|
||||||
|
|
||||||
# verify we got a cookie and session ID
|
|
||||||
cookie = response.headers["Set-Cookie"]?
|
|
||||||
cookie.should_not be_nil
|
|
||||||
response.cookies[Kemal.config.session["name"].as(String)].value.should eq(sid)
|
|
||||||
lastsid = sid
|
|
||||||
existing.should be_nil
|
|
||||||
|
|
||||||
# make second request with cookies to get session
|
|
||||||
request = HTTP::Request.new("GET", "/", response.headers)
|
|
||||||
response = call_request_on_app(request)
|
|
||||||
|
|
||||||
# verify we got cookies and we could see values set
|
|
||||||
# in the previous request
|
|
||||||
cookie2 = response.headers["Set-Cookie"]?
|
|
||||||
cookie2.should_not be_nil
|
|
||||||
cookie2.should eq(cookie)
|
|
||||||
response.cookies[Kemal.config.session["name"].as(String)].value.should eq(lastsid)
|
|
||||||
existing.should eq("abc")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can prune old sessions" do
|
|
||||||
s = Kemal::Sessions::STORE
|
|
||||||
s.clear
|
|
||||||
|
|
||||||
Kemal::Sessions.prune!
|
|
||||||
|
|
||||||
id = "foo"
|
|
||||||
s[id] = Kemal::Sessions::Session.new(id)
|
|
||||||
s.size.should eq(1)
|
|
||||||
Kemal::Sessions.prune!
|
|
||||||
s.size.should eq(1)
|
|
||||||
|
|
||||||
s[id].last_access_at = (Time.now - 1.week).epoch_ms
|
|
||||||
Kemal::Sessions.prune!
|
|
||||||
s.size.should eq(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "supports many types" do
|
|
||||||
who = nil
|
|
||||||
age = nil
|
|
||||||
awesome = nil
|
|
||||||
velocity = nil
|
|
||||||
get "/" do |env|
|
|
||||||
sess = env.session
|
|
||||||
who = sess["who"]?
|
|
||||||
age = sess["age"]?
|
|
||||||
velocity = sess["velocity"]?
|
|
||||||
awesome = sess["awesome"]?
|
|
||||||
arr = sess["arr"]?
|
|
||||||
sess["who"] = "Kemal"
|
|
||||||
sess["age"] = 2016
|
|
||||||
sess["velocity"] = 9999.9
|
|
||||||
sess["awesome"] = true
|
|
||||||
"Hello"
|
|
||||||
end
|
|
||||||
|
|
||||||
request = HTTP::Request.new("GET", "/")
|
|
||||||
response = call_request_on_app(request)
|
|
||||||
request = HTTP::Request.new("GET", "/", response.headers)
|
|
||||||
response = call_request_on_app(request)
|
|
||||||
|
|
||||||
who.should eq "Kemal"
|
|
||||||
age.should eq 2016
|
|
||||||
velocity.should eq 9999.9
|
|
||||||
awesome.should eq true
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -29,11 +29,6 @@ class HTTP::Server
|
||||||
route_lookup.found?
|
route_lookup.found?
|
||||||
end
|
end
|
||||||
|
|
||||||
def session
|
|
||||||
@session ||= Kemal::Sessions.new(self)
|
|
||||||
@session.not_nil!
|
|
||||||
end
|
|
||||||
|
|
||||||
def get(name)
|
def get(name)
|
||||||
@store[name]
|
@store[name]
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
require "secure_random"
|
|
||||||
|
|
||||||
module Kemal::Middleware
|
|
||||||
# This middleware adds CSRF protection to your application.
|
|
||||||
#
|
|
||||||
# Returns 403 "Forbidden" unless the current CSRF token is submitted
|
|
||||||
# with any non-GET/HEAD request.
|
|
||||||
#
|
|
||||||
# Without CSRF protection, your app is vulnerable to replay attacks
|
|
||||||
# where an attacker can re-submit a form.
|
|
||||||
#
|
|
||||||
class CSRF < HTTP::Handler
|
|
||||||
HEADER = "X_CSRF_TOKEN"
|
|
||||||
ALLOWED_METHODS = %w(GET HEAD OPTIONS TRACE)
|
|
||||||
PARAMETER_NAME = "authenticity_token"
|
|
||||||
|
|
||||||
def call(context)
|
|
||||||
unless context.session["csrf"]?
|
|
||||||
context.session["csrf"] = SecureRandom.hex(16)
|
|
||||||
end
|
|
||||||
|
|
||||||
return call_next(context) if ALLOWED_METHODS.includes?(context.request.method)
|
|
||||||
|
|
||||||
req = context.request
|
|
||||||
submitted = if req.headers[HEADER]?
|
|
||||||
req.headers[HEADER]
|
|
||||||
elsif context.params.body[PARAMETER_NAME]?
|
|
||||||
context.params.body[PARAMETER_NAME]
|
|
||||||
else
|
|
||||||
"nothing"
|
|
||||||
end
|
|
||||||
current_token = context.session["csrf"]
|
|
||||||
|
|
||||||
if current_token == submitted
|
|
||||||
# reset the token so it can't be used again
|
|
||||||
context.session["csrf"] = SecureRandom.hex(16)
|
|
||||||
return call_next(context)
|
|
||||||
else
|
|
||||||
context.response.status_code = 403
|
|
||||||
context.response.print "Forbidden"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
Loading…
Reference in a new issue