Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Excon #154

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ Simply add this configuration to your Flexirest initializer in your app and it w
Flexirest::Base.api_auth_credentials(@access_id, @secret_key)
```

### Excon

ApiAuth can also sign all requests made with [Excon](https://github.com/excon/excon).

``` ruby
require 'api_auth/middleware/excon'

Excon.defaults[:api_auth_access_id] = <access_id>
Excon.defaults[:api_auth_secret_key] = <secret_key>
Excon.defaults[:middlewares] << ApiAuth::Middleware::Excon
```

## Server

ApiAuth provides some built in methods to help you generate API keys for your
Expand Down
1 change: 1 addition & 0 deletions lib/api_auth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
require 'api_auth/request_drivers/rack'
require 'api_auth/request_drivers/httpi'
require 'api_auth/request_drivers/faraday'
require 'api_auth/request_drivers/excon'

require 'api_auth/headers'
require 'api_auth/base'
Expand Down
49 changes: 49 additions & 0 deletions lib/api_auth/middleware/excon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
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
72 changes: 72 additions & 0 deletions lib/api_auth/request_drivers/excon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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
@request
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
158 changes: 158 additions & 0 deletions spec/request_drivers/excon_spec.rb
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