canvas-lms/spec/middleware/request_context_generator_s...

167 lines
6.3 KiB
Ruby

# frozen_string_literal: true
#
# Copyright (C) 2014 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb')
describe "RequestContextGenerator" do
let(:env) { {} }
let(:request) { double('Rack::Request', path_parameters: { controller: 'users', action: 'index' }) }
let(:context) { double('Course', class: 'Course', id: 15) }
it "should generate the X-Canvas-Meta response header" do
_, headers, _ = RequestContextGenerator.new(->(env) {
RequestContextGenerator.add_meta_header("a1", "test1")
RequestContextGenerator.add_meta_header("a2", "test2")
RequestContextGenerator.add_meta_header("a3", "")
[ 200, {}, [] ]
}).call(env)
expect(headers['X-Canvas-Meta']).to eq "a1=test1;a2=test2;"
end
it "should add request data to X-Canvas-Meta" do
_, headers, _ = RequestContextGenerator.new(->(env) {
RequestContextGenerator.add_meta_header("a1", "test1")
RequestContextGenerator.store_request_meta(request, nil)
[ 200, {}, [] ]
}).call(env)
expect(headers['X-Canvas-Meta']).to eq "a1=test1;o=users;n=index;"
end
it "should add request and context data to X-Canvas-Meta" do
_, headers, _ = RequestContextGenerator.new(->(env) {
RequestContextGenerator.add_meta_header("a1", "test1")
RequestContextGenerator.store_request_meta(request, context)
[ 200, {}, [] ]
}).call(env)
expect(headers['X-Canvas-Meta']).to eq "a1=test1;o=users;n=index;t=Course;i=15;"
end
it "should add page view data to X-Canvas-Meta" do
pv = page_view_model
_, headers, _ = RequestContextGenerator.new(->(_env) {
RequestContextGenerator.add_meta_header("a1", "test1")
RequestContextGenerator.store_page_view_meta(pv)
[ 200, {}, [] ]
}).call(env)
f = pv.created_at.try(:utc).try(:iso8601, 2)
expect(headers['X-Canvas-Meta']).to eq "a1=test1;x=5.0;p=f;f=#{f};"
end
it "should generate a request_id and store it in Thread.current" do
Thread.current[:context] = nil
_, _, _ = RequestContextGenerator.new(->(env) {[200, {}, []]}).call(env)
expect(Thread.current[:context][:request_id]).to be_present
end
it "should add the request_id to X-Request-Context-Id" do
Thread.current[:context] = nil
_, headers, _ = RequestContextGenerator.new(->(env) {
[200, {}, []]
}).call(env)
expect(headers['X-Request-Context-Id']).to be_present
end
it "should find the session_id in a cookie and store it in Thread.current" do
Thread.current[:context] = nil
env['action_dispatch.cookies'] = { log_session_id: 'abc' }
_, _, _ = RequestContextGenerator.new(->(env) {[200, {}, []]}).call(env)
expect(Thread.current[:context][:session_id]).to eq 'abc'
end
it "should find the session_id from the rack session and add it to X-Session-Id" do
Thread.current[:context] = nil
env['rack.session.options'] = { id: 'abc' }
_, headers, _ = RequestContextGenerator.new(->(env) {
[200, {}, []]
}).call(env)
expect(headers['X-Session-Id']).to eq 'abc'
end
it "should calculate the 'queued' time if header is passed" do
Timecop.freeze do
Thread.current[:context] = nil
env['HTTP_X_REQUEST_START'] = "t=#{(1.minute.ago.to_f * 1000000).to_i}"
_, headers, _ = RequestContextGenerator.new(->(env) {
[200, {}, []]
}).call(env)
q = headers["X-Canvas-Meta"].match(/q=(\d+)/)[1].to_f
expect(q / 1000000).to eq 60.0
end
end
context "when request provides an override context id" do
let(:shared_secret){ 'sup3rs3cr3t!!' }
let(:remote_request_context_id){ '1234-5678-9012-3456-7890-1234-5678' }
let(:remote_signature) do
Canvas::Security.sign_hmac_sha512(remote_request_context_id, shared_secret)
end
before(:each) do
Thread.current[:context] = nil
Canvas::DynamicSettings.reset_cache!
Canvas::DynamicSettings.fallback_data = {
config: {
canvas: {
canvas: {
'signing-secret' => shared_secret
}
}
}
}
env['HTTP_X_REQUEST_CONTEXT_ID'] = Canvas::Security.base64_encode(remote_request_context_id)
env['HTTP_X_REQUEST_CONTEXT_SIGNATURE'] = Canvas::Security.base64_encode(remote_signature)
end
after(:each){ Canvas::DynamicSettings.fallback_data = {} }
def run_middleware
_, headers, _msg = RequestContextGenerator.new(->(_){ [200, {}, []] }).call(env)
headers
end
it "uses a provided request context id if another service submits one that is correctly signed" do
headers = run_middleware
expect(Thread.current[:context][:request_id]).to eq(remote_request_context_id)
expect(headers['X-Request-Context-Id']).to eq(remote_request_context_id)
end
it "won't accept an override without a signature" do
env['HTTP_X_REQUEST_CONTEXT_SIGNATURE'] = nil
headers = run_middleware
expect(Thread.current[:context][:request_id]).not_to eq(remote_request_context_id)
expect(headers['X-Request-Context-Id']).to eq(Thread.current[:context][:request_id])
end
it "rejects a wrong signature" do
env['HTTP_X_REQUEST_CONTEXT_SIGNATURE'] = "nonsense"
headers = run_middleware
expect(Thread.current[:context][:request_id]).not_to eq(remote_request_context_id)
expect(headers['X-Request-Context-Id']).to eq(Thread.current[:context][:request_id])
end
it "rejects a tampered ID" do
env['HTTP_X_REQUEST_CONTEXT_ID'] = "I-changed-it"
headers = run_middleware
expect(Thread.current[:context][:request_id]).not_to eq(remote_request_context_id)
expect(headers['X-Request-Context-Id']).to eq(Thread.current[:context][:request_id])
end
end
end