hawk-auth 0.1.0 → 0.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,5 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.0.0
3
4
  - 1.9.3
4
5
  - 1.9.2
5
6
  - rbx-19mode
data/README.md CHANGED
@@ -30,7 +30,7 @@ $ irb
30
30
  > },
31
31
  > :ts => 1365898519,
32
32
  > :method => 'POST',
33
- > :path => '/somewhere/over/the/rainbow',
33
+ > :request_uri => '/somewhere/over/the/rainbow',
34
34
  > :host => 'example.net',
35
35
  > :port => 80,
36
36
  > :payload => 'something to write about',
@@ -48,7 +48,7 @@ Hawk id="123456", ts="1365898519", nonce="Ygvqdz", hash="LjRmtkSKTW0ObTUyZ7N+vjC
48
48
  > },
49
49
  > :ts => 1365899773,
50
50
  > :method => "POST",
51
- > :path => "/somewhere/over/the/rainbow",
51
+ > :request_uri => "/somewhere/over/the/rainbow",
52
52
  > :host => "example.net",
53
53
  > :port => 80,
54
54
  > :payload => "something to write about",
@@ -68,7 +68,7 @@ Hawk id="123456", ts="1365898519", nonce="Ygvqdz", hash="LjRmtkSKTW0ObTUyZ7N+vjC
68
68
  > Hawk::Server.authenticate(
69
69
  > %(Hawk id="123456", ts="1365900371", nonce="Ygvqdz", hash="9LxQVpfaAgyiyNeOgD8TEKP6RnM=", mac="lv54INsJZym8wnME0nQAu5jW6BA="),
70
70
  > :method => "POST",
71
- > :path => "/somewhere/over/the/rainbow",
71
+ > :request_uri => "/somewhere/over/the/rainbow",
72
72
  > :host => "example.net",
73
73
  > :port => 80,
74
74
  > :content_type => "text/plain",
@@ -81,7 +81,7 @@ Hawk id="123456", ts="1365898519", nonce="Ygvqdz", hash="LjRmtkSKTW0ObTUyZ7N+vjC
81
81
  > res = Hawk::Server.authenticate(
82
82
  > %(Hawk id="123456", ts="1365901299", mac="zTu3FSTmdsdSaLHd/DrpeQRkuYzcb0snYYKOmwDwP3w="),
83
83
  > :method => "POST",
84
- > :path => "/somewhere/over/the/rainbow",
84
+ > :request_uri => "/somewhere/over/the/rainbow",
85
85
  > :host => "example.net",
86
86
  > :port => 80,
87
87
  > :content_type => "text/plain",
@@ -100,7 +100,7 @@ Hawk ts="1365901388", tsm="6mdH5DT66UeWlkBC9x2QD7Upt0eYnud9dB7y7xKoEoU=", error=
100
100
  > },
101
101
  > :ts => 1365900682,
102
102
  > :method => "POST",
103
- > :path => "/somewhere/over/the/rainbow",
103
+ > :request_uri => "/somewhere/over/the/rainbow",
104
104
  > :host => "example.net",
105
105
  > :port => 80,
106
106
  > :ext => "Bazinga!",
@@ -2,11 +2,13 @@ module Hawk
2
2
  module AuthorizationHeader
3
3
  extend self
4
4
 
5
- REQUIRED_OPTIONS = [:method, :path, :host, :port].freeze
5
+ REQUIRED_OPTIONS = [:method, :request_uri, :host, :port].freeze
6
6
  REQUIRED_CREDENTIAL_MEMBERS = [:id, :key, :algorithm].freeze
7
7
  SUPPORTED_ALGORITHMS = ['sha256', 'sha1'].freeze
8
8
  HEADER_PARTS = [:id, :ts, :nonce, :hash, :ext, :mac].freeze
9
9
 
10
+ DEFAULT_TIMESTAMP_SKEW = 60.freeze # ±60 seconds
11
+
10
12
  MissingOptionError = Class.new(StandardError)
11
13
  InvalidCredentialsError = Class.new(StandardError)
12
14
  InvalidAlgorithmError = Class.new(StandardError)
@@ -41,7 +43,7 @@ module Hawk
41
43
  :nonce => options[:nonce],
42
44
  :mac => mac
43
45
  }
44
- parts[:hash] = hash if options.has_key?(:payload)
46
+ parts[:hash] = hash if options.has_key?(:payload) && !options[:payload].nil?
45
47
  parts[:ext] = options[:ext] if options.has_key?(:ext)
46
48
 
47
49
  "Hawk " << (only || HEADER_PARTS).inject([]) { |memo, key|
@@ -52,10 +54,15 @@ module Hawk
52
54
  end
53
55
 
54
56
  def authenticate(header, options)
57
+ options = options.dup
58
+
55
59
  parts = parse(header)
60
+ options.delete(:payload) unless parts[:hash]
56
61
 
57
62
  now = Time.now.to_i
58
63
 
64
+ options[:timestamp_skew] ||= DEFAULT_TIMESTAMP_SKEW
65
+
59
66
  if options[:server_response]
60
67
  credentials = options[:credentials]
61
68
  parts.merge!(
@@ -67,7 +74,7 @@ module Hawk
67
74
  return AuthenticationFailure.new(:id, "Unidentified id")
68
75
  end
69
76
 
70
- if (now - parts[:ts].to_i > 1000) || (parts[:ts].to_i - now > 1000)
77
+ if (now - parts[:ts].to_i > options[:timestamp_skew]) || (parts[:ts].to_i - now > options[:timestamp_skew])
71
78
  # Stale timestamp
72
79
  return AuthenticationFailure.new(:ts, "Stale ts", :credentials => credentials)
73
80
  end
@@ -86,7 +93,9 @@ module Hawk
86
93
  :credentials => credentials,
87
94
  :ts => parts[:ts],
88
95
  :nonce => parts[:nonce],
89
- :ext => parts[:ext]
96
+ :ext => parts[:ext],
97
+ :app => options[:app] || parts[:app],
98
+ :dig => options[:dig] || parts[:dig]
90
99
  ))
91
100
  unless expected_mac == parts[:mac]
92
101
  return AuthenticationFailure.new(:mac, "Invalid mac")
@@ -5,7 +5,7 @@ module Hawk
5
5
  extend self
6
6
 
7
7
  def authenticate(authorization_header, options)
8
- Hawk::AuthorizationHeader.authenticate(authorization_header, { :server_response => true }.merge(options))
8
+ Hawk::AuthorizationHeader.authenticate(authorization_header, { :server_response => true, :type => 'response' }.merge(options))
9
9
  end
10
10
 
11
11
  def build_authorization_header(options)
@@ -34,27 +34,34 @@ module Hawk
34
34
  end
35
35
 
36
36
  def normalized_string(options)
37
+ options = options.dup
38
+ if !options[:hash] && options.has_key?(:payload) && !options[:payload].nil?
39
+ options[:hash] = hash(options)
40
+ end
41
+
37
42
  parts = []
38
43
 
39
44
  parts << "hawk.1.#{options[:type] || 'header'}"
40
45
  parts << options[:ts]
41
46
  parts << options[:nonce]
42
47
  parts << options[:method].to_s.upcase
43
- parts << options[:path]
48
+ parts << options[:request_uri]
44
49
  parts << options[:host]
45
50
  parts << options[:port]
46
51
  parts << options[:hash]
47
52
  parts << options[:ext]
53
+
54
+ if options[:app]
55
+ parts << options[:app]
56
+ parts << options[:dig]
57
+ end
58
+
48
59
  parts << nil # trailing newline
49
60
 
50
61
  parts.join("\n")
51
62
  end
52
63
 
53
64
  def mac(options)
54
- if !options[:hash] && options.has_key?(:payload)
55
- options[:hash] = hash(options)
56
- end
57
-
58
65
  Base64.encode64(
59
66
  OpenSSL::HMAC.digest(
60
67
  openssl_digest(options[:credentials][:algorithm]).new,
@@ -21,7 +21,7 @@ module Hawk
21
21
  expected_bewit = Crypto.bewit(
22
22
  :credentials => credentials,
23
23
  :host => options[:host],
24
- :path => remove_bewit_param_from_path(options[:path]),
24
+ :request_uri => remove_bewit_param_from_path(options[:request_uri]),
25
25
  :port => options[:port],
26
26
  :method => options[:method],
27
27
  :ts => timestamp,
@@ -29,7 +29,13 @@ module Hawk
29
29
  )
30
30
 
31
31
  unless expected_bewit == bewit
32
- return AuthenticationFailure.new(:bewit, "Invalid signature")
32
+ if options[:request_uri].to_s =~ /\Ahttp/
33
+ return authenticate_bewit(bewit, options.merge(
34
+ :request_uri => options[:request_uri].sub(%r{\Ahttps?://[^/]+}, '')
35
+ ))
36
+ else
37
+ return AuthenticationFailure.new(:bewit, "Invalid signature")
38
+ end
33
39
  end
34
40
 
35
41
  credentials
@@ -1,3 +1,3 @@
1
1
  module Hawk
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -21,7 +21,7 @@ describe Hawk::Client do
21
21
  let(:input) do
22
22
  _input = {
23
23
  :method => 'POST',
24
- :path => '/somewhere/over/the/rainbow',
24
+ :request_uri => '/somewhere/over/the/rainbow',
25
25
  :host => 'example.net',
26
26
  :port => 80,
27
27
  :content_type => 'text/plain',
@@ -35,6 +35,7 @@ describe Hawk::Client do
35
35
 
36
36
  let(:client_input) do
37
37
  _input = input
38
+ _input[:type] = 'response'
38
39
  _input[:ext] = ext if ext
39
40
  _input
40
41
  end
@@ -81,7 +82,7 @@ describe Hawk::Client do
81
82
  :credentials => credentials,
82
83
  :ts => timestamp,
83
84
  :method => 'POST',
84
- :path => '/somewhere/over/the/rainbow',
85
+ :request_uri => '/somewhere/over/the/rainbow',
85
86
  :host => 'example.net',
86
87
  :port => 80,
87
88
  :payload => 'something to write about',
@@ -26,7 +26,7 @@ describe Hawk::Crypto do
26
26
  :ts => 1353809207,
27
27
  :nonce => 'Ygvqdz',
28
28
  :method => 'POST',
29
- :path => '/somewhere/over/the/rainbow',
29
+ :request_uri => '/somewhere/over/the/rainbow',
30
30
  :host => 'example.net',
31
31
  :port => 80,
32
32
  :payload => 'something to write about',
@@ -62,7 +62,7 @@ describe Hawk::Crypto do
62
62
  {
63
63
  :credentials => credentials,
64
64
  :method => 'GET',
65
- :path => '/resource/4?a=1&b=2',
65
+ :request_uri => '/resource/4?a=1&b=2',
66
66
  :host => 'example.com',
67
67
  :port => 80,
68
68
  :ext => 'some-app-data',
@@ -91,7 +91,7 @@ describe Hawk::Crypto do
91
91
  :ts => 1353809207,
92
92
  :nonce => 'Ygvqdz',
93
93
  :method => 'POST',
94
- :path => '/somewhere/over/the/rainbow',
94
+ :request_uri => '/somewhere/over/the/rainbow',
95
95
  :host => 'example.net',
96
96
  :port => 80,
97
97
  :payload => 'something to write about',
@@ -140,25 +140,66 @@ describe Hawk::Crypto do
140
140
  :ts => 1365701514,
141
141
  :nonce => '5b4e',
142
142
  :method => 'GET',
143
- :path => "/path/to/foo?bar=baz",
143
+ :request_uri => "/path/to/foo?bar=baz",
144
144
  :host => 'example.com',
145
145
  :port => 8080
146
146
  }
147
147
  end
148
148
 
149
149
  let(:expected_output) do
150
- %(hawk.1.header\n#{input[:ts]}\n#{input[:nonce]}\n#{input[:method]}\n#{input[:path]}\n#{input[:host]}\n#{input[:port]}\n\n\n)
150
+ %(hawk.1.header\n#{input[:ts]}\n#{input[:nonce]}\n#{input[:method]}\n#{input[:request_uri]}\n#{input[:host]}\n#{input[:port]}\n\n\n)
151
151
  end
152
152
 
153
153
  it_behaves_like "an input normalization method"
154
154
 
155
+ context "with app" do
156
+ let(:input) do
157
+ {
158
+ :ts => 1365701514,
159
+ :nonce => '5b4e',
160
+ :method => 'GET',
161
+ :request_uri => '/path/to/foo?bar=baz',
162
+ :host => 'example.com',
163
+ :port => 8080,
164
+ :app => 'some app id'
165
+ }
166
+ end
167
+
168
+ let(:expected_output) do
169
+ %(hawk.1.header\n#{input[:ts]}\n#{input[:nonce]}\n#{input[:method]}\n#{input[:request_uri]}\n#{input[:host]}\n#{input[:port]}\n\n\n#{input[:app]}\n\n)
170
+ end
171
+
172
+ it_behaves_like "an input normalization method"
173
+ end
174
+
175
+ context "with app and dig" do
176
+ let(:input) do
177
+ {
178
+ :ts => 1365701514,
179
+ :nonce => '5b4e',
180
+ :method => 'GET',
181
+ :request_uri => '/path/to/foo?bar=baz',
182
+ :host => 'example.com',
183
+ :port => 8080,
184
+ :app => 'some app id',
185
+ :dig => 'some dig'
186
+ }
187
+ end
188
+
189
+ let(:expected_output) do
190
+ %(hawk.1.header\n#{input[:ts]}\n#{input[:nonce]}\n#{input[:method]}\n#{input[:request_uri]}\n#{input[:host]}\n#{input[:port]}\n\n\n#{input[:app]}\n#{input[:dig]}\n)
191
+ end
192
+
193
+ it_behaves_like "an input normalization method"
194
+ end
195
+
155
196
  context "with ext" do
156
197
  let(:input) do
157
198
  {
158
199
  :ts => 1365701514,
159
200
  :nonce => '5b4e',
160
201
  :method => 'GET',
161
- :path => '/path/to/foo?bar=baz',
202
+ :request_uri => '/path/to/foo?bar=baz',
162
203
  :host => 'example.com',
163
204
  :port => 8080,
164
205
  :ext => 'this is some app data'
@@ -166,7 +207,7 @@ describe Hawk::Crypto do
166
207
  end
167
208
 
168
209
  let(:expected_output) do
169
- %(hawk.1.header\n#{input[:ts]}\n#{input[:nonce]}\n#{input[:method]}\n#{input[:path]}\n#{input[:host]}\n#{input[:port]}\n\n#{input[:ext]}\n)
210
+ %(hawk.1.header\n#{input[:ts]}\n#{input[:nonce]}\n#{input[:method]}\n#{input[:request_uri]}\n#{input[:host]}\n#{input[:port]}\n\n#{input[:ext]}\n)
170
211
  end
171
212
 
172
213
  it_behaves_like "an input normalization method"
@@ -178,7 +219,7 @@ describe Hawk::Crypto do
178
219
  :ts => 1365701514,
179
220
  :nonce => '5b4e',
180
221
  :method => 'GET',
181
- :path => '/path/to/foo?bar=baz',
222
+ :request_uri => '/path/to/foo?bar=baz',
182
223
  :host => 'example.com',
183
224
  :port => 8080,
184
225
  :hash => 'U4MKKSmiVxk37JCCrAVIjV/OhB3y+NdwoCr6RShbVkE=',
@@ -187,7 +228,7 @@ describe Hawk::Crypto do
187
228
  end
188
229
 
189
230
  let(:expected_output) do
190
- %(hawk.1.header\n#{input[:ts]}\n#{input[:nonce]}\n#{input[:method]}\n#{input[:path]}\n#{input[:host]}\n#{input[:port]}\n#{input[:hash]}\n#{input[:ext]}\n)
231
+ %(hawk.1.header\n#{input[:ts]}\n#{input[:nonce]}\n#{input[:method]}\n#{input[:request_uri]}\n#{input[:host]}\n#{input[:port]}\n#{input[:hash]}\n#{input[:ext]}\n)
191
232
  end
192
233
 
193
234
  it_behaves_like "an input normalization method"
@@ -28,10 +28,12 @@ describe Hawk::Server do
28
28
  let(:timestamp) { Time.now.to_i }
29
29
  let(:nonce) { 'Ygvqdz' }
30
30
 
31
+ let(:timestamp_skew) { Hawk::AuthorizationHeader::DEFAULT_TIMESTAMP_SKEW }
32
+
31
33
  let(:input) do
32
34
  _input = {
33
35
  :method => 'POST',
34
- :path => '/somewhere/over/the/rainbow',
36
+ :request_uri => '/somewhere/over/the/rainbow',
35
37
  :host => 'example.net',
36
38
  :port => 80,
37
39
  :content_type => 'text/plain',
@@ -84,7 +86,7 @@ describe Hawk::Server do
84
86
 
85
87
  context "when stale timestamp" do
86
88
  context "when too old" do
87
- let(:timestamp) { Time.now.to_i - 1001 }
89
+ let(:timestamp) { Time.now.to_i - timestamp_skew - 1 }
88
90
 
89
91
  it "returns error object" do
90
92
  actual = described_class.authenticate(authorization_header, input)
@@ -95,7 +97,7 @@ describe Hawk::Server do
95
97
  end
96
98
 
97
99
  context "when too far in the future" do
98
- let(:timestamp) { Time.now.to_i + 1001 }
100
+ let(:timestamp) { Time.now.to_i + timestamp_skew + 1 }
99
101
 
100
102
  it "returns error object" do
101
103
  actual = described_class.authenticate(authorization_header, input)
@@ -184,7 +186,7 @@ describe Hawk::Server do
184
186
  :credentials => credentials,
185
187
  :ts => timestamp,
186
188
  :method => 'POST',
187
- :path => '/somewhere/over/the/rainbow',
189
+ :request_uri => '/somewhere/over/the/rainbow',
188
190
  :host => 'example.net',
189
191
  :port => 80,
190
192
  :payload => 'something to write about',
@@ -220,6 +222,47 @@ describe Hawk::Server do
220
222
  end
221
223
 
222
224
  describe ".authenticate_bewit" do
225
+ shared_examples "authenticate_bewit" do
226
+ context "when valid" do
227
+ it "returns credentials" do
228
+ expect(described_class.authenticate_bewit(bewit, input)).to eql(credentials)
229
+ end
230
+ end
231
+
232
+ context "when invalid format" do
233
+ let(:bewit) { "invalid-bewit" }
234
+
235
+ it "returns error object" do
236
+ actual = described_class.authenticate_bewit(bewit, input)
237
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
238
+ expect(actual.key).to eql(:id)
239
+ expect(actual.message).to_not eql(nil)
240
+ end
241
+ end
242
+
243
+ context "when invalid ext" do
244
+ let(:bewit) { "MTIzNDU2XDQ1MTkzMTE0NThcVTN4dVF5TEVXUGNOa3Q4Vm5oRy9BSDg4VERQZXlKT2JKeGVNb0tkZWZUQT1caW52YWxpZCBleHQ" }
245
+
246
+ it "returns error object" do
247
+ actual = described_class.authenticate_bewit(bewit, input)
248
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
249
+ expect(actual.key).to eql(:bewit)
250
+ expect(actual.message).to_not eql(nil)
251
+ end
252
+ end
253
+
254
+ context "when stale timestamp" do
255
+ let(:now) { 4519311459 }
256
+
257
+ it "returns error object" do
258
+ actual = described_class.authenticate_bewit(bewit, input)
259
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
260
+ expect(actual.key).to eql(:ts)
261
+ expect(actual.message).to_not eql(nil)
262
+ end
263
+ end
264
+ end
265
+
223
266
  let(:credentials_lookup) do
224
267
  lambda { |id|
225
268
  if id == credentials[:id]
@@ -234,7 +277,7 @@ describe Hawk::Server do
234
277
  {
235
278
  :credentials_lookup => credentials_lookup,
236
279
  :method => 'GET',
237
- :path => "/resource/4?a=1&bewit=#{bewit}&b=2",
280
+ :request_uri => "/resource/4?a=1&bewit=#{bewit}&b=2",
238
281
  :host => 'example.com',
239
282
  :port => 80,
240
283
  }
@@ -247,43 +290,16 @@ describe Hawk::Server do
247
290
  Time.stubs(:now).returns(Time.at(now))
248
291
  end
249
292
 
250
- context "when valid" do
251
- it "returns credentials" do
252
- expect(described_class.authenticate_bewit(bewit, input)).to eql(credentials)
253
- end
293
+ context "when request_uri is path" do
294
+ it_behaves_like "authenticate_bewit"
254
295
  end
255
296
 
256
- context "when invalid format" do
257
- let(:bewit) { "invalid-bewit" }
258
-
259
- it "returns error object" do
260
- actual = described_class.authenticate_bewit(bewit, input)
261
- expect(actual).to be_a(Hawk::AuthenticationFailure)
262
- expect(actual.key).to eql(:id)
263
- expect(actual.message).to_not eql(nil)
297
+ context "when request_uri is full url" do
298
+ before do
299
+ input[:request_uri] = "http://example.com/resource/4?a=1&bewit=#{bewit}&b=2"
264
300
  end
265
- end
266
301
 
267
- context "when invalid ext" do
268
- let(:bewit) { "MTIzNDU2XDQ1MTkzMTE0NThcVTN4dVF5TEVXUGNOa3Q4Vm5oRy9BSDg4VERQZXlKT2JKeGVNb0tkZWZUQT1caW52YWxpZCBleHQ" }
269
-
270
- it "returns error object" do
271
- actual = described_class.authenticate_bewit(bewit, input)
272
- expect(actual).to be_a(Hawk::AuthenticationFailure)
273
- expect(actual.key).to eql(:bewit)
274
- expect(actual.message).to_not eql(nil)
275
- end
276
- end
277
-
278
- context "when stale timestamp" do
279
- let(:now) { 4519311459 }
280
-
281
- it "returns error object" do
282
- actual = described_class.authenticate_bewit(bewit, input)
283
- expect(actual).to be_a(Hawk::AuthenticationFailure)
284
- expect(actual.key).to eql(:ts)
285
- expect(actual.message).to_not eql(nil)
286
- end
302
+ it_behaves_like "authenticate_bewit"
287
303
  end
288
304
  end
289
305
 
@@ -35,7 +35,7 @@ shared_examples "an authorization header builder" do
35
35
  context '', &returns_valid_authorization_header
36
36
  end
37
37
 
38
- %w( method path host port ).each do |missing_option|
38
+ %w( method request_uri host port ).each do |missing_option|
39
39
  context "when missing #{missing_option} option" do
40
40
  before do
41
41
  input.delete(missing_option.to_sym)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hawk-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-15 00:00:00.000000000 Z
12
+ date: 2013-06-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler