api-auth 2.1.0 → 2.2.0

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.
@@ -1,20 +1,11 @@
1
1
  module ApiAuth
2
2
  module Helpers # :nodoc:
3
3
  def b64_encode(string)
4
- if Base64.respond_to?(:strict_encode64)
5
- Base64.strict_encode64(string)
6
- else
7
- # Fall back to stripping out newlines on Ruby 1.8.
8
- Base64.encode64(string).delete("\n")
9
- end
4
+ Base64.strict_encode64(string)
10
5
  end
11
6
 
12
7
  def md5_base64digest(string)
13
- if Digest::MD5.respond_to?(:base64digest)
14
- Digest::MD5.base64digest(string)
15
- else
16
- b64_encode(Digest::MD5.digest(string))
17
- end
8
+ Digest::MD5.base64digest(string)
18
9
  end
19
10
 
20
11
  # Capitalizes the keys of a hash
@@ -18,22 +18,14 @@ module ApiAuth
18
18
  end
19
19
  end # ControllerMethods
20
20
 
21
- module ActiveResourceExtension # :nodoc:
21
+ module ActiveResourceExtension # :nodoc:
22
22
  module ActiveResourceApiAuth # :nodoc:
23
23
  def self.included(base)
24
24
  base.extend(ClassMethods)
25
-
26
- if base.respond_to?('class_attribute')
27
- base.class_attribute :hmac_access_id
28
- base.class_attribute :hmac_secret_key
29
- base.class_attribute :use_hmac
30
- base.class_attribute :api_auth_options
31
- else
32
- base.class_inheritable_accessor :hmac_access_id
33
- base.class_inheritable_accessor :hmac_secret_key
34
- base.class_inheritable_accessor :use_hmac
35
- base.class_inheritable_accessor :api_auth_options
36
- end
25
+ base.class_attribute :hmac_access_id
26
+ base.class_attribute :hmac_secret_key
27
+ base.class_attribute :use_hmac
28
+ base.class_attribute :api_auth_options
37
29
  end
38
30
 
39
31
  module ClassMethods
@@ -43,13 +43,15 @@ module ApiAuth
43
43
  end
44
44
 
45
45
  def content_type
46
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
47
- value.nil? ? '' : value
46
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
48
47
  end
49
48
 
50
49
  def content_md5
51
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5 HTTP_CONTENT_MD5))
52
- value.nil? ? '' : value
50
+ find_header(%w[CONTENT-MD5 CONTENT_MD5 HTTP_CONTENT_MD5])
51
+ end
52
+
53
+ def original_uri
54
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
53
55
  end
54
56
 
55
57
  def request_uri
@@ -62,12 +64,11 @@ module ApiAuth
62
64
  end
63
65
 
64
66
  def timestamp
65
- value = find_header(%w(DATE HTTP_DATE))
66
- value.nil? ? '' : value
67
+ find_header(%w[DATE HTTP_DATE])
67
68
  end
68
69
 
69
70
  def authorization_header
70
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
71
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
71
72
  end
72
73
 
73
74
  private
@@ -32,13 +32,15 @@ module ApiAuth
32
32
  end
33
33
 
34
34
  def content_type
35
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
36
- value.nil? ? '' : value
35
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
37
36
  end
38
37
 
39
38
  def content_md5
40
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
41
- value.nil? ? '' : value
39
+ find_header(%w[CONTENT-MD5 CONTENT_MD5])
40
+ end
41
+
42
+ def original_uri
43
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
42
44
  end
43
45
 
44
46
  def request_uri
@@ -51,12 +53,11 @@ module ApiAuth
51
53
  end
52
54
 
53
55
  def timestamp
54
- value = find_header(%w(DATE HTTP_DATE))
55
- value.nil? ? '' : value
56
+ find_header(%w[DATE HTTP_DATE])
56
57
  end
57
58
 
58
59
  def authorization_header
59
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
60
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
60
61
  end
61
62
 
62
63
  private
@@ -21,13 +21,13 @@ module ApiAuth
21
21
  end
22
22
 
23
23
  def populate_content_md5
24
- return unless %w(POST PUT).include?(@request.method.to_s.upcase)
24
+ return unless %w[POST PUT].include?(@request.method.to_s.upcase)
25
25
  @request.headers['Content-MD5'] = calculated_md5
26
26
  fetch_headers
27
27
  end
28
28
 
29
29
  def md5_mismatch?
30
- if %w(POST PUT).include?(@request.method.to_s.upcase)
30
+ if %w[POST PUT].include?(@request.method.to_s.upcase)
31
31
  calculated_md5 != content_md5
32
32
  else
33
33
  false
@@ -43,13 +43,15 @@ module ApiAuth
43
43
  end
44
44
 
45
45
  def content_type
46
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
47
- value.nil? ? '' : value
46
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
48
47
  end
49
48
 
50
49
  def content_md5
51
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5 HTTP-CONTENT-MD5 HTTP_CONTENT_MD5))
52
- value.nil? ? '' : value
50
+ find_header(%w[CONTENT-MD5 CONTENT_MD5 HTTP-CONTENT-MD5 HTTP_CONTENT_MD5])
51
+ end
52
+
53
+ def original_uri
54
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
53
55
  end
54
56
 
55
57
  def request_uri
@@ -65,12 +67,11 @@ module ApiAuth
65
67
  end
66
68
 
67
69
  def timestamp
68
- value = find_header(%w(DATE HTTP_DATE))
69
- value.nil? ? '' : value
70
+ find_header(%w[DATE HTTP_DATE])
70
71
  end
71
72
 
72
73
  def authorization_header
73
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
74
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
74
75
  end
75
76
 
76
77
  private
@@ -42,13 +42,15 @@ module ApiAuth
42
42
  end
43
43
 
44
44
  def content_type
45
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
46
- value.nil? ? '' : value
45
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
47
46
  end
48
47
 
49
48
  def content_md5
50
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
51
- value.nil? ? '' : value
49
+ find_header(%w[CONTENT-MD5 CONTENT_MD5])
50
+ end
51
+
52
+ def original_uri
53
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
52
54
  end
53
55
 
54
56
  def request_uri
@@ -61,12 +63,11 @@ module ApiAuth
61
63
  end
62
64
 
63
65
  def timestamp
64
- value = find_header(%w(DATE HTTP_DATE))
65
- value.nil? ? '' : value
66
+ find_header(%w[DATE HTTP_DATE])
66
67
  end
67
68
 
68
69
  def authorization_header
69
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
70
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
70
71
  end
71
72
 
72
73
  private
@@ -49,13 +49,15 @@ module ApiAuth
49
49
  end
50
50
 
51
51
  def content_type
52
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
53
- value.nil? ? '' : value
52
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
54
53
  end
55
54
 
56
55
  def content_md5
57
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
58
- value.nil? ? '' : value
56
+ find_header(%w[CONTENT-MD5 CONTENT_MD5])
57
+ end
58
+
59
+ def original_uri
60
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
59
61
  end
60
62
 
61
63
  def request_uri
@@ -67,12 +69,11 @@ module ApiAuth
67
69
  end
68
70
 
69
71
  def timestamp
70
- value = find_header(%w(DATE HTTP_DATE))
71
- value.nil? ? '' : value
72
+ find_header(%w[DATE HTTP_DATE])
72
73
  end
73
74
 
74
75
  def authorization_header
75
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
76
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
76
77
  end
77
78
 
78
79
  private
@@ -26,13 +26,13 @@ module ApiAuth
26
26
  end
27
27
 
28
28
  def populate_content_md5
29
- return unless %w(POST PUT).include?(@request.request_method)
29
+ return unless %w[POST PUT].include?(@request.request_method)
30
30
  @request.env['Content-MD5'] = calculated_md5
31
31
  fetch_headers
32
32
  end
33
33
 
34
34
  def md5_mismatch?
35
- if %w(POST PUT).include?(@request.request_method)
35
+ if %w[POST PUT].include?(@request.request_method)
36
36
  calculated_md5 != content_md5
37
37
  else
38
38
  false
@@ -48,13 +48,15 @@ module ApiAuth
48
48
  end
49
49
 
50
50
  def content_type
51
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
52
- value.nil? ? '' : value
51
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
53
52
  end
54
53
 
55
54
  def content_md5
56
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5 HTTP-CONTENT-MD5 HTTP_CONTENT_MD5))
57
- value.nil? ? '' : value
55
+ find_header(%w[CONTENT-MD5 CONTENT_MD5 HTTP-CONTENT-MD5 HTTP_CONTENT_MD5])
56
+ end
57
+
58
+ def original_uri
59
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
58
60
  end
59
61
 
60
62
  def request_uri
@@ -67,12 +69,11 @@ module ApiAuth
67
69
  end
68
70
 
69
71
  def timestamp
70
- value = find_header(%w(DATE HTTP_DATE))
71
- value.nil? ? '' : value
72
+ find_header(%w[DATE HTTP_DATE])
72
73
  end
73
74
 
74
75
  def authorization_header
75
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
76
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
76
77
  end
77
78
 
78
79
  private
@@ -29,13 +29,13 @@ module ApiAuth
29
29
  end
30
30
 
31
31
  def populate_content_md5
32
- return unless [:post, :put].include?(@request.method)
32
+ return unless %i[post put].include?(@request.method)
33
33
  @request.headers['Content-MD5'] = calculated_md5
34
34
  save_headers
35
35
  end
36
36
 
37
37
  def md5_mismatch?
38
- if [:post, :put].include?(@request.method)
38
+ if %i[post put].include?(@request.method)
39
39
  calculated_md5 != content_md5
40
40
  else
41
41
  false
@@ -51,13 +51,15 @@ module ApiAuth
51
51
  end
52
52
 
53
53
  def content_type
54
- value = find_header(%w(CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE))
55
- value.nil? ? '' : value
54
+ find_header(%w[CONTENT-TYPE CONTENT_TYPE HTTP_CONTENT_TYPE])
56
55
  end
57
56
 
58
57
  def content_md5
59
- value = find_header(%w(CONTENT-MD5 CONTENT_MD5))
60
- value.nil? ? '' : value
58
+ find_header(%w[CONTENT-MD5 CONTENT_MD5])
59
+ end
60
+
61
+ def original_uri
62
+ find_header(%w[X-ORIGINAL-URI X_ORIGINAL_URI HTTP_X_ORIGINAL_URI])
61
63
  end
62
64
 
63
65
  def request_uri
@@ -70,12 +72,11 @@ module ApiAuth
70
72
  end
71
73
 
72
74
  def timestamp
73
- value = find_header(%w(DATE HTTP_DATE))
74
- value.nil? ? '' : value
75
+ find_header(%w[DATE HTTP_DATE])
75
76
  end
76
77
 
77
78
  def authorization_header
78
- find_header %w(Authorization AUTHORIZATION HTTP_AUTHORIZATION)
79
+ find_header %w[Authorization AUTHORIZATION HTTP_AUTHORIZATION]
79
80
  end
80
81
 
81
82
  private
@@ -0,0 +1,5 @@
1
+ inherit_from:
2
+ - ../.rubocop.yml
3
+
4
+ Metrics/BlockLength:
5
+ Enabled: false
@@ -1,4 +1,3 @@
1
- # encoding: UTF-8
2
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
2
 
4
3
  describe 'ApiAuth' do
@@ -25,7 +24,7 @@ describe 'ApiAuth' do
25
24
  end
26
25
 
27
26
  describe '.sign!' do
28
- let(:request) { RestClient::Request.new(:url => 'http://google.com', :method => :get) }
27
+ let(:request) { RestClient::Request.new(url: 'http://google.com', method: :get) }
29
28
  let(:headers) { ApiAuth::Headers.new(request) }
30
29
 
31
30
  it 'generates date header before signing' do
@@ -66,7 +65,7 @@ describe 'ApiAuth' do
66
65
  let(:canonical_string) { ApiAuth::Headers.new(request).canonical_string }
67
66
 
68
67
  it 'calculates the hmac_signature with http method' do
69
- ApiAuth.sign!(request, '1044', '123', :digest => 'sha256')
68
+ ApiAuth.sign!(request, '1044', '123', digest: 'sha256')
70
69
  signature = hmac('123', request, canonical_string, 'sha256')
71
70
  expect(request['Authorization']).to eq("APIAuth-HMAC-SHA256 1044:#{signature}")
72
71
  end
@@ -139,13 +138,13 @@ describe 'ApiAuth' do
139
138
 
140
139
  context 'matching client digest' do
141
140
  it 'validates matching digest' do
142
- expect(ApiAuth.authentic?(request, '123', :digest => 'sha256')).to eq true
141
+ expect(ApiAuth.authentic?(request, '123', digest: 'sha256')).to eq true
143
142
  end
144
143
  end
145
144
 
146
145
  context 'different client digest' do
147
146
  it 'raises an exception' do
148
- expect { ApiAuth.authentic?(request, '123', :digest => 'sha512') }.to raise_error(ApiAuth::InvalidRequestDigest)
147
+ expect { ApiAuth.authentic?(request, '123', digest: 'sha512') }.to raise_error(ApiAuth::InvalidRequestDigest)
149
148
  end
150
149
  end
151
150
  end
@@ -154,19 +153,31 @@ describe 'ApiAuth' do
154
153
  let(:digest) { 'SHA111' }
155
154
 
156
155
  it 'fails validation' do
157
- expect(ApiAuth.authentic?(request, '123', :digest => 'sha111')).to eq false
156
+ expect(ApiAuth.authentic?(request, '123', digest: 'sha111')).to eq false
158
157
  end
159
158
  end
160
159
  end
160
+
161
+ context 'when passed the clock_skew option' do
162
+ it 'fails to validate expired requests' do
163
+ request['date'] = 90.seconds.ago.utc.httpdate
164
+ expect(ApiAuth.authentic?(signed_request, '123', clock_skew: 60.seconds)).to eq false
165
+ end
166
+
167
+ it 'fails to validate far future requests' do
168
+ request['date'] = 90.seconds.from_now.utc.httpdate
169
+ expect(ApiAuth.authentic?(signed_request, '123', clock_skew: 60.seconds)).to eq false
170
+ end
171
+ end
161
172
  end
162
173
 
163
174
  describe '.access_id' do
164
175
  context 'normal APIAuth Auth header' do
165
176
  let(:request) do
166
177
  RestClient::Request.new(
167
- :url => 'http://google.com',
168
- :method => :get,
169
- :headers => { :authorization => 'APIAuth 1044:aGVsbG8gd29ybGQ=' }
178
+ url: 'http://google.com',
179
+ method: :get,
180
+ headers: { authorization: 'APIAuth 1044:aGVsbG8gd29ybGQ=' }
170
181
  )
171
182
  end
172
183
 
@@ -178,9 +189,9 @@ describe 'ApiAuth' do
178
189
  context 'Corporate prefixed APIAuth header' do
179
190
  let(:request) do
180
191
  RestClient::Request.new(
181
- :url => 'http://google.com',
182
- :method => :get,
183
- :headers => { :authorization => 'Corporate APIAuth 1044:aGVsbG8gd29ybGQ=' }
192
+ url: 'http://google.com',
193
+ method: :get,
194
+ headers: { authorization: 'Corporate APIAuth 1044:aGVsbG8gd29ybGQ=' }
184
195
  )
185
196
  end
186
197
 
@@ -3,7 +3,7 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
3
  describe ApiAuth::Headers do
4
4
  describe '#canonical_string' do
5
5
  context 'uri edge cases' do
6
- let(:request) { RestClient::Request.new(:url => uri, :method => :get) }
6
+ let(:request) { RestClient::Request.new(url: uri, method: :get) }
7
7
  subject(:headers) { described_class.new(request) }
8
8
  let(:uri) { '' }
9
9
 
@@ -38,11 +38,23 @@ describe ApiAuth::Headers do
38
38
  expect(request.url).to eq(uri)
39
39
  end
40
40
  end
41
+
42
+ context 'uri has a string matching http:// in it' do
43
+ let(:uri) { 'http://google.com/?redirect_to=https://www.example.com'.freeze }
44
+
45
+ it 'return /?redirect_to=https://www.example.com as canonical string path' do
46
+ expect(subject.canonical_string).to eq('GET,,,/?redirect_to=https://www.example.com,')
47
+ end
48
+
49
+ it 'does not change request url (by removing host)' do
50
+ expect(request.url).to eq(uri)
51
+ end
52
+ end
41
53
  end
42
54
 
43
55
  context 'string construction' do
44
56
  context 'with a driver that supplies http_method' do
45
- let(:request) { RestClient::Request.new(:url => 'http://google.com', :method => :get) }
57
+ let(:request) { RestClient::Request.new(url: 'http://google.com', method: :get) }
46
58
  subject(:headers) { described_class.new(request) }
47
59
  let(:driver) { headers.instance_variable_get('@request') }
48
60
 
@@ -96,6 +108,31 @@ describe ApiAuth::Headers do
96
108
  end
97
109
  end
98
110
  end
111
+
112
+ context "when there's a proxy server (e.g. Nginx) with rewrite rules" do
113
+ let(:request) do
114
+ Faraday::Request.create('GET') do |req|
115
+ req.options = Faraday::RequestOptions.new(Faraday::FlatParamsEncoder)
116
+ req.params = Faraday::Utils::ParamsHash.new
117
+ req.url('/resource.xml?foo=bar&bar=foo')
118
+ req.headers = { 'X-Original-URI' => '/api/resource.xml?foo=bar&bar=foo' }
119
+ end
120
+ end
121
+ subject(:headers) { described_class.new(request) }
122
+ let(:driver) { headers.instance_variable_get('@request') }
123
+
124
+ before do
125
+ allow(driver).to receive(:content_type).and_return 'text/html'
126
+ allow(driver).to receive(:content_md5).and_return '12345'
127
+ allow(driver).to receive(:timestamp).and_return 'Mon, 23 Jan 1984 03:29:56 GMT'
128
+ end
129
+
130
+ context 'the driver uses the original_uri' do
131
+ it 'constructs the canonical_string with the original_uri' do
132
+ expect(headers.canonical_string).to eq 'GET,text/html,12345,/api/resource.xml?foo=bar&bar=foo,Mon, 23 Jan 1984 03:29:56 GMT'
133
+ end
134
+ end
135
+ end
99
136
  end
100
137
  end
101
138
 
@@ -106,9 +143,9 @@ describe ApiAuth::Headers do
106
143
  context 'no md5 already calculated' do
107
144
  let(:request) do
108
145
  RestClient::Request.new(
109
- :url => 'http://google.com',
110
- :method => :post,
111
- :payload => "hello\nworld"
146
+ url: 'http://google.com',
147
+ method: :post,
148
+ payload: "hello\nworld"
112
149
  )
113
150
  end
114
151
 
@@ -121,10 +158,10 @@ describe ApiAuth::Headers do
121
158
  context 'md5 already calculated' do
122
159
  let(:request) do
123
160
  RestClient::Request.new(
124
- :url => 'http://google.com',
125
- :method => :post,
126
- :payload => "hello\nworld",
127
- :headers => { :content_md5 => 'abcd' }
161
+ url: 'http://google.com',
162
+ method: :post,
163
+ payload: "hello\nworld",
164
+ headers: { content_md5: 'abcd' }
128
165
  )
129
166
  end
130
167
 
@@ -136,7 +173,7 @@ describe ApiAuth::Headers do
136
173
  end
137
174
 
138
175
  describe '#md5_mismatch?' do
139
- let(:request) { RestClient::Request.new(:url => 'http://google.com', :method => :get) }
176
+ let(:request) { RestClient::Request.new(url: 'http://google.com', method: :get) }
140
177
  subject(:headers) { described_class.new(request) }
141
178
  let(:driver) { headers.instance_variable_get('@request') }
142
179
 
@@ -151,14 +188,14 @@ describe ApiAuth::Headers do
151
188
 
152
189
  context 'when request has no md5' do
153
190
  it "doesn't ask the driver" do
154
- allow(driver).to receive(:content_md5).and_return ''
191
+ allow(driver).to receive(:content_md5).and_return nil
155
192
 
156
193
  expect(driver).not_to receive(:md5_mismatch?).and_call_original
157
194
  headers.md5_mismatch?
158
195
  end
159
196
 
160
197
  it 'returns false' do
161
- allow(driver).to receive(:content_md5).and_return ''
198
+ allow(driver).to receive(:content_md5).and_return nil
162
199
 
163
200
  expect(headers.md5_mismatch?).to be false
164
201
  end