test: cover CorsArticlesController, show and CsrfTest
This commit is contained in:
1 parent
8c06f5decb
commit
4786db1de5
2 files changed
+141
No files matched your search
@@ -0,0 +1,90 @@
|
|||||||
|
require 'minitest/autorun'
|
||||||
|
require 'prism'
|
||||||
|
require 'support'
|
||||||
|
|
||||||
|
# Session 52's documented debt: Cors and ConditionalGet were each tested in
|
||||||
|
# isolation, but never together - and the two subsystems interact in a way
|
||||||
|
# that is easy to get wrong. Prism::Middleware::Cors stamps headers onto
|
||||||
|
# whatever [status, headers, body] the app below it returns
|
||||||
|
# (lib/prism/middleware/cors.rb#call), without special-casing any status
|
||||||
|
# code, and Controller#halt(304, '') (used by fresh_when/stale?) short-
|
||||||
|
# circuits rendering but still returns through the SAME response path as a
|
||||||
|
# normal 200 - so a cross-origin conditional GET that turns out fresh
|
||||||
|
# should still come back as a CORS-tagged 304 with an empty body, not lose
|
||||||
|
# its Access-Control-* headers just because nothing was actually rendered.
|
||||||
|
class CorsArticlesController < Prism::Controller
|
||||||
|
ETAG = 'cors-article-v1'.freeze
|
||||||
|
|
||||||
|
def show
|
||||||
|
fresh_when(etag: ETAG)
|
||||||
|
html('full article body')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class CorsConditionalGetApp < Prism::Application
|
||||||
|
get '/api/articles/1', to: 'cors_articles#show'
|
||||||
|
end
|
||||||
|
|
||||||
|
class CorsConditionalGetTest < Minitest::Test
|
||||||
|
include TestSupport
|
||||||
|
|
||||||
|
ORIGIN = 'https://app.example.com'.freeze
|
||||||
|
FRESH_ETAG = 'W/"' + Digest::SHA1.hexdigest(CorsArticlesController::ETAG) + '"'
|
||||||
|
|
||||||
|
def setup
|
||||||
|
Prism::Cors.reset!
|
||||||
|
Prism::Cors.configure do |config|
|
||||||
|
config.allow do
|
||||||
|
origins ORIGIN
|
||||||
|
resource '/api/*', methods: %w[GET]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
@app = Prism::Middleware::Cors.new(CorsConditionalGetApp)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
Prism::Cors.reset!
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(headers)
|
||||||
|
@app.call(env_for('GET', '/api/articles/1', headers: headers))
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_first_cross_origin_request_renders_and_still_gets_cors_headers
|
||||||
|
status, headers, body = call('Origin' => ORIGIN)
|
||||||
|
|
||||||
|
assert_equal 200, status
|
||||||
|
assert_equal 'full article body', body.join
|
||||||
|
assert_equal FRESH_ETAG, headers['ETag']
|
||||||
|
assert_equal ORIGIN, headers['Access-Control-Allow-Origin']
|
||||||
|
assert_equal 'Origin', headers['Vary']
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fresh_cross_origin_request_gets_304_with_cors_headers_intact
|
||||||
|
status, headers, body = call('Origin' => ORIGIN, 'If-None-Match' => FRESH_ETAG)
|
||||||
|
|
||||||
|
assert_equal 304, status
|
||||||
|
assert_equal '', body.join
|
||||||
|
assert_equal FRESH_ETAG, headers['ETag'], 'ETag must still be set on a 304, the client needs it to keep caching'
|
||||||
|
assert_equal ORIGIN, headers['Access-Control-Allow-Origin'],
|
||||||
|
'a 304 short-circuited by halt() must not lose the CORS headers Cors already knows to stamp on any status'
|
||||||
|
assert_equal 'Origin', headers['Vary']
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fresh_same_origin_request_gets_304_without_cors_headers
|
||||||
|
status, headers, body = call('If-None-Match' => FRESH_ETAG)
|
||||||
|
|
||||||
|
assert_equal 304, status
|
||||||
|
assert_equal '', body.join
|
||||||
|
assert_equal FRESH_ETAG, headers['ETag']
|
||||||
|
assert_nil headers['Access-Control-Allow-Origin']
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_stale_cross_origin_request_from_a_disallowed_origin_still_renders_without_cors_headers
|
||||||
|
status, headers, body = call('Origin' => 'https://evil.example.com', 'If-None-Match' => FRESH_ETAG)
|
||||||
|
|
||||||
|
assert_equal 304, status, 'ConditionalGet does not know or care about CORS - freshness alone decides the status'
|
||||||
|
assert_equal '', body.join
|
||||||
|
assert_nil headers['Access-Control-Allow-Origin']
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
require 'minitest/autorun'
|
||||||
|
require 'prism'
|
||||||
|
|
||||||
|
class CsrfTest < Minitest::Test
|
||||||
|
def setup
|
||||||
|
@session = {}
|
||||||
|
@csrf = Prism::Csrf.new(@session)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raw_token_is_persisted_in_session
|
||||||
|
token = @csrf.raw_token
|
||||||
|
assert_equal token, @session[Prism::Csrf::SESSION_KEY]
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_raw_token_is_stable_across_calls
|
||||||
|
assert_equal @csrf.raw_token, @csrf.raw_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_masked_token_differs_each_call
|
||||||
|
refute_equal @csrf.masked_token, @csrf.masked_token
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_masked_token_is_valid
|
||||||
|
assert @csrf.valid_token?(@csrf.masked_token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_multiple_masked_tokens_from_same_raw_token_all_validate
|
||||||
|
tokens = Array.new(5) { @csrf.masked_token }
|
||||||
|
assert tokens.all? { |token| @csrf.valid_token?(token) }
|
||||||
|
assert_equal 5, tokens.uniq.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_token_from_different_session_is_rejected
|
||||||
|
other_session = {}
|
||||||
|
other_csrf = Prism::Csrf.new(other_session)
|
||||||
|
refute @csrf.valid_token?(other_csrf.masked_token)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_blank_or_garbage_tokens_are_rejected
|
||||||
|
refute @csrf.valid_token?(nil)
|
||||||
|
refute @csrf.valid_token?('')
|
||||||
|
refute @csrf.valid_token?('not-base64!!!')
|
||||||
|
refute @csrf.valid_token?('dGhpcyBpcyB0b28gc2hvcnQ=')
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tampering_with_masked_token_invalidates_it
|
||||||
|
masked = @csrf.masked_token
|
||||||
|
tampered = masked.sub(/.\z/) { |c| c == 'a' ? 'b' : 'a' }
|
||||||
|
refute @csrf.valid_token?(tampered)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user