ey-hmac 2.3.0 → 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 +4 -4
- data/.github/workflows/codeql-analysis.yml +70 -0
- data/.github/workflows/ruby.yml +2 -0
- data/.rubocop.yml +29 -0
- data/.rubocop_todo.yml +82 -0
- data/Gemfile +4 -1
- data/Rakefile +3 -1
- data/lib/ey-hmac/adapter/faraday.rb +10 -14
- data/lib/ey-hmac/adapter/rack.rb +9 -12
- data/lib/ey-hmac/adapter.rb +22 -19
- 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 -93
- data/spec/faraday_spec.rb +67 -61
- data/spec/rack_spec.rb +47 -42
- data/spec/shared/authenticated.rb +30 -28
- data/spec/spec_helper.rb +5 -3
- metadata +5 -2
data/lib/ey-hmac.rb
CHANGED
@@ -1,101 +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
|
-
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)
|
100
101
|
end
|
101
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,113 +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.create(:get)
|
42
|
-
r.path =
|
43
|
-
r.body =
|
44
|
-
r.headers = {
|
45
|
-
|
46
|
-
Faraday::Connection.new(
|
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')
|
47
49
|
)
|
48
50
|
|
49
51
|
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
50
52
|
|
51
|
-
expect(request[:request_headers]['Authorization']).to start_with(
|
53
|
+
expect(request[:request_headers]['Authorization']).to start_with('EyHmac')
|
52
54
|
expect(request[:request_headers]['Content-Digest']).to eq(Digest::MD5.hexdigest(request[:body]))
|
53
55
|
expect(Time.parse(request[:request_headers]['Date'])).not_to be_nil
|
54
56
|
|
55
57
|
yielded = false
|
56
58
|
|
57
|
-
expect(Ey::Hmac.
|
59
|
+
expect(Ey::Hmac).to be_authenticated(request, adapter: adapter) do |key_id|
|
58
60
|
expect(key_id).to eq(key_id)
|
59
61
|
yielded = true
|
60
62
|
key_secret
|
61
|
-
end
|
63
|
+
end
|
62
64
|
|
63
65
|
expect(yielded).to be_truthy
|
64
66
|
end
|
65
67
|
|
66
|
-
it
|
67
|
-
request = Faraday::Request.create(:get)
|
68
|
-
r.path =
|
68
|
+
it 'does not set Content-Digest if body is nil' do
|
69
|
+
request = Faraday::Request.create(:get) do |r|
|
70
|
+
r.path = '/auth'
|
69
71
|
r.body = nil
|
70
|
-
r.headers = {
|
71
|
-
|
72
|
-
Faraday::Connection.new(
|
72
|
+
r.headers = { 'Content-Type' => 'application/xml' }
|
73
|
+
end.to_env(
|
74
|
+
Faraday::Connection.new('http://localhost')
|
73
75
|
)
|
74
76
|
|
75
77
|
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
76
78
|
|
77
|
-
expect(request[:request_headers]['Authorization']).to start_with(
|
79
|
+
expect(request[:request_headers]['Authorization']).to start_with('EyHmac')
|
78
80
|
expect(request[:request_headers]).not_to have_key('Content-Digest')
|
79
81
|
expect(Time.parse(request[:request_headers]['Date'])).not_to be_nil
|
80
82
|
|
81
83
|
yielded = false
|
82
84
|
|
83
|
-
expect(Ey::Hmac.
|
85
|
+
expect(Ey::Hmac).to be_authenticated(request, adapter: adapter) do |key_id|
|
84
86
|
expect(key_id).to eq(key_id)
|
85
87
|
yielded = true
|
86
88
|
key_secret
|
87
|
-
end
|
89
|
+
end
|
88
90
|
|
89
91
|
expect(yielded).to be_truthy
|
90
92
|
end
|
91
93
|
|
92
|
-
it
|
94
|
+
it 'does not set Content-Digest if body is empty' do
|
93
95
|
request = Faraday::Request.create(:get) do |r|
|
94
|
-
r.path =
|
95
|
-
r.body =
|
96
|
-
r.headers = {
|
97
|
-
end.to_env(Faraday::Connection.new(
|
96
|
+
r.path = '/auth'
|
97
|
+
r.body = ''
|
98
|
+
r.headers = { 'Content-Type' => 'application/xml' }
|
99
|
+
end.to_env(Faraday::Connection.new('http://localhost'))
|
98
100
|
|
99
101
|
Ey::Hmac.sign!(request, key_id, key_secret, adapter: adapter)
|
100
102
|
|
101
|
-
expect(request[:request_headers]['Authorization']).to start_with(
|
103
|
+
expect(request[:request_headers]['Authorization']).to start_with('EyHmac')
|
102
104
|
expect(request[:request_headers]).not_to have_key('Content-Digest')
|
103
|
-
#expect(Time.parse(request[:request_headers]['Date'])).not_to be_nil
|
105
|
+
# expect(Time.parse(request[:request_headers]['Date'])).not_to be_nil
|
104
106
|
|
105
107
|
yielded = false
|
106
108
|
|
107
|
-
expect(Ey::Hmac.
|
109
|
+
expect(Ey::Hmac).to be_authenticated(request, adapter: adapter) do |key_id|
|
108
110
|
expect(key_id).to eq(key_id)
|
109
111
|
yielded = true
|
110
112
|
key_secret
|
111
|
-
end
|
113
|
+
end
|
112
114
|
|
113
115
|
expect(yielded).to be_truthy
|
114
116
|
end
|
115
117
|
|
116
|
-
context
|
118
|
+
context 'with a request' do
|
117
119
|
let!(:request) do
|
118
120
|
Faraday::Request.create(:get) do |r|
|
119
|
-
r.path =
|
120
|
-
r.body =
|
121
|
-
r.headers = {
|
122
|
-
end.to_env(Faraday::Connection.new(
|
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'))
|
123
125
|
end
|
124
|
-
|
126
|
+
|
127
|
+
include_examples 'authentication'
|
125
128
|
end
|
126
129
|
end
|
127
130
|
|
128
|
-
describe
|
129
|
-
it
|
131
|
+
describe 'middleware' do
|
132
|
+
it 'accepts a SHA1 signature' do
|
130
133
|
require 'ey-hmac/faraday'
|
131
134
|
Bundler.require(:rack)
|
132
135
|
|
133
136
|
app = lambda do |env|
|
134
|
-
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|
|
135
139
|
(auth_id == key_id) && key_secret
|
136
140
|
end
|
137
|
-
[(authenticated ? 200 : 401), {
|
141
|
+
[(authenticated ? 200 : 401), { 'Content-Type' => 'text/plain' }, []]
|
138
142
|
end
|
139
143
|
|
140
144
|
connection = Faraday.new do |c|
|
@@ -142,10 +146,10 @@ describe "faraday" do
|
|
142
146
|
c.adapter(:rack, app)
|
143
147
|
end
|
144
148
|
|
145
|
-
expect(connection.get(
|
149
|
+
expect(connection.get('/resources').status).to eq(200)
|
146
150
|
end
|
147
151
|
|
148
|
-
it
|
152
|
+
it 'accepts a SHA256 signature' do # default
|
149
153
|
require 'ey-hmac/faraday'
|
150
154
|
Bundler.require(:rack)
|
151
155
|
|
@@ -153,7 +157,7 @@ describe "faraday" do
|
|
153
157
|
authenticated = Ey::Hmac.authenticated?(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
154
158
|
(auth_id == key_id) && key_secret
|
155
159
|
end
|
156
|
-
[(authenticated ? 200 : 401), {
|
160
|
+
[(authenticated ? 200 : 401), { 'Content-Type' => 'text/plain' }, []]
|
157
161
|
end
|
158
162
|
|
159
163
|
connection = Faraday.new do |c|
|
@@ -161,18 +165,19 @@ describe "faraday" do
|
|
161
165
|
c.adapter(:rack, app)
|
162
166
|
end
|
163
167
|
|
164
|
-
expect(connection.get(
|
168
|
+
expect(connection.get('/resources').status).to eq(200)
|
165
169
|
end
|
166
170
|
|
167
|
-
it
|
171
|
+
it 'accepts multiple digest signatures' do # default
|
168
172
|
require 'ey-hmac/faraday'
|
169
173
|
Bundler.require(:rack)
|
170
174
|
|
171
175
|
app = lambda do |env|
|
172
|
-
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|
|
173
178
|
(auth_id == key_id) && key_secret
|
174
179
|
end
|
175
|
-
[(authenticated ? 200 : 401), {
|
180
|
+
[(authenticated ? 200 : 401), { 'Content-Type' => 'text/plain' }, []]
|
176
181
|
end
|
177
182
|
|
178
183
|
connection = Faraday.new do |c|
|
@@ -180,23 +185,24 @@ describe "faraday" do
|
|
180
185
|
c.adapter(:rack, app)
|
181
186
|
end
|
182
187
|
|
183
|
-
expect(connection.get(
|
188
|
+
expect(connection.get('/resources').status).to eq(200)
|
184
189
|
end
|
185
190
|
|
186
|
-
it
|
191
|
+
it 'signs empty request' do
|
187
192
|
require 'ey-hmac/faraday'
|
188
193
|
Bundler.require(:rack)
|
189
194
|
|
190
|
-
|
195
|
+
outer_key_id = key_id
|
196
|
+
outer_key_secret = key_secret
|
191
197
|
app = Rack::Builder.new do
|
192
198
|
use Rack::Config do |env|
|
193
|
-
env[
|
199
|
+
env['CONTENT_TYPE'] ||= 'text/html'
|
194
200
|
end
|
195
|
-
run(lambda {|env|
|
201
|
+
run(lambda { |env|
|
196
202
|
authenticated = Ey::Hmac.authenticate!(env, adapter: Ey::Hmac::Adapter::Rack) do |auth_id|
|
197
|
-
(auth_id ==
|
203
|
+
(auth_id == outer_key_id) && outer_key_secret
|
198
204
|
end
|
199
|
-
[(authenticated ? 200 : 401), {
|
205
|
+
[(authenticated ? 200 : 401), { 'Content-Type' => 'text/plain' }, []]
|
200
206
|
})
|
201
207
|
end
|
202
208
|
|
@@ -206,10 +212,10 @@ describe "faraday" do
|
|
206
212
|
end
|
207
213
|
|
208
214
|
expect(connection.get do |req|
|
209
|
-
req.path =
|
215
|
+
req.path = '/resource'
|
210
216
|
req.body = nil
|
211
|
-
req.params = {
|
212
|
-
req.headers = {
|
217
|
+
req.params = { 'a' => '1' }
|
218
|
+
req.headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
213
219
|
end.status).to eq(200)
|
214
220
|
end
|
215
221
|
end
|