ey-hmac 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/ey-hmac/adapter/faraday.rb +8 -4
- data/lib/ey-hmac/adapter/rack.rb +5 -1
- data/lib/ey-hmac/adapter.rb +5 -6
- data/lib/ey-hmac/version.rb +1 -1
- data/lib/ey-hmac.rb +23 -4
- data/spec/faraday_spec.rb +96 -6
- data/spec/rack_spec.rb +59 -7
- metadata +2 -2
@@ -4,14 +4,18 @@ class Ey::Hmac::Adapter::Faraday < Ey::Hmac::Adapter
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def content_type
|
7
|
-
%w[CONTENT-TYPE CONTENT_TYPE Content-Type Content_Type].inject(nil){|r, h| r || request[h]}
|
7
|
+
%w[CONTENT-TYPE CONTENT_TYPE Content-Type Content_Type].inject(nil){|r, h| r || request[:request_headers][h]}
|
8
8
|
end
|
9
9
|
|
10
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
|
-
|
11
|
+
if existing = %w[CONTENT-DIGEST CONTENT_DIGEST Content-Digest Content_Digest].inject(nil){|r,h| r || request[:request_headers][h]}
|
12
|
+
existing
|
13
|
+
elsif digestable = body && Digest::MD5.hexdigest(body)
|
14
|
+
request[:request_headers]['Content-Digest'] = digestable
|
15
|
+
else nil
|
16
|
+
end
|
13
17
|
end
|
14
|
-
|
18
|
+
|
15
19
|
def body
|
16
20
|
if request[:body] && request[:body].to_s != ""
|
17
21
|
request[:body]
|
data/lib/ey-hmac/adapter/rack.rb
CHANGED
@@ -15,7 +15,11 @@ class Ey::Hmac::Adapter::Rack < Ey::Hmac::Adapter
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def content_digest
|
18
|
-
request.env['HTTP_CONTENT_DIGEST']
|
18
|
+
if existing = request.env['HTTP_CONTENT_DIGEST']
|
19
|
+
existing
|
20
|
+
elsif digest = body && Digest::MD5.hexdigest(body)
|
21
|
+
request.env['HTTP_CONTENT_DIGEST'] = digest
|
22
|
+
end
|
19
23
|
end
|
20
24
|
|
21
25
|
def body
|
data/lib/ey-hmac/adapter.rb
CHANGED
@@ -76,7 +76,7 @@ class Ey::Hmac::Adapter
|
|
76
76
|
|
77
77
|
# @abstract
|
78
78
|
# @return [String] value of the Date header in {#request}.
|
79
|
-
# @see
|
79
|
+
# @see Time#http_date
|
80
80
|
def date
|
81
81
|
raise NotImplementedError
|
82
82
|
end
|
@@ -89,6 +89,8 @@ class Ey::Hmac::Adapter
|
|
89
89
|
|
90
90
|
# @abstract
|
91
91
|
# Add {#signature} header to request. Typically this is 'Authorization' or 'WWW-Authorization'
|
92
|
+
# @return [String] calculated {#authorization}
|
93
|
+
# @see Ey::Hmac#sign!
|
92
94
|
def sign!(key_id, key_secret)
|
93
95
|
raise NotImplementedError
|
94
96
|
end
|
@@ -96,17 +98,14 @@ class Ey::Hmac::Adapter
|
|
96
98
|
# Check {#authorization_signature} against calculated {#signature}
|
97
99
|
# @yieldparam key_id [String] public HMAC key
|
98
100
|
# @return [Boolean] true if block yields matching private key and signature matches, else false
|
99
|
-
# @see
|
101
|
+
# @see #authenticated!
|
100
102
|
def authenticated?(&block)
|
101
103
|
authenticated!(&block)
|
102
104
|
rescue Ey::Hmac::Error
|
103
105
|
false
|
104
106
|
end
|
105
107
|
|
106
|
-
#
|
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
|
108
|
+
# @see Ey::Hmac#authenticate!
|
110
109
|
def authenticated!(&block)
|
111
110
|
if authorization_match = AUTHORIZATION_REGEXP.match(authorization_signature)
|
112
111
|
key_id = authorization_match[1]
|
data/lib/ey-hmac/version.rb
CHANGED
data/lib/ey-hmac.rb
CHANGED
@@ -30,15 +30,19 @@ module Ey
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
# Signs request by calculating signature and adding it to the specified header
|
33
34
|
# @example
|
34
35
|
# Ey::Hmac.sign!(env, @key_id, @key_secret)
|
35
36
|
#
|
37
|
+
# @see Ey::Hmac::Adapter#sign!
|
38
|
+
#
|
36
39
|
# @param request [Hash] request environment
|
37
40
|
# @option options [Ey::Hmac::Adapter] :adapter (#{default_adapter}) adapter to sign request with
|
38
41
|
# @option options [Integer] :version (nil) signature version
|
39
42
|
# @option options [String] :authorization_header ('Authorization') Authorization header key.
|
40
|
-
# @option options [String] :
|
41
|
-
#
|
43
|
+
# @option options [String] :service ('EyHmac') service name prefixed to {Ey::Hmac::Adapter#authorization}
|
44
|
+
#
|
45
|
+
# @return [String] authorization signature
|
42
46
|
def self.sign!(request, key_id, key_secret, options={})
|
43
47
|
adapter = options[:adapter] || Ey::Hmac.default_adapter
|
44
48
|
|
@@ -52,9 +56,15 @@ module Ey
|
|
52
56
|
# @consumer = Consumer.where(auth_id: key_id).first
|
53
57
|
# @consumer && @consumer.auth_key
|
54
58
|
# end
|
59
|
+
#
|
60
|
+
# @see Ey::Hmac::Adapter#authenticated?
|
61
|
+
# @see Ey::Hmac#authenticate!
|
62
|
+
#
|
55
63
|
# @param request [Hash] request environment
|
56
64
|
# @option options [Ey::Hmac::Adapter] :adapter ({#default_adapter}) adapter to verify request with
|
57
|
-
# @
|
65
|
+
# @yieldparam key_id [String] public HMAC key
|
66
|
+
#
|
67
|
+
# @return [Boolean] success of authentication
|
58
68
|
def self.authenticated?(request, options={}, &block)
|
59
69
|
adapter = options[:adapter] || Ey::Hmac.default_adapter
|
60
70
|
|
@@ -63,14 +73,23 @@ module Ey
|
|
63
73
|
adapter.new(request, options).authenticated?(&block)
|
64
74
|
end
|
65
75
|
|
76
|
+
# Check {Ey::Hmac::Adapter#authorization_signature} against calculated {Ey::Hmac::Adapter#signature}
|
66
77
|
# @example
|
67
78
|
# Ey::Hmac.authenticate! do |key_id|
|
68
79
|
# @consumer = Consumer.where(auth_id: key_id).first
|
69
80
|
# @consumer && @consumer.auth_key
|
70
81
|
# end
|
82
|
+
#
|
83
|
+
# @see Ey::Hmac::Adapter#authenticate!
|
84
|
+
#
|
71
85
|
# @param request [Hash] request environment
|
86
|
+
# @yieldparam key_id [String] public HMAC key
|
72
87
|
# @option options [Ey::Hmac::Adapter] :adapter ({#default_adapter}) adapter to verify request with
|
73
|
-
#
|
88
|
+
#
|
89
|
+
# @raise [SignatureMismatch] if the value of {Ey::Hmac::Adapter#authorization_signature} does not match {Ey::Hmac::Adapter#signature}
|
90
|
+
# @raise [MissingSecret] if the block does not return a private key matching +key_id+
|
91
|
+
# @raise [MissingAuthorization] if the value of {Ey::Hmac::Adapter#authorization_signature} is nil
|
92
|
+
# @return [TrueClass] if authentication was successful
|
74
93
|
def self.authenticate!(request, options={}, &block)
|
75
94
|
adapter = options[:adapter] || Ey::Hmac.default_adapter
|
76
95
|
|
data/spec/faraday_spec.rb
CHANGED
@@ -7,16 +7,15 @@ describe "faraday" do
|
|
7
7
|
|
8
8
|
describe "adapter" do
|
9
9
|
let!(:adapter) { Ey::Hmac::Adapter::Faraday }
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
it "should sign and read request" do
|
12
|
+
request = Faraday::Request.new.tap do |r|
|
12
13
|
r.method = :get
|
13
14
|
r.path = "/auth"
|
14
15
|
r.body = "{1: 2}"
|
15
16
|
r.headers = {"Content-Type" => "application/xml"}
|
16
|
-
|
17
|
-
end
|
17
|
+
end.to_env(Faraday::Connection.new("http://localhost"))
|
18
18
|
|
19
|
-
it "should sign and read request" do
|
20
19
|
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
21
20
|
|
22
21
|
request[:request_headers]['Authorization'].should start_with("EyHmac")
|
@@ -34,7 +33,67 @@ describe "faraday" do
|
|
34
33
|
yielded.should be_true
|
35
34
|
end
|
36
35
|
|
37
|
-
|
36
|
+
it "should not set Content-Digest if body is nil" do
|
37
|
+
request = Faraday::Request.new.tap do |r|
|
38
|
+
r.method = :get
|
39
|
+
r.path = "/auth"
|
40
|
+
r.body = nil
|
41
|
+
r.headers = {"Content-Type" => "application/xml"}
|
42
|
+
end.to_env(Faraday::Connection.new("http://localhost"))
|
43
|
+
|
44
|
+
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
45
|
+
|
46
|
+
request[:request_headers]['Authorization'].should start_with("EyHmac")
|
47
|
+
request[:request_headers].should_not have_key('Content-Digest')
|
48
|
+
Time.parse(request[:request_headers]['Date']).should_not be_nil
|
49
|
+
|
50
|
+
yielded = false
|
51
|
+
|
52
|
+
Ey::Hmac.authenticated?(request, adapter: adapter) do |key_id|
|
53
|
+
key_id.should == key_id
|
54
|
+
yielded = true
|
55
|
+
key_secret
|
56
|
+
end.should be_true
|
57
|
+
|
58
|
+
yielded.should be_true
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should not set Content-Digest if body is empty" do
|
62
|
+
request = Faraday::Request.new.tap do |r|
|
63
|
+
r.method = :get
|
64
|
+
r.path = "/auth"
|
65
|
+
r.body = ""
|
66
|
+
r.headers = {"Content-Type" => "application/xml"}
|
67
|
+
end.to_env(Faraday::Connection.new("http://localhost"))
|
68
|
+
|
69
|
+
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
70
|
+
|
71
|
+
request[:request_headers]['Authorization'].should start_with("EyHmac")
|
72
|
+
request[:request_headers].should_not have_key('Content-Digest')
|
73
|
+
Time.parse(request[:request_headers]['Date']).should_not be_nil
|
74
|
+
|
75
|
+
yielded = false
|
76
|
+
|
77
|
+
Ey::Hmac.authenticated?(request, adapter: adapter) do |key_id|
|
78
|
+
key_id.should == key_id
|
79
|
+
yielded = true
|
80
|
+
key_secret
|
81
|
+
end.should be_true
|
82
|
+
|
83
|
+
yielded.should be_true
|
84
|
+
end
|
85
|
+
|
86
|
+
context "with a request" do
|
87
|
+
let!(:request) do
|
88
|
+
Faraday::Request.new.tap do |r|
|
89
|
+
r.method = :get
|
90
|
+
r.path = "/auth"
|
91
|
+
r.body = "{1: 2}"
|
92
|
+
r.headers = {"Content-Type" => "application/xml"}
|
93
|
+
end.to_env(Faraday::Connection.new("http://localhost"))
|
94
|
+
end
|
95
|
+
include_examples "authentication"
|
96
|
+
end
|
38
97
|
end
|
39
98
|
|
40
99
|
describe "middleware" do
|
@@ -57,5 +116,36 @@ describe "faraday" do
|
|
57
116
|
|
58
117
|
connection.get("/resources").status.should == 200
|
59
118
|
end
|
119
|
+
|
120
|
+
it "should sign empty request" do
|
121
|
+
require 'ey-hmac/faraday'
|
122
|
+
Bundler.require(:rack)
|
123
|
+
|
124
|
+
_key_id, _key_secret = key_id, key_secret
|
125
|
+
app = Rack::Builder.new do
|
126
|
+
use Rack::Config do |env|
|
127
|
+
env["CONTENT_TYPE"] ||= "text/html"
|
128
|
+
end
|
129
|
+
run(lambda {|env|
|
130
|
+
authenticated = Ey::Hmac.authenticate!(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
131
|
+
(auth_id == _key_id) && _key_secret
|
132
|
+
end
|
133
|
+
[(authenticated ? 200 : 401), {"Content-Type" => "text/plain"}, []]
|
134
|
+
})
|
135
|
+
end
|
136
|
+
|
137
|
+
request_env = nil
|
138
|
+
connection = Faraday.new do |c|
|
139
|
+
c.request :hmac, key_id, key_secret
|
140
|
+
c.adapter(:rack, app)
|
141
|
+
end
|
142
|
+
|
143
|
+
connection.get do |req|
|
144
|
+
req.path = "/resource"
|
145
|
+
req.body = nil
|
146
|
+
req.params = {"a" => "1"}
|
147
|
+
req.headers = {"Content-Type" => "application/x-www-form-urlencoded"}
|
148
|
+
end.status.should == 200
|
149
|
+
end
|
60
150
|
end
|
61
151
|
end
|
data/spec/rack_spec.rb
CHANGED
@@ -8,14 +8,12 @@ describe "rack" do
|
|
8
8
|
|
9
9
|
describe "adapter" do
|
10
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
11
|
|
18
12
|
it "should sign and read request" do
|
13
|
+
request = Rack::Request.new(
|
14
|
+
"rack.input" => StringIO.new("{1: 2}"),
|
15
|
+
"HTTP_CONTENT_TYPE" => "application/json",
|
16
|
+
)
|
19
17
|
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
20
18
|
|
21
19
|
request.env['HTTP_AUTHORIZATION'].should start_with("EyHmac")
|
@@ -33,7 +31,61 @@ describe "rack" do
|
|
33
31
|
yielded.should be_true
|
34
32
|
end
|
35
33
|
|
36
|
-
|
34
|
+
it "should not set Content-Digest if body is nil" do
|
35
|
+
request = Rack::Request.new(
|
36
|
+
"HTTP_CONTENT_TYPE" => "application/json",
|
37
|
+
)
|
38
|
+
|
39
|
+
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
40
|
+
|
41
|
+
request.env['HTTP_AUTHORIZATION'].should start_with("EyHmac")
|
42
|
+
request.env.should_not have_key('HTTP_CONTENT_DIGEST')
|
43
|
+
Time.parse(request.env['HTTP_DATE']).should_not be_nil
|
44
|
+
|
45
|
+
yielded = false
|
46
|
+
|
47
|
+
Ey::Hmac.authenticated?(request, adapter: adapter) do |key_id|
|
48
|
+
key_id.should == key_id
|
49
|
+
yielded = true
|
50
|
+
key_secret
|
51
|
+
end.should be_true
|
52
|
+
|
53
|
+
yielded.should be_true
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not set Content-Digest if body is empty" do
|
57
|
+
request = Rack::Request.new(
|
58
|
+
"rack.input" => StringIO.new(""),
|
59
|
+
"HTTP_CONTENT_TYPE" => "application/json",
|
60
|
+
)
|
61
|
+
|
62
|
+
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
63
|
+
|
64
|
+
request.env['HTTP_AUTHORIZATION'].should start_with("EyHmac")
|
65
|
+
request.env.should_not have_key('HTTP_CONTENT_DIGEST')
|
66
|
+
Time.parse(request.env['HTTP_DATE']).should_not be_nil
|
67
|
+
|
68
|
+
yielded = false
|
69
|
+
|
70
|
+
Ey::Hmac.authenticated?(request, adapter: adapter) do |key_id|
|
71
|
+
key_id.should == key_id
|
72
|
+
yielded = true
|
73
|
+
key_secret
|
74
|
+
end.should be_true
|
75
|
+
|
76
|
+
yielded.should be_true
|
77
|
+
end
|
78
|
+
|
79
|
+
context "with a request" do
|
80
|
+
let(:request) {
|
81
|
+
Rack::Request.new(
|
82
|
+
"rack.input" => StringIO.new("{1: 2}"),
|
83
|
+
"HTTP_CONTENT_TYPE" => "application/json",
|
84
|
+
)
|
85
|
+
}
|
86
|
+
|
87
|
+
include_examples "authentication"
|
88
|
+
end
|
37
89
|
end
|
38
90
|
|
39
91
|
describe "middleware" do
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ey-hmac
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.2
|
5
4
|
prerelease:
|
5
|
+
version: 0.0.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Josh Lane & Jason Hansen
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-06 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Lightweight HMAC signing libraries and middleware for Farday and Rack
|
15
15
|
email:
|