hawk-auth 0.0.0 → 0.1.0

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