167 lines
6.3 KiB
Ruby
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
|