hawk-auth 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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