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,54 @@
1
+ module Hawk
2
+ module Server
3
+ extend self
4
+
5
+ def authenticate(authorization_header, options)
6
+ Hawk::AuthorizationHeader.authenticate(authorization_header, options)
7
+ end
8
+
9
+ def authenticate_bewit(bewit, options)
10
+ padding = '=' * ((4 - bewit.size) % 4)
11
+ id, timestamp, mac, ext = Base64.decode64(bewit + padding).split('\\')
12
+
13
+ unless options[:credentials_lookup].respond_to?(:call) && (credentials = options[:credentials_lookup].call(id))
14
+ return AuthenticationFailure.new(:id, "Unidentified id")
15
+ end
16
+
17
+ if Time.at(timestamp.to_i) < Time.now
18
+ return AuthenticationFailure.new(:ts, "Stale timestamp")
19
+ end
20
+
21
+ expected_bewit = Crypto.bewit(
22
+ :credentials => credentials,
23
+ :host => options[:host],
24
+ :path => remove_bewit_param_from_path(options[:path]),
25
+ :port => options[:port],
26
+ :method => options[:method],
27
+ :ts => timestamp,
28
+ :ext => ext
29
+ )
30
+
31
+ unless expected_bewit == bewit
32
+ return AuthenticationFailure.new(:bewit, "Invalid signature")
33
+ end
34
+
35
+ credentials
36
+ end
37
+
38
+ def build_authorization_header(options)
39
+ Hawk::AuthorizationHeader.build(options, [:hash, :ext, :mac])
40
+ end
41
+
42
+ private
43
+
44
+ def remove_bewit_param_from_path(path)
45
+ path, query = path.split('?')
46
+ return path unless query
47
+ query, fragment = query.split('#')
48
+ query = query.split('&').reject { |i| i =~ /\Abewit=/ }.join('&')
49
+ path << "?#{query}" if query != ''
50
+ path << "#{fragment}" if fragment
51
+ path
52
+ end
53
+ end
54
+ end
data/lib/hawk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Hawk
2
- VERSION = "0.0.0"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hawk::AuthenticationFailure do
4
+ let(:algorithm) { "sha256" }
5
+ let(:credentials) do
6
+ {
7
+ :id => '123456',
8
+ :key => '2983d45yun89q',
9
+ :algorithm => algorithm
10
+ }
11
+ end
12
+
13
+ describe "#header" do
14
+ let(:instance) {
15
+ described_class.new(:mac, "Invalid mac", :credentials => credentials)
16
+ }
17
+
18
+ let(:timestamp) { Time.now.to_i }
19
+
20
+ let(:timestamp_mac) {
21
+ Hawk::Crypto.ts_mac({ :ts => timestamp, :credentials => credentials })
22
+ }
23
+
24
+ before do
25
+ now = Time.now
26
+ Time.stubs(:now).returns(now)
27
+ end
28
+
29
+ it "returns valid hawk authentication failure header" do
30
+ expect(instance.header).to eql(%(Hawk ts="#{timestamp}", tsm="#{timestamp_mac}", error="#{instance.message}"))
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+ require 'support/shared_examples/authorization_header'
3
+
4
+ describe Hawk::Client do
5
+
6
+ let(:credentials) do
7
+ {
8
+ :id => '123456',
9
+ :key => '2983d45yun89q',
10
+ :algorithm => algorithm
11
+ }
12
+ end
13
+
14
+ let(:timestamp) { Time.now.to_i }
15
+ let(:nonce) { 'Ygvqdz' }
16
+
17
+ describe ".authenticate" do
18
+ let(:payload) {}
19
+ let(:ext) {}
20
+
21
+ let(:input) do
22
+ _input = {
23
+ :method => 'POST',
24
+ :path => '/somewhere/over/the/rainbow',
25
+ :host => 'example.net',
26
+ :port => 80,
27
+ :content_type => 'text/plain',
28
+ :credentials => credentials,
29
+ :ts => timestamp,
30
+ :nonce => nonce
31
+ }
32
+ _input[:payload] = payload if payload
33
+ _input
34
+ end
35
+
36
+ let(:client_input) do
37
+ _input = input
38
+ _input[:ext] = ext if ext
39
+ _input
40
+ end
41
+
42
+ let(:expected_mac) { Hawk::Crypto.mac(client_input) }
43
+ let(:expected_hash) { client_input[:payload] ? Hawk::Crypto.hash(client_input) : nil }
44
+
45
+ let(:authorization_header) do
46
+ parts = []
47
+ parts << %(hash="#{expected_hash}") if expected_hash
48
+ parts << %(mac="#{expected_mac}")
49
+ parts << %(ext="#{ext}") if ext
50
+ "Hawk #{parts.join(', ')}"
51
+ end
52
+
53
+ shared_examples "an authorization response header authenticator" do
54
+ it_behaves_like "an authorization header authenticator"
55
+ end
56
+
57
+ context "when using sha256" do
58
+ let(:algorithm) { "sha256" }
59
+
60
+ it_behaves_like "an authorization response header authenticator"
61
+ end
62
+
63
+ context "when using sha1" do
64
+ let(:algorithm) { "sha1" }
65
+
66
+ it_behaves_like "an authorization response header authenticator"
67
+ end
68
+ end
69
+
70
+ describe ".build_authorization_header" do
71
+ before do
72
+ now = Time.now
73
+ Time.stubs(:now).returns(now)
74
+ end
75
+
76
+ let(:expected_mac) { Hawk::Crypto.mac(input) }
77
+ let(:expected_hash) { input[:payload] ? Hawk::Crypto.hash(input) : nil }
78
+
79
+ let(:input) do
80
+ _input = {
81
+ :credentials => credentials,
82
+ :ts => timestamp,
83
+ :method => 'POST',
84
+ :path => '/somewhere/over/the/rainbow',
85
+ :host => 'example.net',
86
+ :port => 80,
87
+ :payload => 'something to write about',
88
+ :ext => 'Bazinga!'
89
+ }
90
+ _input[:nonce] = nonce if nonce
91
+ _input
92
+ end
93
+
94
+ let(:expected_output_parts) do
95
+ parts = []
96
+ parts << %(id="#{credentials[:id]}")
97
+ parts << %(ts="#{timestamp}")
98
+ parts << %(nonce="#{input[:nonce]}")
99
+ parts << %(hash="#{expected_hash}") if input[:payload]
100
+ parts << %(ext="#{input[:ext]}") if input[:ext]
101
+ parts << %(mac="#{expected_mac}")
102
+ parts
103
+ end
104
+
105
+ let(:expected_output) do
106
+ "Hawk #{expected_output_parts.join(', ')}"
107
+ end
108
+
109
+ shared_examples "an authorization request header builder" do
110
+ it_behaves_like "an authorization header builder"
111
+
112
+ context "without nonce" do
113
+ let(:nonce) { nil }
114
+
115
+ it "generates a nonce" do
116
+ actual = described_class.build_authorization_header(input)
117
+ expect(actual).to match(%r{\bnonce="[^"]+"})
118
+ end
119
+ end
120
+ end
121
+
122
+ context "when using sha256" do
123
+ let(:algorithm) { "sha256" }
124
+
125
+ it_behaves_like "an authorization request header builder"
126
+ end
127
+
128
+ context "when using sha1" do
129
+ let(:algorithm) { "sha1" }
130
+
131
+ it_behaves_like "an authorization request header builder"
132
+ end
133
+ end
134
+
135
+ describe ".calculate_time_offset" do
136
+ let(:algorithm) { 'sha256' }
137
+ let(:timestamp) { 1365741469 }
138
+ let(:header) do
139
+ %(Hawk ts="#{timestamp}", tsm="#{timestamp_mac}", error="Some Error Message")
140
+ end
141
+
142
+ before do
143
+ Time.stubs(:now).returns(Time.at(timestamp + offset))
144
+ end
145
+
146
+ context "with valid timestamp mac" do
147
+ let(:timestamp_mac) { Hawk::Crypto.ts_mac(:ts => timestamp, :credentials => credentials) }
148
+
149
+ context "with positive offset" do
150
+ let(:offset) { -2030 }
151
+ it "returns time offset in seconds" do
152
+ expect(described_class.calculate_time_offset(header, :credentials => credentials)).to eql(offset * -1)
153
+ end
154
+ end
155
+
156
+ context "with negative offset" do
157
+ let(:offset) { 12345 }
158
+ it "returns time offset in seconds" do
159
+ expect(described_class.calculate_time_offset(header, :credentials => credentials)).to eql(offset * -1)
160
+ end
161
+ end
162
+ end
163
+
164
+ context "with invalid timestamp mac" do
165
+ let(:timestamp_mac) { "fooabr" }
166
+ let(:offset) { 1432 }
167
+
168
+ it "returns nil" do
169
+ expect(described_class.calculate_time_offset(header, :credentials => credentials)).to be_nil
170
+ end
171
+ end
172
+ end
173
+
174
+ end
@@ -0,0 +1,197 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hawk::Crypto do
4
+
5
+ let(:algorithm) { "sha256" }
6
+ let(:credentials) do
7
+ {
8
+ :id => '123456',
9
+ :key => '2983d45yun89q',
10
+ :algorithm => algorithm
11
+ }
12
+ end
13
+
14
+ describe "#hash" do
15
+ let(:hashing_method) { "hash" }
16
+
17
+ shared_examples "a payload hashing method" do
18
+ it "returns valid base64 encoded hash of payload" do
19
+ expect(described_class.send(hashing_method, input)).to eql(expected_output)
20
+ end
21
+ end
22
+
23
+ let(:input) do
24
+ {
25
+ :credentials => credentials,
26
+ :ts => 1353809207,
27
+ :nonce => 'Ygvqdz',
28
+ :method => 'POST',
29
+ :path => '/somewhere/over/the/rainbow',
30
+ :host => 'example.net',
31
+ :port => 80,
32
+ :payload => 'something to write about',
33
+ :ext => 'Bazinga!'
34
+ }
35
+ end
36
+
37
+ context "when using sha1 algorithm" do
38
+ let(:expected_output) { "bsvY3IfUllw6V5rvk4tStEvpBhE=" }
39
+ let(:algorithm) { "sha1" }
40
+
41
+ it_behaves_like "a payload hashing method"
42
+ end
43
+
44
+ context "when using sha256 algorithm" do
45
+ let(:expected_output) { "LjRmtkSKTW0ObTUyZ7N+vjClKd//KTTdfhF1M4XCuEM=" }
46
+
47
+ it_behaves_like "a payload hashing method"
48
+ end
49
+ end
50
+
51
+ shared_examples "a mac digest method" do
52
+ it "returns valid base64 encoded hmac" do
53
+ expect(described_class.send(mac_digest_method, input)).to eql(expected_output)
54
+ end
55
+ end
56
+
57
+ describe ".bewit" do
58
+ let(:mac_digest_method) { "bewit" }
59
+
60
+ context "when using sha256 algorithm" do
61
+ let(:input) do
62
+ {
63
+ :credentials => credentials,
64
+ :method => 'GET',
65
+ :path => '/resource/4?a=1&b=2',
66
+ :host => 'example.com',
67
+ :port => 80,
68
+ :ext => 'some-app-data',
69
+ :ttl => 60 * 60 * 24 * 365 * 100
70
+ }
71
+ end
72
+
73
+ let(:expected_output) {
74
+ "MTIzNDU2XDQ1MTkzMTE0NThcYkkwanFlS1prUHE0V1hRMmkxK0NrQ2lOanZEc3BSVkNGajlmbElqMXphWT1cc29tZS1hcHAtZGF0YQ"
75
+ }
76
+
77
+ before do
78
+ Time.stubs(:now).returns(Time.at(1365711458))
79
+ end
80
+
81
+ it_behaves_like "a mac digest method"
82
+ end
83
+ end
84
+
85
+ describe ".mac" do
86
+ let(:mac_digest_method) { "mac" }
87
+
88
+ let(:input) do
89
+ {
90
+ :credentials => credentials,
91
+ :ts => 1353809207,
92
+ :nonce => 'Ygvqdz',
93
+ :method => 'POST',
94
+ :path => '/somewhere/over/the/rainbow',
95
+ :host => 'example.net',
96
+ :port => 80,
97
+ :payload => 'something to write about',
98
+ :ext => 'Bazinga!'
99
+ }
100
+ end
101
+
102
+ context "when using sha1 algorithm" do
103
+ let(:expected_output) { "qbf1ZPG/r/e06F4ht+T77LXi5vw=" }
104
+ let(:algorithm) { "sha1" }
105
+
106
+ it_behaves_like "a mac digest method"
107
+ end
108
+
109
+ context "when using sha256 algorithm" do
110
+ let(:expected_output) { "dh5kEkotNusOuHPolRYUhvy2vlhJybTC2pqBdUQk5z0=" }
111
+
112
+ it_behaves_like "a mac digest method"
113
+ end
114
+ end
115
+
116
+ describe ".ts_mac" do
117
+ let(:input) do
118
+ {
119
+ :credentials => credentials,
120
+ :ts => 1365741469
121
+ }
122
+ end
123
+
124
+ it "returns valid timestamp mac" do
125
+ expect(described_class.ts_mac(input)).to eql("h/Ff6XI1euObD78ZNflapvLKXGuaw1RiLI4Q6Q5sAbM=")
126
+ end
127
+ end
128
+
129
+ describe ".normalized_string" do
130
+ let(:normalization_method) { "normalized_string" }
131
+
132
+ shared_examples "an input normalization method" do
133
+ it "returns a valid normalized string" do
134
+ expect(described_class.send(normalization_method, input)).to eql(expected_output)
135
+ end
136
+ end
137
+
138
+ let(:input) do
139
+ {
140
+ :ts => 1365701514,
141
+ :nonce => '5b4e',
142
+ :method => 'GET',
143
+ :path => "/path/to/foo?bar=baz",
144
+ :host => 'example.com',
145
+ :port => 8080
146
+ }
147
+ end
148
+
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)
151
+ end
152
+
153
+ it_behaves_like "an input normalization method"
154
+
155
+ context "with ext" do
156
+ let(:input) do
157
+ {
158
+ :ts => 1365701514,
159
+ :nonce => '5b4e',
160
+ :method => 'GET',
161
+ :path => '/path/to/foo?bar=baz',
162
+ :host => 'example.com',
163
+ :port => 8080,
164
+ :ext => 'this is some app data'
165
+ }
166
+ end
167
+
168
+ 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)
170
+ end
171
+
172
+ it_behaves_like "an input normalization method"
173
+ end
174
+
175
+ context "with payload and ext" do
176
+ let(:input) do
177
+ {
178
+ :ts => 1365701514,
179
+ :nonce => '5b4e',
180
+ :method => 'GET',
181
+ :path => '/path/to/foo?bar=baz',
182
+ :host => 'example.com',
183
+ :port => 8080,
184
+ :hash => 'U4MKKSmiVxk37JCCrAVIjV/OhB3y+NdwoCr6RShbVkE=',
185
+ :ext => 'this is some app data'
186
+ }
187
+ end
188
+
189
+ 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)
191
+ end
192
+
193
+ it_behaves_like "an input normalization method"
194
+ end
195
+ end
196
+
197
+ end