ey-hmac 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/Gemfile +22 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +81 -0
- data/Rakefile +1 -0
- data/ey-hmac.gemspec +19 -0
- data/lib/ey-hmac/adapter/faraday.rb +43 -0
- data/lib/ey-hmac/adapter/rack.rb +50 -0
- data/lib/ey-hmac/adapter.rb +127 -0
- data/lib/ey-hmac/faraday.rb +25 -0
- data/lib/ey-hmac/rack.rb +15 -0
- data/lib/ey-hmac/version.rb +5 -0
- data/lib/ey-hmac.rb +82 -0
- data/spec/faraday_spec.rb +61 -0
- data/spec/rack_spec.rb +57 -0
- data/spec/shared/authenticated.rb +55 -0
- data/spec/spec_helper.rb +9 -0
- metadata +68 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in ey-hmac.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
group(:test) do
|
7
|
+
gem 'rb-fsevent'
|
8
|
+
gem 'guard-bundler'
|
9
|
+
gem 'guard-rspec'
|
10
|
+
gem 'pry-nav'
|
11
|
+
end
|
12
|
+
|
13
|
+
group(:rack) do
|
14
|
+
gem 'rack'
|
15
|
+
gem 'rack-test'
|
16
|
+
gem 'rack-client'
|
17
|
+
end
|
18
|
+
|
19
|
+
group(:faraday) do
|
20
|
+
gem 'faraday'
|
21
|
+
gem 'faraday_middleware'
|
22
|
+
end
|
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Josh Lane & Jason Hansen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# Ey::Hmac
|
2
|
+
|
3
|
+
Lightweight libraries and middlewares to perform HMAC signing and verification
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'ey-hmac'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install ey-hmac
|
18
|
+
|
19
|
+
## Documentation
|
20
|
+
|
21
|
+
[rdoc.info](http://rubydoc.info/gems/ey-hmac)
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
## Client Middleware
|
26
|
+
|
27
|
+
### Rack
|
28
|
+
|
29
|
+
Using ```Rack::Client```
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
client = Rack::Client.new do
|
33
|
+
use Ey::Hmac::Rack, key_id, key_secret
|
34
|
+
run app
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
### Faraday
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
require 'ey-hmac/faraday'
|
42
|
+
|
43
|
+
connection = Faraday.new do |c|
|
44
|
+
c.request :hmac, key_id, key_secret
|
45
|
+
c.adapter(:rack, app)
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
## Server
|
50
|
+
|
51
|
+
### Rack
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
app = lambda do |env|
|
55
|
+
authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
56
|
+
(auth_id == key_id) && key_secret
|
57
|
+
end
|
58
|
+
[(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
|
59
|
+
end
|
60
|
+
|
61
|
+
```
|
62
|
+
|
63
|
+
### Rails
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
Ey::Hmac.authenticated?(request.env) do |auth_id|
|
67
|
+
if consumer_credential = ConsumerCredential.where(deleted_at: nil, auth_id: auth_id).first
|
68
|
+
consumer = consumer_credential.try(:consumer)
|
69
|
+
consumer && consumer.enabled && consumer_credential.auth_key
|
70
|
+
end
|
71
|
+
end && consumer
|
72
|
+
```
|
73
|
+
|
74
|
+
|
75
|
+
## Contributing
|
76
|
+
|
77
|
+
1. Fork it
|
78
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
79
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
80
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
81
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/ey-hmac.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ey-hmac/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "ey-hmac"
|
8
|
+
gem.version = Ey::Hmac::VERSION
|
9
|
+
gem.authors = ["Josh Lane & Jason Hansen"]
|
10
|
+
gem.email = ["jlane@engineyard.com"]
|
11
|
+
gem.description = %q{Lightweight HMAC signing libraries and middleware for Farday and Rack}
|
12
|
+
gem.summary = %q{Lightweight HMAC signing libraries and middleware for Farday and Rack}
|
13
|
+
gem.homepage = ""
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Ey::Hmac::Adapter::Faraday < Ey::Hmac::Adapter
|
2
|
+
def method
|
3
|
+
request[:method].to_s.upcase
|
4
|
+
end
|
5
|
+
|
6
|
+
def content_type
|
7
|
+
%w[CONTENT-TYPE CONTENT_TYPE Content-Type Content_Type].inject(nil){|r, h| r || request[h]}
|
8
|
+
end
|
9
|
+
|
10
|
+
def content_digest
|
11
|
+
existing = %w[CONTENT-DIGEST CONTENT_DIGEST Content-Digest Content_Digest].inject(nil){|r,h| r || request[:request_headers][h]}
|
12
|
+
existing || (request[:request_headers]['Content-Digest'] = (body && Digest::MD5.hexdigest(body)))
|
13
|
+
end
|
14
|
+
|
15
|
+
def body
|
16
|
+
if request[:body] && request[:body].to_s != ""
|
17
|
+
request[:body]
|
18
|
+
else nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def date
|
23
|
+
existing = %w[DATE Date].inject(nil){|r,h| r || request[h]}
|
24
|
+
existing || (request[:request_headers]['Date'] = Time.now.httpdate)
|
25
|
+
end
|
26
|
+
|
27
|
+
def path
|
28
|
+
request[:url].path
|
29
|
+
end
|
30
|
+
|
31
|
+
def sign!(key_id, key_secret)
|
32
|
+
%w[CONTENT-TYPE CONTENT_TYPE Content-Type Content_Type].inject(nil){|r,h| request[:request_headers][h]}
|
33
|
+
if options[:version]
|
34
|
+
request[:request_headers]['X-Signature-Version'] = options[:version]
|
35
|
+
end
|
36
|
+
|
37
|
+
request[:request_headers][authorization_header] = authorization(key_id, key_secret)
|
38
|
+
end
|
39
|
+
|
40
|
+
def authorization_signature
|
41
|
+
%w[Authorization AUTHORIZATION].inject(nil){|r, h| r || request[:request_headers][h]}
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
class Ey::Hmac::Adapter::Rack < Ey::Hmac::Adapter
|
4
|
+
def initialize(request, options)
|
5
|
+
super
|
6
|
+
@request = ::Rack::Request.new(request) if request.is_a?(Hash)
|
7
|
+
end
|
8
|
+
|
9
|
+
def method
|
10
|
+
request.request_method.to_s.upcase
|
11
|
+
end
|
12
|
+
|
13
|
+
def content_type
|
14
|
+
request.content_type
|
15
|
+
end
|
16
|
+
|
17
|
+
def content_digest
|
18
|
+
request.env['HTTP_CONTENT_DIGEST'] ||= body && Digest::MD5.hexdigest(body)
|
19
|
+
end
|
20
|
+
|
21
|
+
def body
|
22
|
+
if request.env["rack.input"]
|
23
|
+
request.env["rack.input"].rewind
|
24
|
+
body = request.env["rack.input"].read
|
25
|
+
request.env["rack.input"].rewind
|
26
|
+
body == "" ? nil : body
|
27
|
+
else nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def date
|
32
|
+
request.env['HTTP_DATE'] ||= Time.now.httpdate
|
33
|
+
end
|
34
|
+
|
35
|
+
def path
|
36
|
+
request.path
|
37
|
+
end
|
38
|
+
|
39
|
+
def sign!(key_id, key_secret)
|
40
|
+
if options[:version]
|
41
|
+
request.env['HTTP_X_SIGNATURE_VERSION'] = options[:version]
|
42
|
+
end
|
43
|
+
|
44
|
+
request.env["HTTP_#{authorization_header.to_s.upcase}"] = authorization(key_id, key_secret)
|
45
|
+
end
|
46
|
+
|
47
|
+
def authorization_signature
|
48
|
+
request.env["HTTP_#{authorization_header.to_s.upcase}"]
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# This class is responsible for forming the canonical string to used to sign requests
|
2
|
+
# @abstract override methods {#method}, {#path}, {#body}, {#content_type} and {#content_digest}
|
3
|
+
class Ey::Hmac::Adapter
|
4
|
+
AUTHORIZATION_REGEXP = /\w+ ([^:]+):(.+)$/
|
5
|
+
|
6
|
+
autoload :Rack, "ey-hmac/adapter/rack"
|
7
|
+
autoload :Faraday, "ey-hmac/adapter/faraday"
|
8
|
+
|
9
|
+
attr_reader :request, :options, :authorization_header, :service
|
10
|
+
|
11
|
+
# @param [Object] request signer-specific request implementation
|
12
|
+
# @option options [Integer] :version signature version
|
13
|
+
# @option options [String] :authorization_header ('Authorization') Authorization header key.
|
14
|
+
# @option options [String] :server ('EyHmac') service name prefixed to {#authorization}. set to {#service}
|
15
|
+
def initialize(request, options={})
|
16
|
+
@request, @options = request, options
|
17
|
+
|
18
|
+
@authorization_header = options[:authorization_header] || 'Authorization'
|
19
|
+
@service = options[:service] || 'EyHmac'
|
20
|
+
end
|
21
|
+
|
22
|
+
# In order for the server to correctly authorize the request, the client and server MUST AGREE on this format
|
23
|
+
#
|
24
|
+
# default canonical string formation is '{#method}\\n{#content_type}\\n{#content_digest}\\n{#date}\\n{#path}'
|
25
|
+
# @return [String] canonical string used to form the {#signature}
|
26
|
+
def canonicalize
|
27
|
+
[method, content_type, content_digest, date, path].join("\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [String] key_secret private HMAC key
|
31
|
+
# @return [String] HMAC signature of {#request}
|
32
|
+
# @todo handle multiple hash functions
|
33
|
+
def signature(key_secret)
|
34
|
+
Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), key_secret, canonicalize)).strip
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [String] key_id public HMAC key
|
38
|
+
# @param [String] key_secret private HMAC key
|
39
|
+
# @return [String] HMAC header value of {#request}
|
40
|
+
def authorization(key_id, key_secret)
|
41
|
+
"#{service} #{key_id}:#{signature(key_secret)}"
|
42
|
+
end
|
43
|
+
|
44
|
+
# @abstract
|
45
|
+
# @return [String] upcased request verb. i.e. 'GET'
|
46
|
+
def method
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
|
50
|
+
# @abstract
|
51
|
+
# @return [String] request path. i.e. '/blogs/1'
|
52
|
+
def path
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
|
56
|
+
# @abstract
|
57
|
+
# Digest of body. Default is MD5.
|
58
|
+
# @todo support explicit digest methods
|
59
|
+
# @return [String] digest of body
|
60
|
+
def content_digest
|
61
|
+
raise NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
# @abstract
|
65
|
+
# @return [String] request body.
|
66
|
+
# @return [NilClass] if there is no body or the body is empty
|
67
|
+
def body
|
68
|
+
raise NotImplementedError
|
69
|
+
end
|
70
|
+
|
71
|
+
# @abstract
|
72
|
+
# @return [String] value of the Content-Type header in {#request}
|
73
|
+
def content_type
|
74
|
+
raise NotImplementedError
|
75
|
+
end
|
76
|
+
|
77
|
+
# @abstract
|
78
|
+
# @return [String] value of the Date header in {#request}.
|
79
|
+
# @see {Time#http_date}
|
80
|
+
def date
|
81
|
+
raise NotImplementedError
|
82
|
+
end
|
83
|
+
|
84
|
+
# @abstract used when verifying a signed request
|
85
|
+
# @return [String] value of the {#authorization_header}
|
86
|
+
def authorization_signature
|
87
|
+
raise NotImplementedError
|
88
|
+
end
|
89
|
+
|
90
|
+
# @abstract
|
91
|
+
# Add {#signature} header to request. Typically this is 'Authorization' or 'WWW-Authorization'
|
92
|
+
def sign!(key_id, key_secret)
|
93
|
+
raise NotImplementedError
|
94
|
+
end
|
95
|
+
|
96
|
+
# Check {#authorization_signature} against calculated {#signature}
|
97
|
+
# @yieldparam key_id [String] public HMAC key
|
98
|
+
# @return [Boolean] true if block yields matching private key and signature matches, else false
|
99
|
+
# @see {#authenticated!}
|
100
|
+
def authenticated?(&block)
|
101
|
+
authenticated!(&block)
|
102
|
+
rescue Ey::Hmac::Error
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
# Check {#authorization_signature} against calculated {#signature}
|
107
|
+
# @yieldparam key_id [String] public HMAC key
|
108
|
+
# @return [Boolean] true if block yields matching private key
|
109
|
+
# @raise [Ey::Hmac::Error] if authentication fails
|
110
|
+
def authenticated!(&block)
|
111
|
+
if authorization_match = AUTHORIZATION_REGEXP.match(authorization_signature)
|
112
|
+
key_id = authorization_match[1]
|
113
|
+
signature_value = authorization_match[2]
|
114
|
+
|
115
|
+
if key_secret = block.call(key_id)
|
116
|
+
if signature_value == (calculated_signature = signature(key_secret))
|
117
|
+
else raise(Ey::Hmac::SignatureMismatch, "Calculated siganature #{signature_value} does not match #{calculated_signature} using #{canonicalize.inspect}")
|
118
|
+
end
|
119
|
+
else raise(Ey::Hmac::MissingSecret, "Failed to find secret matching #{key_id.inspect}")
|
120
|
+
end
|
121
|
+
else
|
122
|
+
raise(Ey::Hmac::MissingAuthorization, "Failed to parse authorization_signature #{authorization_signature}")
|
123
|
+
end
|
124
|
+
true
|
125
|
+
end
|
126
|
+
alias authenticate! authenticated!
|
127
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
if Faraday.respond_to? :register_middleware
|
4
|
+
Faraday.register_middleware(:request, {:hmac => lambda { Ey::Hmac::Faraday }})
|
5
|
+
end
|
6
|
+
|
7
|
+
# Request middleware that performs HMAC request signing
|
8
|
+
class Ey::Hmac::Faraday < Faraday::Middleware
|
9
|
+
dependency do
|
10
|
+
require 'ey-hmac' unless defined?(Ey::Hmac)
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :key_id, :key_secret, :options
|
14
|
+
|
15
|
+
def initialize(app, key_id, key_secret, options = {})
|
16
|
+
super(app)
|
17
|
+
@key_id, @key_secret = key_id, key_secret
|
18
|
+
@options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def call(env)
|
22
|
+
Ey::Hmac.sign!(env, key_id, key_secret, {adapter: Ey::Hmac::Adapter::Faraday}.merge(options))
|
23
|
+
@app.call(env)
|
24
|
+
end
|
25
|
+
end
|
data/lib/ey-hmac/rack.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Request middleware that performs HMAC request signing
|
2
|
+
class Ey::Hmac::Rack
|
3
|
+
attr_reader :key_id, :key_secret, :options
|
4
|
+
|
5
|
+
def initialize(app, key_id, key_secret, options = {})
|
6
|
+
@app = app
|
7
|
+
@key_id, @key_secret = key_id, key_secret
|
8
|
+
@options = options
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(env)
|
12
|
+
Ey::Hmac.sign!(env, key_id, key_secret, {adapter: Ey::Hmac::Adapter::Rack}.merge(options))
|
13
|
+
@app.call(env)
|
14
|
+
end
|
15
|
+
end
|
data/lib/ey-hmac.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
require "ey-hmac/version"
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
module Ey
|
8
|
+
module Hmac
|
9
|
+
Error = Class.new(StandardError)
|
10
|
+
|
11
|
+
MissingSecret = Class.new(Error)
|
12
|
+
MissingAuthorization = Class.new(Error)
|
13
|
+
SignatureMismatch = Class.new(Error)
|
14
|
+
|
15
|
+
autoload :Adapter, "ey-hmac/adapter"
|
16
|
+
autoload :Faraday, "ey-hmac/faraday"
|
17
|
+
autoload :Rack, "ey-hmac/rack"
|
18
|
+
|
19
|
+
def self.default_adapter=(default_adapter)
|
20
|
+
@default_adapter = default_adapter
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.default_adapter
|
24
|
+
@default_adapter ||= begin
|
25
|
+
if defined?(Rack) || defined?(Rails)
|
26
|
+
Ey::Hmac::Adapter::Rack
|
27
|
+
elsif defined?(Faraday)
|
28
|
+
Ey::Hmac::Adapter::Rails
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @example
|
34
|
+
# Ey::Hmac.sign!(env, @key_id, @key_secret)
|
35
|
+
#
|
36
|
+
# @param request [Hash] request environment
|
37
|
+
# @option options [Ey::Hmac::Adapter] :adapter (#{default_adapter}) adapter to sign request with
|
38
|
+
# @option options [Integer] :version (nil) signature version
|
39
|
+
# @option options [String] :authorization_header ('Authorization') Authorization header key.
|
40
|
+
# @option options [String] :server ('EyHmac') service name prefixed to {#authorization}
|
41
|
+
# @see {Ey::Hmac::Adapter#sign!}
|
42
|
+
def self.sign!(request, key_id, key_secret, options={})
|
43
|
+
adapter = options[:adapter] || Ey::Hmac.default_adapter
|
44
|
+
|
45
|
+
raise ArgumentError, "Missing adapter and Ey::Hmac.default_adapter" unless adapter
|
46
|
+
|
47
|
+
adapter.new(request, options).sign!(key_id, key_secret)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @example
|
51
|
+
# Ey::Hmac.authenticated? do |key_id|
|
52
|
+
# @consumer = Consumer.where(auth_id: key_id).first
|
53
|
+
# @consumer && @consumer.auth_key
|
54
|
+
# end
|
55
|
+
# @param request [Hash] request environment
|
56
|
+
# @option options [Ey::Hmac::Adapter] :adapter ({#default_adapter}) adapter to verify request with
|
57
|
+
# @see {Ey::Hmac::Adapter#authenticated?}
|
58
|
+
def self.authenticated?(request, options={}, &block)
|
59
|
+
adapter = options[:adapter] || Ey::Hmac.default_adapter
|
60
|
+
|
61
|
+
raise ArgumentError, "Missing adapter and Ey::Hmac.default_adapter" unless adapter
|
62
|
+
|
63
|
+
adapter.new(request, options).authenticated?(&block)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @example
|
67
|
+
# Ey::Hmac.authenticate! do |key_id|
|
68
|
+
# @consumer = Consumer.where(auth_id: key_id).first
|
69
|
+
# @consumer && @consumer.auth_key
|
70
|
+
# end
|
71
|
+
# @param request [Hash] request environment
|
72
|
+
# @option options [Ey::Hmac::Adapter] :adapter ({#default_adapter}) adapter to verify request with
|
73
|
+
# @see {Ey::Hmac::Adapter#authenticate!}
|
74
|
+
def self.authenticate!(request, options={}, &block)
|
75
|
+
adapter = options[:adapter] || Ey::Hmac.default_adapter
|
76
|
+
|
77
|
+
raise ArgumentError, "Missing adapter and Ey::Hmac.default_adapter" unless adapter
|
78
|
+
|
79
|
+
adapter.new(request, options).authenticate!(&block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "faraday" do
|
4
|
+
before(:all) { Bundler.require(:faraday) }
|
5
|
+
let!(:key_id) { (0...8).map{ 65.+(rand(26)).chr}.join }
|
6
|
+
let!(:key_secret) { (0...16).map{ 65.+(rand(26)).chr}.join }
|
7
|
+
|
8
|
+
describe "adapter" do
|
9
|
+
let!(:adapter) { Ey::Hmac::Adapter::Faraday }
|
10
|
+
let!(:request) do
|
11
|
+
Faraday::Request.new.tap do |r|
|
12
|
+
r.method = :get
|
13
|
+
r.path = "/auth"
|
14
|
+
r.body = "{1: 2}"
|
15
|
+
r.headers = {"Content-Type" => "application/xml"}
|
16
|
+
end.to_env(Faraday::Connection.new("http://localhost"))
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should sign and read request" do
|
20
|
+
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
21
|
+
|
22
|
+
request[:request_headers]['Authorization'].should start_with("EyHmac")
|
23
|
+
request[:request_headers]['Content-Digest'].should == Digest::MD5.hexdigest(request[:body])
|
24
|
+
Time.parse(request[:request_headers]['Date']).should_not be_nil
|
25
|
+
|
26
|
+
yielded = false
|
27
|
+
|
28
|
+
Ey::Hmac.authenticated?(request, adapter: adapter) do |key_id|
|
29
|
+
key_id.should == key_id
|
30
|
+
yielded = true
|
31
|
+
key_secret
|
32
|
+
end.should be_true
|
33
|
+
|
34
|
+
yielded.should be_true
|
35
|
+
end
|
36
|
+
|
37
|
+
include_examples "authentication"
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "middleware" do
|
41
|
+
it "should sign request" do
|
42
|
+
require 'ey-hmac/faraday'
|
43
|
+
Bundler.require(:rack)
|
44
|
+
|
45
|
+
app = lambda do |env|
|
46
|
+
authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
47
|
+
(auth_id == key_id) && key_secret
|
48
|
+
end
|
49
|
+
[(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
|
50
|
+
end
|
51
|
+
|
52
|
+
request_env = nil
|
53
|
+
connection = Faraday.new do |c|
|
54
|
+
c.request :hmac, key_id, key_secret
|
55
|
+
c.adapter(:rack, app)
|
56
|
+
end
|
57
|
+
|
58
|
+
connection.get("/resources").status.should == 200
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/rack_spec.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "rack" do
|
4
|
+
before(:all) { Bundler.require(:rack) }
|
5
|
+
|
6
|
+
let!(:key_id) { (0...8).map{ 65.+(rand(26)).chr}.join }
|
7
|
+
let!(:key_secret) { (0...16).map{ 65.+(rand(26)).chr}.join }
|
8
|
+
|
9
|
+
describe "adapter" do
|
10
|
+
let(:adapter) { Ey::Hmac::Adapter::Rack }
|
11
|
+
let(:request) {
|
12
|
+
Rack::Request.new({
|
13
|
+
"rack.input" => StringIO.new("{1: 2}"),
|
14
|
+
"HTTP_CONTENT_TYPE" => "application/json",
|
15
|
+
})
|
16
|
+
}
|
17
|
+
|
18
|
+
it "should sign and read request" do
|
19
|
+
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
20
|
+
|
21
|
+
request.env['HTTP_AUTHORIZATION'].should start_with("EyHmac")
|
22
|
+
request.env['HTTP_CONTENT_DIGEST'].should == Digest::MD5.hexdigest(request.body.tap(&:rewind).read)
|
23
|
+
Time.parse(request.env['HTTP_DATE']).should_not be_nil
|
24
|
+
|
25
|
+
yielded = false
|
26
|
+
|
27
|
+
Ey::Hmac.authenticated?(request, adapter: adapter) do |key_id|
|
28
|
+
key_id.should == key_id
|
29
|
+
yielded = true
|
30
|
+
key_secret
|
31
|
+
end.should be_true
|
32
|
+
|
33
|
+
yielded.should be_true
|
34
|
+
end
|
35
|
+
|
36
|
+
include_examples "authentication"
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "middleware" do
|
40
|
+
it "should sign and read request" do
|
41
|
+
app = lambda do |env|
|
42
|
+
authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
43
|
+
(auth_id == key_id) && key_secret
|
44
|
+
end
|
45
|
+
[(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
|
46
|
+
end
|
47
|
+
|
48
|
+
_key_id, _key_secret = key_id, key_secret
|
49
|
+
client = Rack::Client.new do
|
50
|
+
use Ey::Hmac::Rack, _key_id, _key_secret
|
51
|
+
run app
|
52
|
+
end
|
53
|
+
|
54
|
+
client.get("/resource").status.should == 200
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
shared_examples_for "authentication" do
|
2
|
+
describe "#authenticated?" do
|
3
|
+
it "should not authenticate invalid secret" do
|
4
|
+
Ey::Hmac.sign!(request, key_id, "#{key_secret}bad", adapter: adapter)
|
5
|
+
|
6
|
+
Ey::Hmac.authenticated?(request, adapter: adapter) do |auth_id|
|
7
|
+
(auth_id == key_id) && key_secret
|
8
|
+
end.should be_false
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should not authenticate invalid id" do
|
12
|
+
Ey::Hmac.sign!(request, "what#{key_id}", key_secret, adapter: adapter)
|
13
|
+
|
14
|
+
Ey::Hmac.authenticated?(request, adapter: adapter) do |auth_id|
|
15
|
+
(auth_id == key_id) && key_secret
|
16
|
+
end.should be_false
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not authenticate missing header" do
|
20
|
+
Ey::Hmac.authenticated?(request, adapter: adapter) do |auth_id|
|
21
|
+
(auth_id == key_id) && key_secret
|
22
|
+
end.should be_false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#authenticate!" do
|
27
|
+
it "should not authenticate invalid secret" do
|
28
|
+
Ey::Hmac.sign!(request, key_id, "#{key_secret}bad", adapter: adapter)
|
29
|
+
|
30
|
+
lambda {
|
31
|
+
Ey::Hmac.authenticate!(request, adapter: adapter) do |auth_id|
|
32
|
+
(auth_id == key_id) && key_secret
|
33
|
+
end
|
34
|
+
}.should raise_exception(Ey::Hmac::SignatureMismatch)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not authenticate invalid id" do
|
38
|
+
Ey::Hmac.sign!(request, "what#{key_id}", key_secret, adapter: adapter)
|
39
|
+
|
40
|
+
lambda {
|
41
|
+
Ey::Hmac.authenticate!(request, adapter: adapter) do |auth_id|
|
42
|
+
(auth_id == key_id) && key_secret
|
43
|
+
end
|
44
|
+
}.should raise_exception(Ey::Hmac::MissingSecret)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not authenticate missing header" do
|
48
|
+
lambda {
|
49
|
+
Ey::Hmac.authenticate!(request, adapter: adapter) do |auth_id|
|
50
|
+
(auth_id == key_id) && key_secret
|
51
|
+
end.should be_false
|
52
|
+
}.should raise_exception(Ey::Hmac::MissingAuthorization)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ey-hmac
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Lane & Jason Hansen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-05 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Lightweight HMAC signing libraries and middleware for Farday and Rack
|
15
|
+
email:
|
16
|
+
- jlane@engineyard.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- Guardfile
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- ey-hmac.gemspec
|
28
|
+
- lib/ey-hmac.rb
|
29
|
+
- lib/ey-hmac/adapter.rb
|
30
|
+
- lib/ey-hmac/adapter/faraday.rb
|
31
|
+
- lib/ey-hmac/adapter/rack.rb
|
32
|
+
- lib/ey-hmac/faraday.rb
|
33
|
+
- lib/ey-hmac/rack.rb
|
34
|
+
- lib/ey-hmac/version.rb
|
35
|
+
- spec/faraday_spec.rb
|
36
|
+
- spec/rack_spec.rb
|
37
|
+
- spec/shared/authenticated.rb
|
38
|
+
- spec/spec_helper.rb
|
39
|
+
homepage: ''
|
40
|
+
licenses: []
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
none: false
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
none: false
|
57
|
+
requirements: []
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.8.24
|
60
|
+
signing_key:
|
61
|
+
specification_version: 3
|
62
|
+
summary: Lightweight HMAC signing libraries and middleware for Farday and Rack
|
63
|
+
test_files:
|
64
|
+
- spec/faraday_spec.rb
|
65
|
+
- spec/rack_spec.rb
|
66
|
+
- spec/shared/authenticated.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
has_rdoc:
|