hawk-auth 0.0.0 → 0.1.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.
@@ -0,0 +1,290 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/authorization_header'
3
+
4
+ describe Hawk::Server do
5
+ let(:credentials) do
6
+ {
7
+ :id => '123456',
8
+ :key => '2983d45yun89q',
9
+ :algorithm => algorithm
10
+ }
11
+ end
12
+
13
+ describe ".authenticate" do
14
+ let(:credentials_lookup) do
15
+ lambda { |id|
16
+ if id == credentials[:id]
17
+ credentials
18
+ end
19
+ }
20
+ end
21
+
22
+ let(:nonce_lookup) do
23
+ lambda { |nonce| nil }
24
+ end
25
+
26
+ let(:payload) {}
27
+ let(:ext) {}
28
+ let(:timestamp) { Time.now.to_i }
29
+ let(:nonce) { 'Ygvqdz' }
30
+
31
+ let(:input) do
32
+ _input = {
33
+ :method => 'POST',
34
+ :path => '/somewhere/over/the/rainbow',
35
+ :host => 'example.net',
36
+ :port => 80,
37
+ :content_type => 'text/plain',
38
+ :credentials_lookup => credentials_lookup,
39
+ :nonce_lookup => nonce_lookup
40
+ }
41
+ _input[:payload] = payload if payload
42
+ _input
43
+ end
44
+
45
+ let(:client_input) do
46
+ _input = input.merge(
47
+ :credentials => credentials,
48
+ :ts => timestamp,
49
+ :nonce => nonce
50
+ )
51
+ _input[:ext] = ext if ext
52
+ _input
53
+ end
54
+
55
+ let(:expected_mac) { Hawk::Crypto.mac(client_input) }
56
+ let(:expected_hash) { client_input[:payload] ? Hawk::Crypto.hash(client_input) : nil }
57
+
58
+ let(:authorization_header) do
59
+ parts = []
60
+ parts << %(id="#{credentials[:id]}")
61
+ parts << %(ts="#{timestamp}")
62
+ parts << %(nonce="#{nonce}") if nonce
63
+ parts << %(hash="#{expected_hash}") if expected_hash
64
+ parts << %(mac="#{expected_mac}")
65
+ parts << %(ext="#{ext}") if ext
66
+ "Hawk #{parts.join(', ')}"
67
+ end
68
+
69
+ shared_examples "an authorization request header authenticator" do
70
+ it_behaves_like "an authorization header authenticator"
71
+
72
+ context "when unidentified id" do
73
+ let(:credentials_lookup) do
74
+ lambda { |id| }
75
+ end
76
+
77
+ it "returns error object" do
78
+ actual = described_class.authenticate(authorization_header, input)
79
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
80
+ expect(actual.key).to eql(:id)
81
+ expect(actual.message).to_not eql(nil)
82
+ end
83
+ end
84
+
85
+ context "when stale timestamp" do
86
+ context "when too old" do
87
+ let(:timestamp) { Time.now.to_i - 1001 }
88
+
89
+ it "returns error object" do
90
+ actual = described_class.authenticate(authorization_header, input)
91
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
92
+ expect(actual.key).to eql(:ts)
93
+ expect(actual.message).to_not eql(nil)
94
+ end
95
+ end
96
+
97
+ context "when too far in the future" do
98
+ let(:timestamp) { Time.now.to_i + 1001 }
99
+
100
+ it "returns error object" do
101
+ actual = described_class.authenticate(authorization_header, input)
102
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
103
+ expect(actual.key).to eql(:ts)
104
+ expect(actual.message).to_not eql(nil)
105
+ end
106
+ end
107
+
108
+ context "when invalid content type" do
109
+ let(:payload) { 'baz' }
110
+ before do
111
+ client_input[:content_type] = 'application/foo'
112
+ end
113
+
114
+ it "returns error object" do
115
+ actual = described_class.authenticate(authorization_header, input)
116
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
117
+ expect(actual.key).to eql(:mac)
118
+ expect(actual.message).to_not eql(nil)
119
+ end
120
+ end
121
+
122
+ context "when nonce missing" do
123
+ let(:nonce) { nil }
124
+
125
+ it "returns error object" do
126
+ actual = described_class.authenticate(authorization_header, input)
127
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
128
+ expect(actual.key).to eql(:nonce)
129
+ expect(actual.message).to_not eql(nil)
130
+ end
131
+ end
132
+ end
133
+
134
+ context "when replay" do
135
+ let(:nonce_lookup) do
136
+ lambda do |nonce|
137
+ true
138
+ end
139
+ end
140
+
141
+ it "returns error object" do
142
+ actual = described_class.authenticate(authorization_header, input)
143
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
144
+ expect(actual.key).to eql(:nonce)
145
+ expect(actual.message).to_not eql(nil)
146
+ end
147
+ end
148
+
149
+ context "when no credentials_lookup given" do
150
+ before do
151
+ input.delete(:credentials_lookup)
152
+ end
153
+
154
+ it "returns error object" do
155
+ actual = described_class.authenticate(authorization_header, input)
156
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
157
+ expect(actual.key).to eql(:id)
158
+ expect(actual.message).to_not eql(nil)
159
+ end
160
+ end
161
+ end
162
+
163
+ context "when using sha256" do
164
+ let(:algorithm) { "sha256" }
165
+
166
+ it_behaves_like "an authorization request header authenticator"
167
+ end
168
+
169
+ context "when using sha1" do
170
+ let(:algorithm) { "sha1" }
171
+
172
+ it_behaves_like "an authorization request header authenticator"
173
+ end
174
+ end
175
+
176
+ describe ".build_authorization_header" do
177
+ let(:expected_mac) { Hawk::Crypto.mac(input) }
178
+ let(:expected_hash) { input[:payload] ? Hawk::Crypto.hash(input) : nil }
179
+ let(:timestamp) { Time.now.to_i }
180
+ let(:nonce) { 'Ygvqdz' }
181
+
182
+ let(:input) do
183
+ _input = {
184
+ :credentials => credentials,
185
+ :ts => timestamp,
186
+ :method => 'POST',
187
+ :path => '/somewhere/over/the/rainbow',
188
+ :host => 'example.net',
189
+ :port => 80,
190
+ :payload => 'something to write about',
191
+ :ext => 'Bazinga!'
192
+ }
193
+ _input[:nonce] = nonce if nonce
194
+ _input
195
+ end
196
+
197
+ let(:expected_output_parts) do
198
+ parts = []
199
+ parts << %(hash="#{expected_hash}") if input[:payload]
200
+ parts << %(ext="#{input[:ext]}") if input[:ext]
201
+ parts << %(mac="#{expected_mac}")
202
+ parts
203
+ end
204
+
205
+ let(:expected_output) do
206
+ "Hawk #{expected_output_parts.join(', ')}"
207
+ end
208
+
209
+ context "when using sha256" do
210
+ let(:algorithm) { "sha256" }
211
+
212
+ it_behaves_like "an authorization header builder"
213
+ end
214
+
215
+ context "when using sha1" do
216
+ let(:algorithm) { "sha1" }
217
+
218
+ it_behaves_like "an authorization header builder"
219
+ end
220
+ end
221
+
222
+ describe ".authenticate_bewit" do
223
+ let(:credentials_lookup) do
224
+ lambda { |id|
225
+ if id == credentials[:id]
226
+ credentials
227
+ end
228
+ }
229
+ end
230
+
231
+ let(:algorithm) { "sha256" }
232
+
233
+ let(:input) do
234
+ {
235
+ :credentials_lookup => credentials_lookup,
236
+ :method => 'GET',
237
+ :path => "/resource/4?a=1&bewit=#{bewit}&b=2",
238
+ :host => 'example.com',
239
+ :port => 80,
240
+ }
241
+ end
242
+
243
+ let(:bewit) { "MTIzNDU2XDQ1MTkzMTE0NThcYkkwanFlS1prUHE0V1hRMmkxK0NrQ2lOanZEc3BSVkNGajlmbElqMXphWT1cc29tZS1hcHAtZGF0YQ" }
244
+
245
+ let(:now) { 1365711458 }
246
+ before do
247
+ Time.stubs(:now).returns(Time.at(now))
248
+ end
249
+
250
+ context "when valid" do
251
+ it "returns credentials" do
252
+ expect(described_class.authenticate_bewit(bewit, input)).to eql(credentials)
253
+ end
254
+ end
255
+
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)
264
+ end
265
+ end
266
+
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
287
+ end
288
+ end
289
+
290
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'bundler/setup'
5
+ require 'hawk'
6
+
7
+ RSpec.configure do |config|
8
+ config.mock_with :mocha
9
+ config.expect_with :rspec do |c|
10
+ c.syntax = :expect
11
+ end
12
+ end
@@ -0,0 +1,154 @@
1
+ shared_examples "an authorization header builder" do
2
+ returns_valid_authorization_header = proc do
3
+ it "returns valid authorization header" do
4
+ actual = described_class.build_authorization_header(input)
5
+
6
+ expected_output_parts.each do |expected_part|
7
+ matcher = Regexp === expected_part ? expected_part : Regexp.new(Regexp.escape(expected_part))
8
+ expect(actual).to match(matcher)
9
+ end
10
+
11
+ expect(actual).to eql(expected_output)
12
+ end
13
+ end
14
+
15
+ context "with full options", &returns_valid_authorization_header
16
+
17
+ context "without ext" do
18
+ before do
19
+ input.delete(:ext)
20
+ end
21
+ context '', &returns_valid_authorization_header
22
+ end
23
+
24
+ context "without payload" do
25
+ before do
26
+ input.delete(:payload)
27
+ end
28
+ context '', &returns_valid_authorization_header
29
+ end
30
+
31
+ context "without ts" do
32
+ before do
33
+ input.delete(:ts)
34
+ end
35
+ context '', &returns_valid_authorization_header
36
+ end
37
+
38
+ %w( method path host port ).each do |missing_option|
39
+ context "when missing #{missing_option} option" do
40
+ before do
41
+ input.delete(missing_option.to_sym)
42
+ end
43
+
44
+ it "raises MissingOptionError" do
45
+ expect { described_class.build_authorization_header(input) }.to raise_error(Hawk::AuthorizationHeader::MissingOptionError)
46
+ end
47
+ end
48
+ end
49
+
50
+ context "with invalid credentials" do
51
+ context "when missing id" do
52
+ before do
53
+ credentials.delete(:id)
54
+ end
55
+
56
+ it "raises InvalidCredentialsError" do
57
+ expect { described_class.build_authorization_header(input) }.to raise_error(Hawk::AuthorizationHeader::InvalidCredentialsError)
58
+ end
59
+ end
60
+
61
+ context "when missing key" do
62
+ before do
63
+ credentials.delete(:key)
64
+ end
65
+
66
+ it "raises InvalidCredentialsError" do
67
+ expect { described_class.build_authorization_header(input) }.to raise_error(Hawk::AuthorizationHeader::InvalidCredentialsError)
68
+ end
69
+ end
70
+
71
+ context "when missing algorithm" do
72
+ before do
73
+ credentials.delete(:algorithm)
74
+ end
75
+
76
+ it "raises InvalidCredentialsError" do
77
+ expect { described_class.build_authorization_header(input) }.to raise_error(Hawk::AuthorizationHeader::InvalidCredentialsError)
78
+ end
79
+ end
80
+
81
+ context "when invalid algorithm" do
82
+ before do
83
+ credentials[:algorithm] = 'foobar'
84
+ end
85
+
86
+ it "raises InvalidAlgorithmError" do
87
+ expect { described_class.build_authorization_header(input) }.to raise_error(Hawk::AuthorizationHeader::InvalidAlgorithmError)
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ shared_examples "an authorization header authenticator" do
94
+ context "with valid authorization header" do
95
+ it "returns credentials object" do
96
+ expect(described_class.authenticate(authorization_header, input)).to eql(credentials)
97
+ end
98
+
99
+ context "when hash present" do
100
+ let(:payload) { 'something to write about' }
101
+
102
+ it "returns credentials object" do
103
+ expect(described_class.authenticate(authorization_header, input)).to eql(credentials)
104
+ end
105
+ end
106
+
107
+ context "when ext present" do
108
+ let(:ext) { 'some random ext' }
109
+
110
+ it "returns credentials object" do
111
+ expect(described_class.authenticate(authorization_header, input)).to eql(credentials)
112
+ end
113
+ end
114
+ end
115
+
116
+ context "with invalid authorization header" do
117
+ context "when invalid mac" do
118
+ let(:expected_mac) { 'foobar' }
119
+
120
+ it "returns error object" do
121
+ actual = described_class.authenticate(authorization_header, input)
122
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
123
+ expect(actual.key).to eql(:mac)
124
+ expect(actual.message).to_not eql(nil)
125
+ end
126
+ end
127
+
128
+ context "when invalid hash" do
129
+ let(:expected_hash) { 'foobar' }
130
+ let(:payload) { 'baz' }
131
+
132
+ it "returns error object" do
133
+ actual = described_class.authenticate(authorization_header, input)
134
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
135
+ expect(actual.key).to eql(:hash)
136
+ expect(actual.message).to_not eql(nil)
137
+ end
138
+ end
139
+
140
+ context "when invalid ext" do
141
+ before do
142
+ client_input[:ext] = 'something else'
143
+ end
144
+
145
+ it "returns error object" do
146
+ actual = described_class.authenticate(authorization_header, input)
147
+ expect(actual).to be_a(Hawk::AuthenticationFailure)
148
+ expect(actual.key).to eql(:mac)
149
+ expect(actual.message).to_not eql(nil)
150
+ end
151
+ end
152
+ end
153
+ end
154
+