-
Notifications
You must be signed in to change notification settings - Fork 147
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
module ApiAuth | ||
module Middleware # :nodoc: | ||
class Excon # :nodoc: | ||
def initialize(stack) | ||
@stack = stack | ||
end | ||
|
||
def error_call(datum) | ||
@stack.error_call(datum) | ||
end | ||
|
||
def request_call(datum) | ||
request = ExconRequestWrapper.new(datum, @stack.query_string(datum)) | ||
ApiAuth.sign!(request, datum[:api_auth_access_id], datum[:api_auth_secret_key]) | ||
|
||
@stack.request_call(datum) | ||
end | ||
|
||
def response_call(datum) | ||
@stack.response_call(datum) | ||
end | ||
end | ||
|
||
class ExconRequestWrapper # :nodoc: | ||
attr_reader :datum, :query_string | ||
|
||
def initialize(datum, query_string) | ||
@datum = datum | ||
@query_string = query_string | ||
end | ||
|
||
def uri | ||
datum[:path] + query_string | ||
end | ||
|
||
def method | ||
datum[:method] | ||
end | ||
|
||
def headers | ||
datum[:headers] | ||
end | ||
|
||
def body | ||
datum[:body] | ||
end | ||
|
||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
module ApiAuth | ||
module RequestDrivers # :nodoc: | ||
class ExconRequest # :nodoc: | ||
include ApiAuth::Helpers | ||
|
||
def initialize(request) | ||
@request = request | ||
end | ||
|
||
def set_auth_header(header) | ||
@request.headers['Authorization'] = header | ||
end | ||
|
||
def calculated_md5 | ||
md5_base64digest(@request.body || '') | ||
end | ||
|
||
def populate_content_md5 | ||
return unless @request.body | ||
@request.headers['Content-MD5'] = calculated_md5 | ||
end | ||
|
||
def md5_mismatch? | ||
if @request.body | ||
calculated_md5 != content_md5 | ||
else | ||
false | ||
end | ||
end | ||
|
||
def http_method | ||
@request.method | ||
end | ||
|
||
def content_type | ||
find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE]) | ||
end | ||
|
||
def content_md5 | ||
find_header(%w[CONTENT-MD5 CONTENT_MD5]) | ||
end | ||
|
||
def original_uri | ||
find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI]) | ||
end | ||
|
||
def request_uri | ||
@request.uri | ||
end | ||
|
||
def set_date | ||
@request.headers['DATE'] = Time.now.utc.httpdate | ||
end | ||
|
||
def timestamp | ||
find_header(%w[DATE HTTP_DATE]) | ||
end | ||
|
||
def authorization_header | ||
find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION] | ||
end | ||
|
||
private | ||
|
||
def find_header(keys) | ||
headers = capitalize_keys(@request.headers) | ||
keys.map { |key| headers[key] }.compact.first | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
require 'spec_helper' | ||
require 'api_auth/middleware/excon' | ||
|
||
describe ApiAuth::RequestDrivers::ExconRequest do | ||
let(:timestamp) { Time.now.utc.httpdate } | ||
let(:body) { "hello\nworld" } | ||
let(:method) {'GET'} | ||
let(:headers) do | ||
{ | ||
'Authorization' => 'APIAuth 1044:12345', | ||
'content-md5' => '1B2M2Y8AsgTpgAmY7PhCfg==', | ||
'content-type' => 'text/plain', | ||
'date' => timestamp | ||
} | ||
end | ||
|
||
let(:request) do | ||
datum = { path: '/resource.xml', | ||
method: method, | ||
headers: headers, | ||
body: body } | ||
query_string = '?foo=bar&bar=foo' | ||
|
||
ApiAuth::Middleware::ExconRequestWrapper.new(datum, query_string) | ||
end | ||
|
||
subject(:driven_request) { described_class.new(request) } | ||
|
||
describe 'getting headers correctly' do | ||
it 'gets the content_type' do | ||
expect(driven_request.content_type).to eq('text/plain') | ||
end | ||
|
||
it 'gets the content_md5' do | ||
expect(driven_request.content_md5).to eq('1B2M2Y8AsgTpgAmY7PhCfg==') | ||
end | ||
|
||
it 'gets the request_uri' do | ||
expect(driven_request.request_uri).to eq('/resource.xml?foo=bar&bar=foo') | ||
end | ||
|
||
it 'gets the timestamp' do | ||
expect(driven_request.timestamp).to eq(timestamp) | ||
end | ||
|
||
it 'gets the authorization_header' do | ||
expect(driven_request.authorization_header).to eq('APIAuth 1044:12345') | ||
end | ||
|
||
describe '#calculated_md5' do | ||
it 'calculates md5 from the body' do | ||
expect(driven_request.calculated_md5).to eq('kZXQvrKoieG+Be1rsZVINw==') | ||
end | ||
|
||
context 'no body' do | ||
let(:body) { nil } | ||
|
||
it 'is treated as empty string' do | ||
expect(driven_request.calculated_md5).to eq('1B2M2Y8AsgTpgAmY7PhCfg==') | ||
end | ||
end | ||
end | ||
|
||
describe 'http_method' do | ||
let(:method) { 'PUT' } | ||
|
||
it 'is as passed' do | ||
expect(driven_request.http_method).to eq(method) | ||
end | ||
end | ||
end | ||
|
||
describe 'setting headers correctly' do | ||
let(:headers) { { 'content-type' => 'text/plain'} } | ||
|
||
describe '#populate_content_md5' do | ||
context 'when there is no content body' do | ||
let(:body) { nil } | ||
|
||
it "doesn't populate content-md5" do | ||
driven_request.populate_content_md5 | ||
expect(request.headers['Content-MD5']).to be_nil | ||
end | ||
end | ||
|
||
context 'when there is a content body' do | ||
let(:body) { "hello\nworld" } | ||
|
||
it 'populates content-md5' do | ||
driven_request.populate_content_md5 | ||
expect(request.headers['Content-MD5']).to eq('kZXQvrKoieG+Be1rsZVINw==') | ||
end | ||
|
||
it 'refreshes the cached headers' do | ||
driven_request.populate_content_md5 | ||
expect(driven_request.content_md5).to eq('kZXQvrKoieG+Be1rsZVINw==') | ||
end | ||
end | ||
end | ||
|
||
describe '#set_date' do | ||
before do | ||
allow(Time).to receive_message_chain(:now, :utc, :httpdate).and_return(timestamp) | ||
end | ||
|
||
it 'sets the date header of the request' do | ||
driven_request.set_date | ||
expect(request.headers['DATE']).to eq(timestamp) | ||
end | ||
|
||
it 'refreshes the cached headers' do | ||
driven_request.set_date | ||
expect(driven_request.timestamp).to eq(timestamp) | ||
end | ||
end | ||
|
||
describe '#set_auth_header' do | ||
it 'sets the auth header' do | ||
driven_request.set_auth_header('APIAuth 1044:54321') | ||
expect(request.headers['Authorization']).to eq('APIAuth 1044:54321') | ||
end | ||
end | ||
end | ||
|
||
describe 'md5_mismatch?' do | ||
context 'when there is no content body' do | ||
let(:body) { nil } | ||
|
||
it 'is false' do | ||
expect(driven_request.md5_mismatch?).to be false | ||
end | ||
end | ||
|
||
context 'when there is a content body' do | ||
let(:body) { "hello\nworld" } | ||
|
||
context 'when calculated matches sent' do | ||
before do | ||
request.headers['Content-MD5'] = 'kZXQvrKoieG+Be1rsZVINw==' | ||
end | ||
|
||
it 'is false' do | ||
expect(driven_request.md5_mismatch?).to be false | ||
end | ||
end | ||
|
||
context "when calculated doesn't match sent" do | ||
before do | ||
request.headers['Content-MD5'] = '3' | ||
end | ||
|
||
it 'is true' do | ||
expect(driven_request.md5_mismatch?).to be true | ||
end | ||
end | ||
end | ||
end | ||
end |