ey-hmac 2.0.2 → 2.3.1
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.
- checksums.yaml +5 -5
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/ruby.yml +37 -0
- data/.rubocop.yml +29 -0
- data/.rubocop_todo.yml +82 -0
- data/CHANGELOG.md +68 -0
- data/Gemfile +7 -2
- data/Rakefile +3 -1
- data/ey-hmac.gemspec +18 -14
- data/lib/ey-hmac/adapter/faraday.rb +40 -22
- data/lib/ey-hmac/adapter/rack.rb +22 -19
- data/lib/ey-hmac/adapter.rb +57 -23
- data/lib/ey-hmac/faraday.rb +8 -5
- data/lib/ey-hmac/rack.rb +4 -1
- data/lib/ey-hmac/version.rb +4 -2
- data/lib/ey-hmac.rb +94 -94
- data/spec/faraday_spec.rb +70 -68
- data/spec/rack_spec.rb +47 -42
- data/spec/shared/authenticated.rb +43 -21
- data/spec/spec_helper.rb +6 -3
- metadata +24 -21
- data/.travis.yml +0 -7
data/lib/ey-hmac/faraday.rb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'ey-hmac'
|
2
4
|
require 'faraday'
|
3
5
|
|
4
|
-
class Ey::Hmac::Faraday < Faraday::
|
5
|
-
dependency(
|
6
|
+
class Ey::Hmac::Faraday < Faraday::Middleware
|
7
|
+
dependency('ey-hmac') if respond_to?(:dependency)
|
6
8
|
|
7
9
|
attr_reader :key_id, :key_secret, :options
|
8
10
|
|
9
11
|
def initialize(app, key_id, key_secret, options = {})
|
10
12
|
super(app)
|
11
|
-
@key_id
|
13
|
+
@key_id = key_id
|
14
|
+
@key_secret = key_secret
|
12
15
|
@options = options
|
13
16
|
end
|
14
17
|
|
15
18
|
def call(env)
|
16
|
-
Ey::Hmac.sign!(env, key_id, key_secret, {adapter: Ey::Hmac::Adapter::Faraday}.merge(options))
|
19
|
+
Ey::Hmac.sign!(env, key_id, key_secret, { adapter: Ey::Hmac::Adapter::Faraday }.merge(options))
|
17
20
|
@app.call(env)
|
18
21
|
end
|
19
22
|
end
|
20
23
|
|
21
|
-
Faraday::Middleware.register_middleware :
|
24
|
+
Faraday::Middleware.register_middleware hmac: Ey::Hmac::Faraday
|
data/lib/ey-hmac/rack.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Request middleware that performs HMAC request signing
|
2
4
|
class Ey::Hmac::Rack
|
3
5
|
attr_reader :key_id, :key_secret, :options
|
4
6
|
|
5
7
|
def initialize(app, key_id, key_secret, options = {})
|
6
8
|
@app = app
|
7
|
-
@key_id
|
9
|
+
@key_id = key_id
|
10
|
+
@key_secret = key_secret
|
8
11
|
@options = options
|
9
12
|
end
|
10
13
|
|
data/lib/ey-hmac/version.rb
CHANGED
data/lib/ey-hmac.rb
CHANGED
@@ -1,102 +1,102 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ey-hmac/version'
|
2
4
|
|
3
5
|
require 'base64'
|
4
6
|
require 'digest/md5'
|
5
7
|
require 'openssl'
|
6
8
|
require 'time'
|
7
9
|
|
8
|
-
module Ey
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
adapter.new(request, options).authenticate!(&block)
|
100
|
-
end
|
10
|
+
module Ey::Hmac
|
11
|
+
Error = Class.new(StandardError)
|
12
|
+
|
13
|
+
MissingSecret = Class.new(Error)
|
14
|
+
MissingAuthorization = Class.new(Error)
|
15
|
+
SignatureMismatch = Class.new(Error)
|
16
|
+
ExpiredHmac = Class.new(Error)
|
17
|
+
|
18
|
+
autoload :Adapter, 'ey-hmac/adapter'
|
19
|
+
autoload :Faraday, 'ey-hmac/faraday'
|
20
|
+
autoload :Rack, 'ey-hmac/rack'
|
21
|
+
|
22
|
+
def self.default_adapter=(default_adapter)
|
23
|
+
@default_adapter = default_adapter
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.default_adapter
|
27
|
+
@default_adapter ||= if defined?(::Rack) || defined?(::Rails)
|
28
|
+
Ey::Hmac::Adapter::Rack
|
29
|
+
elsif defined?(::Faraday)
|
30
|
+
Ey::Hmac::Adapter::Faraday
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Signs request by calculating signature and adding it to the specified header
|
35
|
+
# @example
|
36
|
+
# Ey::Hmac.sign!(env, @key_id, @key_secret)
|
37
|
+
#
|
38
|
+
# @see Ey::Hmac::Adapter#sign!
|
39
|
+
#
|
40
|
+
# @param request [Hash] request environment
|
41
|
+
# @option options [Ey::Hmac::Adapter] :adapter (#{default_adapter}) adapter to sign request with
|
42
|
+
# @option options [Integer] :version (nil) signature version
|
43
|
+
# @option options [String] :authorization_header ('Authorization') Authorization header key.
|
44
|
+
# @option options [String] :service ('EyHmac') service name prefixed to {Ey::Hmac::Adapter#authorization}
|
45
|
+
#
|
46
|
+
# @return [String] authorization signature
|
47
|
+
def self.sign!(request, key_id, key_secret, options = {})
|
48
|
+
adapter = options[:adapter] || Ey::Hmac.default_adapter
|
49
|
+
|
50
|
+
raise ArgumentError, 'Missing adapter and Ey::Hmac.default_adapter' unless adapter
|
51
|
+
|
52
|
+
adapter.new(request, options).sign!(key_id, key_secret)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @example
|
56
|
+
# Ey::Hmac.authenticated? do |key_id|
|
57
|
+
# @consumer = Consumer.where(auth_id: key_id).first
|
58
|
+
# @consumer && @consumer.auth_key
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# @see Ey::Hmac::Adapter#authenticated?
|
62
|
+
# @see Ey::Hmac#authenticate!
|
63
|
+
#
|
64
|
+
# @param request [Hash] request environment
|
65
|
+
# @option options [Ey::Hmac::Adapter] :adapter ({#default_adapter}) adapter to verify request with
|
66
|
+
# @yieldparam key_id [String] public HMAC key
|
67
|
+
#
|
68
|
+
# @return [Boolean] success of authentication
|
69
|
+
def self.authenticated?(request, options = {}, &block)
|
70
|
+
adapter = options[:adapter] || Ey::Hmac.default_adapter
|
71
|
+
|
72
|
+
raise ArgumentError, 'Missing adapter and Ey::Hmac.default_adapter' unless adapter
|
73
|
+
|
74
|
+
adapter.new(request, options).authenticated?(&block)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Check {Ey::Hmac::Adapter#authorization_signature} against calculated {Ey::Hmac::Adapter#signature}
|
78
|
+
# @example
|
79
|
+
# Ey::Hmac.authenticate! do |key_id|
|
80
|
+
# @consumer = Consumer.where(auth_id: key_id).first
|
81
|
+
# @consumer && @consumer.auth_key
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# @see Ey::Hmac::Adapter#authenticate!
|
85
|
+
#
|
86
|
+
# @param request [Hash] request environment
|
87
|
+
# @yieldparam key_id [String] public HMAC key
|
88
|
+
# @option options [Ey::Hmac::Adapter] :adapter ({#default_adapter}) adapter to verify request with
|
89
|
+
#
|
90
|
+
# @raise [SignatureMismatch] if the value of {Ey::Hmac::Adapter#authorization_signature} does not
|
91
|
+
# match {Ey::Hmac::Adapter#signature}
|
92
|
+
# @raise [MissingSecret] if the block does not return a private key matching +key_id+
|
93
|
+
# @raise [MissingAuthorization] if the value of {Ey::Hmac::Adapter#authorization_signature} is nil
|
94
|
+
# @return [TrueClass] if authentication was successful
|
95
|
+
def self.authenticate!(request, options = {}, &block)
|
96
|
+
adapter = options[:adapter] || Ey::Hmac.default_adapter
|
97
|
+
|
98
|
+
raise ArgumentError, 'Missing adapter and Ey::Hmac.default_adapter' unless adapter
|
99
|
+
|
100
|
+
adapter.new(request, options).authenticate!(&block)
|
101
101
|
end
|
102
102
|
end
|
data/spec/faraday_spec.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
|
-
describe
|
5
|
+
describe 'faraday' do
|
4
6
|
before(:all) { Bundler.require(:faraday) }
|
5
7
|
|
6
8
|
let!(:key_id) { SecureRandom.hex(8) }
|
7
9
|
let!(:key_secret) { SecureRandom.hex(16) }
|
8
10
|
|
9
|
-
describe
|
11
|
+
describe 'adapter' do
|
10
12
|
let!(:adapter) { Ey::Hmac::Adapter::Faraday }
|
11
13
|
|
12
|
-
it
|
14
|
+
it 'signs a multipart post' do
|
13
15
|
app = lambda do |env|
|
14
16
|
authenticated = Ey::Hmac.authenticate!(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
15
17
|
(auth_id == key_id) && key_secret
|
16
18
|
end
|
17
|
-
[(authenticated ? 200 : 401), {
|
19
|
+
[(authenticated ? 200 : 401), { 'Content-Type' => 'text/plain' }, []]
|
18
20
|
end
|
19
21
|
|
20
22
|
require 'ey-hmac/faraday'
|
@@ -28,117 +30,115 @@ describe "faraday" do
|
|
28
30
|
c.adapter(:rack, app)
|
29
31
|
end
|
30
32
|
|
31
|
-
tempfile = Tempfile.new(
|
33
|
+
tempfile = Tempfile.new('hmac')
|
32
34
|
tempfile.write SecureRandom.hex(512)
|
33
35
|
tempfile.close
|
34
36
|
|
35
37
|
expect(
|
36
|
-
connection.post { |req| req.body = {
|
38
|
+
connection.post { |req| req.body = { 'output' => Faraday::UploadIO.new(tempfile.path, 'text/plain') } }.status
|
37
39
|
).to eq(200)
|
38
40
|
end
|
39
41
|
|
40
|
-
it
|
41
|
-
request = Faraday::Request.
|
42
|
-
r.
|
43
|
-
r.
|
44
|
-
r.
|
45
|
-
|
46
|
-
|
47
|
-
Faraday::Connection.new("http://localhost")
|
42
|
+
it 'signs and reads a request' do
|
43
|
+
request = Faraday::Request.create(:get) do |r|
|
44
|
+
r.path = '/auth'
|
45
|
+
r.body = '{1: 2}'
|
46
|
+
r.headers = { 'Content-Type' => 'application/xml' }
|
47
|
+
end.to_env(
|
48
|
+
Faraday::Connection.new('http://localhost')
|
48
49
|
)
|
49
50
|
|
50
51
|
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
51
52
|
|
52
|
-
expect(request[:request_headers]['Authorization']).to
|
53
|
-
expect(request[:request_headers]['Content-Digest']).to
|
53
|
+
expect(request[:request_headers]['Authorization']).to start_with('EyHmac')
|
54
|
+
expect(request[:request_headers]['Content-Digest']).to eq(Digest::MD5.hexdigest(request[:body]))
|
54
55
|
expect(Time.parse(request[:request_headers]['Date'])).not_to be_nil
|
55
56
|
|
56
57
|
yielded = false
|
57
58
|
|
58
|
-
expect(Ey::Hmac.
|
59
|
+
expect(Ey::Hmac).to be_authenticated(request, adapter: adapter) do |key_id|
|
59
60
|
expect(key_id).to eq(key_id)
|
60
61
|
yielded = true
|
61
62
|
key_secret
|
62
|
-
end
|
63
|
+
end
|
63
64
|
|
64
65
|
expect(yielded).to be_truthy
|
65
66
|
end
|
66
67
|
|
67
|
-
it
|
68
|
-
request = Faraday::Request.
|
69
|
-
r.
|
70
|
-
r.path = "/auth"
|
68
|
+
it 'does not set Content-Digest if body is nil' do
|
69
|
+
request = Faraday::Request.create(:get) do |r|
|
70
|
+
r.path = '/auth'
|
71
71
|
r.body = nil
|
72
|
-
r.headers = {
|
73
|
-
|
74
|
-
Faraday::Connection.new(
|
72
|
+
r.headers = { 'Content-Type' => 'application/xml' }
|
73
|
+
end.to_env(
|
74
|
+
Faraday::Connection.new('http://localhost')
|
75
75
|
)
|
76
76
|
|
77
77
|
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
78
78
|
|
79
|
-
expect(request[:request_headers]['Authorization']).to start_with(
|
79
|
+
expect(request[:request_headers]['Authorization']).to start_with('EyHmac')
|
80
80
|
expect(request[:request_headers]).not_to have_key('Content-Digest')
|
81
81
|
expect(Time.parse(request[:request_headers]['Date'])).not_to be_nil
|
82
82
|
|
83
83
|
yielded = false
|
84
84
|
|
85
|
-
expect(Ey::Hmac.
|
85
|
+
expect(Ey::Hmac).to be_authenticated(request, adapter: adapter) do |key_id|
|
86
86
|
expect(key_id).to eq(key_id)
|
87
87
|
yielded = true
|
88
88
|
key_secret
|
89
|
-
end
|
89
|
+
end
|
90
90
|
|
91
91
|
expect(yielded).to be_truthy
|
92
92
|
end
|
93
93
|
|
94
|
-
it
|
95
|
-
request = Faraday::Request.
|
96
|
-
r.
|
97
|
-
r.
|
98
|
-
r.
|
99
|
-
|
100
|
-
end.to_env(Faraday::Connection.new("http://localhost"))
|
94
|
+
it 'does not set Content-Digest if body is empty' do
|
95
|
+
request = Faraday::Request.create(:get) do |r|
|
96
|
+
r.path = '/auth'
|
97
|
+
r.body = ''
|
98
|
+
r.headers = { 'Content-Type' => 'application/xml' }
|
99
|
+
end.to_env(Faraday::Connection.new('http://localhost'))
|
101
100
|
|
102
101
|
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
103
102
|
|
104
|
-
expect(request[:request_headers]['Authorization']).to start_with(
|
103
|
+
expect(request[:request_headers]['Authorization']).to start_with('EyHmac')
|
105
104
|
expect(request[:request_headers]).not_to have_key('Content-Digest')
|
106
|
-
#expect(Time.parse(request[:request_headers]['Date'])).not_to be_nil
|
105
|
+
# expect(Time.parse(request[:request_headers]['Date'])).not_to be_nil
|
107
106
|
|
108
107
|
yielded = false
|
109
108
|
|
110
|
-
expect(Ey::Hmac.
|
109
|
+
expect(Ey::Hmac).to be_authenticated(request, adapter: adapter) do |key_id|
|
111
110
|
expect(key_id).to eq(key_id)
|
112
111
|
yielded = true
|
113
112
|
key_secret
|
114
|
-
end
|
113
|
+
end
|
115
114
|
|
116
115
|
expect(yielded).to be_truthy
|
117
116
|
end
|
118
117
|
|
119
|
-
context
|
118
|
+
context 'with a request' do
|
120
119
|
let!(:request) do
|
121
|
-
Faraday::Request.
|
122
|
-
r.
|
123
|
-
r.
|
124
|
-
r.
|
125
|
-
|
126
|
-
end.to_env(Faraday::Connection.new("http://localhost"))
|
120
|
+
Faraday::Request.create(:get) do |r|
|
121
|
+
r.path = '/auth'
|
122
|
+
r.body = '{1: 2}'
|
123
|
+
r.headers = { 'Content-Type' => 'application/xml' }
|
124
|
+
end.to_env(Faraday::Connection.new('http://localhost'))
|
127
125
|
end
|
128
|
-
|
126
|
+
|
127
|
+
include_examples 'authentication'
|
129
128
|
end
|
130
129
|
end
|
131
130
|
|
132
|
-
describe
|
133
|
-
it
|
131
|
+
describe 'middleware' do
|
132
|
+
it 'accepts a SHA1 signature' do
|
134
133
|
require 'ey-hmac/faraday'
|
135
134
|
Bundler.require(:rack)
|
136
135
|
|
137
136
|
app = lambda do |env|
|
138
|
-
authenticated = Ey::Hmac.authenticated?(env, accept_digests: :sha1,
|
137
|
+
authenticated = Ey::Hmac.authenticated?(env, accept_digests: :sha1,
|
138
|
+
adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
139
139
|
(auth_id == key_id) && key_secret
|
140
140
|
end
|
141
|
-
[(authenticated ? 200 : 401), {
|
141
|
+
[(authenticated ? 200 : 401), { 'Content-Type' => 'text/plain' }, []]
|
142
142
|
end
|
143
143
|
|
144
144
|
connection = Faraday.new do |c|
|
@@ -146,10 +146,10 @@ describe "faraday" do
|
|
146
146
|
c.adapter(:rack, app)
|
147
147
|
end
|
148
148
|
|
149
|
-
expect(connection.get(
|
149
|
+
expect(connection.get('/resources').status).to eq(200)
|
150
150
|
end
|
151
151
|
|
152
|
-
it
|
152
|
+
it 'accepts a SHA256 signature' do # default
|
153
153
|
require 'ey-hmac/faraday'
|
154
154
|
Bundler.require(:rack)
|
155
155
|
|
@@ -157,7 +157,7 @@ describe "faraday" do
|
|
157
157
|
authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
158
158
|
(auth_id == key_id) && key_secret
|
159
159
|
end
|
160
|
-
[(authenticated ? 200 : 401), {
|
160
|
+
[(authenticated ? 200 : 401), { 'Content-Type' => 'text/plain' }, []]
|
161
161
|
end
|
162
162
|
|
163
163
|
connection = Faraday.new do |c|
|
@@ -165,18 +165,19 @@ describe "faraday" do
|
|
165
165
|
c.adapter(:rack, app)
|
166
166
|
end
|
167
167
|
|
168
|
-
expect(connection.get(
|
168
|
+
expect(connection.get('/resources').status).to eq(200)
|
169
169
|
end
|
170
170
|
|
171
|
-
it
|
171
|
+
it 'accepts multiple digest signatures' do # default
|
172
172
|
require 'ey-hmac/faraday'
|
173
173
|
Bundler.require(:rack)
|
174
174
|
|
175
175
|
app = lambda do |env|
|
176
|
-
authenticated = Ey::Hmac.authenticated?(env, accept_digests: [
|
176
|
+
authenticated = Ey::Hmac.authenticated?(env, accept_digests: %i[sha1 sha256],
|
177
|
+
adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
177
178
|
(auth_id == key_id) && key_secret
|
178
179
|
end
|
179
|
-
[(authenticated ? 200 : 401), {
|
180
|
+
[(authenticated ? 200 : 401), { 'Content-Type' => 'text/plain' }, []]
|
180
181
|
end
|
181
182
|
|
182
183
|
connection = Faraday.new do |c|
|
@@ -184,23 +185,24 @@ describe "faraday" do
|
|
184
185
|
c.adapter(:rack, app)
|
185
186
|
end
|
186
187
|
|
187
|
-
expect(connection.get(
|
188
|
+
expect(connection.get('/resources').status).to eq(200)
|
188
189
|
end
|
189
190
|
|
190
|
-
it
|
191
|
+
it 'signs empty request' do
|
191
192
|
require 'ey-hmac/faraday'
|
192
193
|
Bundler.require(:rack)
|
193
194
|
|
194
|
-
|
195
|
+
outer_key_id = key_id
|
196
|
+
outer_key_secret = key_secret
|
195
197
|
app = Rack::Builder.new do
|
196
198
|
use Rack::Config do |env|
|
197
|
-
env[
|
199
|
+
env['CONTENT_TYPE'] ||= 'text/html'
|
198
200
|
end
|
199
|
-
run(lambda {|env|
|
201
|
+
run(lambda { |env|
|
200
202
|
authenticated = Ey::Hmac.authenticate!(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
201
|
-
(auth_id ==
|
203
|
+
(auth_id == outer_key_id) && outer_key_secret
|
202
204
|
end
|
203
|
-
[(authenticated ? 200 : 401), {
|
205
|
+
[(authenticated ? 200 : 401), { 'Content-Type' => 'text/plain' }, []]
|
204
206
|
})
|
205
207
|
end
|
206
208
|
|
@@ -210,10 +212,10 @@ describe "faraday" do
|
|
210
212
|
end
|
211
213
|
|
212
214
|
expect(connection.get do |req|
|
213
|
-
req.path =
|
215
|
+
req.path = '/resource'
|
214
216
|
req.body = nil
|
215
|
-
req.params = {
|
216
|
-
req.headers = {
|
217
|
+
req.params = { 'a' => '1' }
|
218
|
+
req.headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
217
219
|
end.status).to eq(200)
|
218
220
|
end
|
219
221
|
end
|