simple-api-auth 0.1.1 → 0.1.2
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.
- checksums.yaml +4 -4
- data/README.md +21 -9
- data/lib/simple-api-auth/authenticable.rb +26 -18
- data/lib/simple-api-auth/authenticator.rb +1 -1
- data/lib/simple-api-auth/config.rb +8 -7
- data/lib/simple-api-auth/errors.rb +4 -0
- data/lib/simple-api-auth/helpers/auth_helpers.rb +19 -5
- data/lib/simple-api-auth/helpers/{request_helpers.rb → request_normalizer.rb} +7 -3
- data/lib/simple-api-auth/request.rb +26 -17
- data/lib/simple-api-auth/signer.rb +6 -4
- data/lib/simple-api-auth/version.rb +1 -1
- data/lib/simple-api-auth.rb +5 -6
- data/spec/internal/app/models/models.rb +2 -2
- data/spec/internal/db/schema.rb +3 -3
- data/spec/lib/simple-api-auth/authenticable_spec.rb +59 -24
- data/spec/lib/simple-api-auth/authenticator_spec.rb +2 -3
- data/spec/lib/simple-api-auth/config_spec.rb +8 -8
- data/spec/lib/simple-api-auth/helpers/auth_helpers_spec.rb +8 -8
- data/spec/lib/simple-api-auth/helpers/request_normalizer_spec.rb +45 -0
- data/spec/lib/simple-api-auth/request_spec.rb +25 -5
- data/spec/lib/simple-api-auth/signer_spec.rb +8 -1
- data/spec/lib/simple-api-auth_spec.rb +26 -10
- data/spec/support/mocks.rb +5 -5
- metadata +5 -4
- data/spec/lib/simple-api-auth/helpers/request_helpers_spec.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb9c0b2676ac9e4687ddb0800f62e421dcb1d4f9
|
4
|
+
data.tar.gz: 51ddeed3eece0be306e11bc32ef32c170948a479
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afb834f110b250256b1e94b57528f74bf297974a572257e3bbee3ac9a1e318108e117eea2afed6b5f2035e6d575c6b2b3eaa0f7565cebe141bfaece3971a408f
|
7
|
+
data.tar.gz: 3aab96642cb7fe5f1844f69be91640981503a4ea65fda225983e18f6e61023271542136f1863b937915685aff0729b9ad2df2ad55fd404e2900b47c7b453de7a
|
data/README.md
CHANGED
@@ -36,13 +36,13 @@ class User < ActiveRecord::Base
|
|
36
36
|
end
|
37
37
|
```
|
38
38
|
|
39
|
-
you will need to have `
|
39
|
+
you will need to have `saa_key` and `saa_secret` defined as strings
|
40
40
|
in your database for this to work. If you want to change the columns name,
|
41
41
|
you can pass them in option.
|
42
42
|
|
43
43
|
```ruby
|
44
44
|
class User < ActiveRecord::Base
|
45
|
-
acts_as_api_authenticable
|
45
|
+
acts_as_api_authenticable saa_key: :resource_key_field, saa_secret: :secret_token
|
46
46
|
end
|
47
47
|
```
|
48
48
|
|
@@ -51,14 +51,15 @@ to generate.
|
|
51
51
|
|
52
52
|
```ruby
|
53
53
|
class User < ActiveRecord::Base
|
54
|
-
# this will generate a after_initialize to assign `
|
55
|
-
acts_as_api_authenticable auto_generate: :
|
56
|
-
# this will generate a after_initialize for both `
|
54
|
+
# this will generate a after_initialize to assign `saa_key`
|
55
|
+
acts_as_api_authenticable auto_generate: :saa_key
|
56
|
+
# this will generate a after_initialize for both `saa_key` and `saa_secret`
|
57
|
+
# not that this will only work for new records
|
57
58
|
acts_as_api_authenticable auto_generate: true
|
58
59
|
end
|
59
60
|
```
|
60
61
|
|
61
|
-
Note that the keys for autogenerate should be `:
|
62
|
+
Note that the keys for autogenerate should be `:saa_key` and `:saa_secret` even if you change the key column name.
|
62
63
|
|
63
64
|
You can then use
|
64
65
|
|
@@ -73,8 +74,8 @@ this will return the user if the request is valid, or `nil` otherwise.
|
|
73
74
|
This gem can easily used without `ActiveRecord`.
|
74
75
|
|
75
76
|
```ruby
|
76
|
-
|
77
|
-
secret_key = your_logic_to_get_secret_key(
|
77
|
+
saa_key = SimpleApiAuth.extract_key(request)
|
78
|
+
secret_key = your_logic_to_get_secret_key(saa_key)
|
78
79
|
valid = SimpleApiAuth.valid_signature?(request, secret_key)
|
79
80
|
```
|
80
81
|
|
@@ -84,8 +85,13 @@ The library accepts the following configurations
|
|
84
85
|
|
85
86
|
```ruby
|
86
87
|
SimpleApiAuth.configure do |config|
|
88
|
+
# class used to transform headers key to :underscored_symbols and back
|
89
|
+
# the provided normalizer works for Rack requests
|
90
|
+
# override for other types of requests
|
91
|
+
request_normalizer = SimpleApiAuth::Helpers::RequestNormalizer
|
92
|
+
|
87
93
|
# values used as default with `acts_as_api_authenticable`
|
88
|
-
config.model_defaults = {
|
94
|
+
config.model_defaults = { saa_key: :saa_key, saa_secret: :saa_secret, auto_generate: false }
|
89
95
|
|
90
96
|
# the normalized keys for the HTTP headers
|
91
97
|
config.header_keys = { key: :x_saa_key, time: :x_saa_auth_time, authorization: :authorization
|
@@ -155,6 +161,12 @@ or directly sign the request with
|
|
155
161
|
SimpleApiAuth.sign!(request, secret_key)
|
156
162
|
```
|
157
163
|
|
164
|
+
With `ActiveRecord`, it can also be used the following way.
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
my_model.saa_sign!(request)
|
168
|
+
```
|
169
|
+
|
158
170
|
A JS client, with an AngularJS module intregrated with the `$http` service
|
159
171
|
is in progress and should be available soon (with a few weeks).
|
160
172
|
|
@@ -6,65 +6,73 @@ module SimpleApiAuth
|
|
6
6
|
|
7
7
|
def acts_as_api_authenticable(options = {})
|
8
8
|
if api_authenticable?
|
9
|
-
self.
|
9
|
+
self.saa_options = saa_options.merge(options)
|
10
10
|
else
|
11
11
|
extend ClassMethods
|
12
12
|
include InstanceMethods
|
13
|
-
self.
|
14
|
-
|
13
|
+
self.saa_options = SimpleApiAuth.config.make_model_options(options)
|
14
|
+
saa_options[:auto_generate].each do |field|
|
15
15
|
send(:after_initialize, "assign_#{field}")
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
20
|
module ClassMethods
|
21
|
-
attr_accessor :
|
21
|
+
attr_accessor :saa_options
|
22
22
|
|
23
23
|
def api_authenticable?
|
24
24
|
true
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
27
|
+
def saa_authenticate(request)
|
28
28
|
request = SimpleApiAuth::Request.create(request)
|
29
|
-
entity =
|
29
|
+
entity = saa_find(request)
|
30
30
|
return false if entity.nil?
|
31
|
-
secret_key = entity.send(
|
31
|
+
secret_key = entity.send(saa_options[:saa_secret])
|
32
32
|
return false unless SimpleApiAuth.valid_signature?(request, secret_key)
|
33
33
|
entity
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
36
|
+
def saa_find(request)
|
37
37
|
key = SimpleApiAuth.extract_key(request)
|
38
|
-
find_by(
|
38
|
+
find_by(saa_options[:saa_key] => key)
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
41
|
+
def generate_saa_key(options = {})
|
42
42
|
length = options[:length] || (Math.log(count + 1, 64) + 5)
|
43
43
|
loop do
|
44
44
|
key = SecureRandom.urlsafe_base64(length)
|
45
|
-
break key unless exists?(
|
45
|
+
break key unless exists?(saa_options[:saa_key] => key)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
49
|
+
def generate_saa_secret(options = {})
|
50
50
|
length = options[:length] || 64
|
51
51
|
SecureRandom.urlsafe_base64(length)
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
55
|
module InstanceMethods
|
56
|
-
def
|
57
|
-
|
56
|
+
def assign_saa_key(options = {})
|
57
|
+
assign_saa(:saa_key, options)
|
58
58
|
end
|
59
59
|
|
60
|
-
def
|
61
|
-
|
60
|
+
def assign_saa_secret(options = {})
|
61
|
+
assign_saa(:saa_secret, options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def saa_sign!(request)
|
65
|
+
request = SimpleApiAuth::Request.create(request)
|
66
|
+
key = send(self.class.saa_options[:saa_key])
|
67
|
+
request.add_header(SimpleApiAuth.config.header_keys[:saa_key], key)
|
68
|
+
SimpleApiAuth.sign!(request, send(self.class.saa_options[:saa_secret]))
|
62
69
|
end
|
63
70
|
|
64
71
|
private
|
65
72
|
|
66
|
-
def
|
67
|
-
|
73
|
+
def assign_saa(field, options = {})
|
74
|
+
return unless new_record? || options[:force]
|
75
|
+
key_name = self.class.saa_options[field]
|
68
76
|
send("#{key_name}=", self.class.send("generate_#{field}", options))
|
69
77
|
end
|
70
78
|
end
|
@@ -12,7 +12,7 @@ module SimpleApiAuth
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def valid_signature?
|
15
|
-
return false
|
15
|
+
return false unless check_data(request) && valid_time?(request)
|
16
16
|
signed_request = signer.sign(request, @secret_key)
|
17
17
|
SimpleApiAuth.log(Logger::DEBUG, "Signed request: #{signed_request}")
|
18
18
|
SimpleApiAuth.log(Logger::DEBUG, "User signature: #{signature}")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module SimpleApiAuth
|
2
2
|
class Config
|
3
|
-
attr_accessor :request_fields, :allowed_methods
|
3
|
+
attr_accessor :request_fields, :allowed_methods, :request_normalizer
|
4
4
|
attr_accessor :signer, :request_timeout, :required_headers, :hasher
|
5
5
|
attr_accessor :logger, :header_keys, :model_defaults
|
6
6
|
|
@@ -9,6 +9,7 @@ module SimpleApiAuth
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def reset!
|
12
|
+
self.request_normalizer = SimpleApiAuth::Helpers::RequestNormalizer
|
12
13
|
self.model_defaults = model_default_values
|
13
14
|
self.header_keys = default_header_keys
|
14
15
|
self.request_fields = default_request_fields
|
@@ -25,7 +26,7 @@ module SimpleApiAuth
|
|
25
26
|
if options[:auto_generate].is_a?(Symbol)
|
26
27
|
options[:auto_generate] = [options[:auto_generate]]
|
27
28
|
elsif !options[:auto_generate].is_a?(Array)
|
28
|
-
options[:auto_generate] = options[:auto_generate] ? [:
|
29
|
+
options[:auto_generate] = options[:auto_generate] ? [:saa_key, :saa_secret] : []
|
29
30
|
end
|
30
31
|
options
|
31
32
|
end
|
@@ -44,16 +45,16 @@ module SimpleApiAuth
|
|
44
45
|
|
45
46
|
def default_header_keys
|
46
47
|
{
|
47
|
-
|
48
|
-
|
49
|
-
authorization: :
|
48
|
+
saa_key: :http_x_saa_key,
|
49
|
+
saa_auth_time: :http_x_saa_auth_time,
|
50
|
+
authorization: :http_authorization
|
50
51
|
}
|
51
52
|
end
|
52
53
|
|
53
54
|
def model_default_values
|
54
55
|
{
|
55
|
-
|
56
|
-
|
56
|
+
saa_key: :saa_key,
|
57
|
+
saa_secret: :saa_secret,
|
57
58
|
auto_generate: []
|
58
59
|
}
|
59
60
|
end
|
@@ -25,16 +25,30 @@ module SimpleApiAuth
|
|
25
25
|
|
26
26
|
def check_data(request)
|
27
27
|
required_headers.each do |k, _|
|
28
|
-
return
|
28
|
+
return log_and_fail(missing_header_message(k)) unless request.headers.key?(k)
|
29
29
|
end
|
30
|
-
allowed_methods.include?(request.http_verb)
|
30
|
+
allowed_verb = allowed_methods.include?(request.http_verb)
|
31
|
+
return log_and_fail("verb #{request.http_verb} not allowed") unless allowed_verb
|
32
|
+
true
|
31
33
|
end
|
32
34
|
|
33
|
-
def
|
35
|
+
def missing_header_message(header_name)
|
36
|
+
available_headers = request.headers.keys.join(', ')
|
37
|
+
"missing header #{header_name}. available headers are: #{available_headers}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_time?(request)
|
34
41
|
request_time = request.time
|
35
|
-
return
|
42
|
+
return log_and_fail('request time not found') if request_time.nil?
|
36
43
|
difference = Time.now - request_time
|
37
|
-
|
44
|
+
return log_and_fail('negative time') if difference < 0
|
45
|
+
return log_and_fail('request too old') if difference > request_timeout
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def log_and_fail(message)
|
50
|
+
SimpleApiAuth.log(Logger::DEBUG, message)
|
51
|
+
false
|
38
52
|
end
|
39
53
|
|
40
54
|
def secure_equals?(m1, m2, key)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module SimpleApiAuth
|
2
2
|
module Helpers
|
3
|
-
|
3
|
+
class RequestNormalizer
|
4
4
|
def normalize_headers(headers)
|
5
5
|
normalized_headers = {}
|
6
6
|
headers.each do |key, value|
|
@@ -10,8 +10,12 @@ module SimpleApiAuth
|
|
10
10
|
normalized_headers
|
11
11
|
end
|
12
12
|
|
13
|
-
def normalize(
|
14
|
-
|
13
|
+
def normalize(key)
|
14
|
+
key.to_s.downcase.gsub(/-/, '_').to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def denormalize(key)
|
18
|
+
key.to_s.upcase
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
@@ -1,33 +1,42 @@
|
|
1
1
|
module SimpleApiAuth
|
2
2
|
class Request
|
3
|
-
|
4
|
-
|
5
|
-
attr_accessor :headers, :http_verb, :query_string, :uri, :body
|
3
|
+
attr_accessor :headers, :http_verb, :query_string, :uri, :body, :original
|
6
4
|
|
7
5
|
def initialize(options = {})
|
8
|
-
options
|
9
|
-
|
10
|
-
|
11
|
-
self.headers = normalize_headers(headers)
|
12
|
-
self.http_verb =
|
6
|
+
assign_options(options)
|
7
|
+
@normalizer = SimpleApiAuth.config.request_normalizer.new
|
8
|
+
@header_field = SimpleApiAuth.config.request_fields[:headers]
|
9
|
+
self.headers = @normalizer.normalize_headers(headers)
|
10
|
+
self.http_verb = http_verb.downcase.to_sym
|
13
11
|
end
|
14
12
|
|
15
13
|
def time
|
16
|
-
header_key = SimpleApiAuth.config.header_keys[:
|
14
|
+
header_key = SimpleApiAuth.config.header_keys[:saa_auth_time]
|
17
15
|
Time.parse(headers[header_key])
|
18
16
|
rescue ArgumentError, TypeError
|
19
17
|
nil
|
20
18
|
end
|
21
19
|
|
20
|
+
def add_header(key, value)
|
21
|
+
headers[key] = value
|
22
|
+
denormalized_key = @normalizer.denormalize(key)
|
23
|
+
original.send(@header_field)[denormalized_key] = value
|
24
|
+
end
|
25
|
+
|
22
26
|
def self.create(request)
|
23
|
-
if request.is_a?(Request)
|
24
|
-
|
25
|
-
|
26
|
-
options =
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
return request if request.is_a?(Request)
|
28
|
+
options = {}
|
29
|
+
SimpleApiAuth.config.request_fields.each do |k, v|
|
30
|
+
options[k] = request.send(v)
|
31
|
+
end
|
32
|
+
Request.new(options.merge(original: request))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def assign_options(options)
|
38
|
+
options.each do |k, v|
|
39
|
+
send("#{k}=", v)
|
31
40
|
end
|
32
41
|
end
|
33
42
|
end
|
@@ -8,7 +8,9 @@ module SimpleApiAuth
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def sign(request, secret_key)
|
11
|
-
|
11
|
+
fail SigningError, 'time header is not present' if request.time.nil?
|
12
|
+
|
13
|
+
signing_key = make_signing_key(request, secret_key)
|
12
14
|
SimpleApiAuth.log(Logger::DEBUG, "Signing key(hex): #{Digest.hexencode(signing_key)}")
|
13
15
|
|
14
16
|
string_to_sign = make_string_to_sign(request)
|
@@ -35,10 +37,10 @@ module SimpleApiAuth
|
|
35
37
|
|
36
38
|
private
|
37
39
|
|
38
|
-
def
|
40
|
+
def make_signing_key(request, secret_key)
|
39
41
|
date = request.time.strftime('%Y%m%d')
|
40
|
-
hashed_date = hasher.hmac('
|
41
|
-
hasher.hmac(hashed_date, '
|
42
|
+
hashed_date = hasher.hmac('saa' + secret_key, date)
|
43
|
+
hasher.hmac(hashed_date, 'saa_request')
|
42
44
|
end
|
43
45
|
|
44
46
|
def make_canonical_request(request)
|
data/lib/simple-api-auth.rb
CHANGED
@@ -22,7 +22,7 @@ module SimpleApiAuth
|
|
22
22
|
|
23
23
|
def self.extract_key(request)
|
24
24
|
request = SimpleApiAuth::Request.create(request)
|
25
|
-
request.headers[SimpleApiAuth.config.header_keys[:
|
25
|
+
request.headers[SimpleApiAuth.config.header_keys[:saa_key]]
|
26
26
|
end
|
27
27
|
|
28
28
|
def self.valid_signature?(request, secret_key, options = {})
|
@@ -37,12 +37,11 @@ module SimpleApiAuth
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.sign!(request, secret_key, options = {})
|
40
|
-
|
41
|
-
request.
|
40
|
+
request = SimpleApiAuth::Request.create(request)
|
41
|
+
request.add_header(SimpleApiAuth.config.header_keys[:saa_auth_time], Time.now.utc.iso8601)
|
42
42
|
signature = compute_signature(request, secret_key, options)
|
43
|
-
|
44
|
-
request.
|
45
|
-
request
|
43
|
+
request.add_header(SimpleApiAuth.config.header_keys[:authorization], "Signature: #{signature}")
|
44
|
+
request.original
|
46
45
|
end
|
47
46
|
end
|
48
47
|
|
@@ -3,8 +3,8 @@ class AuthenticableModel < ActiveRecord::Base
|
|
3
3
|
end
|
4
4
|
|
5
5
|
class OverriddenAuthenticableModel < ActiveRecord::Base
|
6
|
-
acts_as_api_authenticable
|
7
|
-
acts_as_api_authenticable
|
6
|
+
acts_as_api_authenticable saa_key: :saa_key, saa_secret: :saa_secret
|
7
|
+
acts_as_api_authenticable saa_key: :saa_key, saa_secret: :overriden_secret
|
8
8
|
end
|
9
9
|
|
10
10
|
class AuthenticableModelWithHooks < ActiveRecord::Base
|
data/spec/internal/db/schema.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
ActiveRecord::Schema.define version: 0 do
|
2
2
|
create_table :authenticable_models do |t|
|
3
|
-
t.string :
|
4
|
-
t.string :
|
3
|
+
t.string :saa_key
|
4
|
+
t.string :saa_secret
|
5
5
|
end
|
6
|
-
add_index :authenticable_models, :
|
6
|
+
add_index :authenticable_models, :saa_key, unique: true
|
7
7
|
end
|
@@ -4,64 +4,64 @@ describe SimpleApiAuth do
|
|
4
4
|
describe 'class extensions' do
|
5
5
|
subject(:clazz) { AuthenticableModel }
|
6
6
|
|
7
|
-
it { should respond_to(:
|
8
|
-
it { should respond_to(:
|
9
|
-
it { should respond_to(:
|
7
|
+
it { should respond_to(:saa_options) }
|
8
|
+
it { should respond_to(:saa_authenticate) }
|
9
|
+
it { should respond_to(:saa_find) }
|
10
10
|
|
11
11
|
it 'should set options' do
|
12
|
-
expect(clazz.
|
13
|
-
expect(clazz.
|
12
|
+
expect(clazz.saa_options[:saa_key]).to eq(:saa_key)
|
13
|
+
expect(clazz.saa_options[:saa_secret]).to eq(:saa_secret)
|
14
14
|
end
|
15
15
|
|
16
16
|
context 'when included several times' do
|
17
17
|
subject(:clazz) { OverriddenAuthenticableModel }
|
18
18
|
|
19
|
-
it { should respond_to(:
|
19
|
+
it { should respond_to(:saa_options) }
|
20
20
|
it 'should have overriden attributes' do
|
21
|
-
expect(clazz.
|
21
|
+
expect(clazz.saa_options[:saa_secret]).to eq(:overriden_secret)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
describe '#
|
25
|
+
describe '#saa_find' do
|
26
26
|
let(:request) { mock_request }
|
27
27
|
|
28
|
-
subject { clazz.
|
28
|
+
subject { clazz.saa_find(request) }
|
29
29
|
|
30
30
|
it 'should return nil when not found' do
|
31
31
|
expect(subject).to be_nil
|
32
32
|
end
|
33
33
|
|
34
34
|
it 'should return entity when present' do
|
35
|
-
entity = AuthenticableModel.create(
|
35
|
+
entity = AuthenticableModel.create(saa_key: 'user_personal_key')
|
36
36
|
expect(subject.id).to eq(entity.id)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
describe '#
|
40
|
+
describe '#saa_authenticate' do
|
41
41
|
let(:request) { mock_request }
|
42
42
|
before(:each) do
|
43
|
-
request.headers[:
|
43
|
+
request.headers[:http_authorization] = "Signature: #{mock_signature}"
|
44
44
|
end
|
45
45
|
|
46
|
-
subject { clazz.
|
46
|
+
subject { clazz.saa_authenticate(request) }
|
47
47
|
|
48
48
|
it 'should return false when the entity does not exists' do
|
49
49
|
expect(subject).to be false
|
50
50
|
end
|
51
51
|
|
52
52
|
it 'should return false when the secret key does not match' do
|
53
|
-
clazz.create(
|
53
|
+
clazz.create(saa_key: 'user_personal_key', saa_secret: 'something else')
|
54
54
|
expect(subject).to be false
|
55
55
|
end
|
56
56
|
|
57
57
|
it 'should return the resource when signature matches' do
|
58
58
|
entity = clazz.create(
|
59
|
-
|
59
|
+
saa_key: 'user_personal_key', saa_secret: 'ultra_secret_key')
|
60
60
|
expect(subject.id).to eq(entity.id)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
{ '
|
64
|
+
{ 'generate_saa_key' => 5, 'generate_saa_secret' => 64 }.each do |method, value|
|
65
65
|
describe "##{method}" do
|
66
66
|
let(:options) { {} }
|
67
67
|
let(:key) { clazz.send(method.to_sym, options) }
|
@@ -77,21 +77,30 @@ describe SimpleApiAuth do
|
|
77
77
|
end
|
78
78
|
|
79
79
|
describe 'hooks' do
|
80
|
-
subject { AuthenticableModelWithHooks.
|
81
|
-
its(:
|
82
|
-
its(:
|
80
|
+
subject(:model) { AuthenticableModelWithHooks.create }
|
81
|
+
its(:saa_key) { should_not be_nil }
|
82
|
+
its(:saa_secret) { should_not be_nil }
|
83
|
+
|
84
|
+
context 'with existing model' do
|
85
|
+
subject { AuthenticableModelWithHooks.find(model.id) }
|
86
|
+
it 'should not override keys' do
|
87
|
+
expect(subject.saa_key).to eq(model.saa_key)
|
88
|
+
expect(subject.saa_secret).to eq(model.saa_secret)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
83
92
|
end
|
84
93
|
end
|
85
94
|
|
86
95
|
describe 'instance extension' do
|
87
96
|
subject(:model) { AuthenticableModel.new }
|
88
97
|
|
89
|
-
it { should respond_to(:
|
90
|
-
it { should respond_to(:
|
91
|
-
its(:
|
92
|
-
its(:
|
98
|
+
it { should respond_to(:assign_saa_key) }
|
99
|
+
it { should respond_to(:assign_saa_secret) }
|
100
|
+
its(:saa_key) { should be_nil }
|
101
|
+
its(:saa_secret) { should be_nil }
|
93
102
|
|
94
|
-
[:
|
103
|
+
[:assign_saa_key, :assign_saa_secret].each do |method|
|
95
104
|
field_name = method.to_s.gsub('assign_', '')
|
96
105
|
describe "##{method}" do
|
97
106
|
it "should assign #{field_name}" do
|
@@ -101,6 +110,32 @@ describe SimpleApiAuth do
|
|
101
110
|
end
|
102
111
|
end
|
103
112
|
end
|
113
|
+
|
114
|
+
describe '#saa_sign!' do
|
115
|
+
let!(:model) { AuthenticableModelWithHooks.create }
|
116
|
+
let(:request) { mock_request }
|
117
|
+
before(:each) { model.saa_sign!(request) }
|
118
|
+
|
119
|
+
it 'should add time headers' do
|
120
|
+
expect(request.headers[:http_x_saa_auth_time]).not_to be_nil
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should add key header' do
|
124
|
+
expect(request.headers[:http_x_saa_key]).to eq(model.saa_key)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should add authorization' do
|
128
|
+
expect(request.headers[:http_authorization]).not_to be_nil
|
129
|
+
expected = "Signature: #{SimpleApiAuth.compute_signature(request, model.saa_secret)}"
|
130
|
+
expect(request.headers[:http_authorization]).to eq(expected)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should sign request' do
|
134
|
+
fetched = AuthenticableModelWithHooks.saa_authenticate(request)
|
135
|
+
expect(fetched).not_to be_falsy
|
136
|
+
expect(fetched.id).to eq(model.id)
|
137
|
+
end
|
138
|
+
end
|
104
139
|
end
|
105
140
|
end
|
106
141
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
describe SimpleApiAuth do
|
2
2
|
describe SimpleApiAuth::Authenticator do
|
3
|
-
include SimpleApiAuth::Helpers::Request
|
4
3
|
|
5
4
|
let(:dummy_headers) { mock_headers }
|
6
5
|
|
@@ -16,12 +15,12 @@ describe SimpleApiAuth do
|
|
16
15
|
|
17
16
|
describe '#valid_signature?' do
|
18
17
|
it 'should fail on missing header' do
|
19
|
-
dummy_headers.delete '
|
18
|
+
dummy_headers.delete 'HTTP_X_SAA_KEY'
|
20
19
|
expect(authenticator.valid_signature?).to be_falsy
|
21
20
|
end
|
22
21
|
|
23
22
|
it 'should fail on outdated request' do
|
24
|
-
dummy_headers[:
|
23
|
+
dummy_headers[:http_x_saa_auth_time] = outdated_time.iso8601
|
25
24
|
expect(authenticator.valid_signature?).to be_falsy
|
26
25
|
end
|
27
26
|
|
@@ -13,24 +13,24 @@ describe SimpleApiAuth do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
describe 'make_model_options' do
|
16
|
-
let(:model_options) { {
|
16
|
+
let(:model_options) { { saa_key: :foobar } }
|
17
17
|
subject(:options) { config.make_model_options(model_options) }
|
18
18
|
|
19
19
|
it 'should merge options' do
|
20
|
-
expect(options[:
|
21
|
-
expect(options[:
|
20
|
+
expect(options[:saa_key]).to eq(:foobar)
|
21
|
+
expect(options[:saa_secret]).to eq(:saa_secret)
|
22
22
|
expect(options[:auto_generate]).to eq([])
|
23
23
|
end
|
24
24
|
|
25
25
|
describe 'auto_generate configuration' do
|
26
26
|
it 'should handle symbol' do
|
27
|
-
model_options[:auto_generate] = :
|
28
|
-
expect(options[:auto_generate]).to eq([:
|
27
|
+
model_options[:auto_generate] = :saa_key
|
28
|
+
expect(options[:auto_generate]).to eq([:saa_key])
|
29
29
|
end
|
30
30
|
|
31
31
|
it 'should handle true' do
|
32
32
|
model_options[:auto_generate] = true
|
33
|
-
expect(options[:auto_generate]).to eq([:
|
33
|
+
expect(options[:auto_generate]).to eq([:saa_key, :saa_secret])
|
34
34
|
end
|
35
35
|
|
36
36
|
it 'should handle false' do
|
@@ -39,8 +39,8 @@ describe SimpleApiAuth do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
it 'should handle arrays' do
|
42
|
-
model_options[:auto_generate] = [:
|
43
|
-
expect(options[:auto_generate]).to eq([:
|
42
|
+
model_options[:auto_generate] = [:saa_secret]
|
43
|
+
expect(options[:auto_generate]).to eq([:saa_secret])
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
describe SimpleApiAuth do
|
2
2
|
describe SimpleApiAuth::Helpers::Auth do
|
3
|
-
include SimpleApiAuth::Helpers::Request
|
4
3
|
include SimpleApiAuth::Helpers::Auth
|
5
4
|
|
6
|
-
let(:
|
5
|
+
let(:normalizer) { SimpleApiAuth::Helpers::RequestNormalizer.new }
|
6
|
+
let(:headers) { normalizer.normalize_headers(mock_headers) }
|
7
7
|
let(:request) { SimpleApiAuth::Request.create(rails_request) }
|
8
8
|
|
9
9
|
describe '#extract_signature' do
|
@@ -12,7 +12,7 @@ describe SimpleApiAuth do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it 'should return nil otherwise' do
|
15
|
-
headers[:
|
15
|
+
headers[:http_authorization] = 'Signatur: foobar'
|
16
16
|
expect(extract_signature(headers)).to be_nil
|
17
17
|
end
|
18
18
|
end
|
@@ -32,19 +32,19 @@ describe SimpleApiAuth do
|
|
32
32
|
end
|
33
33
|
|
34
34
|
it 'should fail when header is missing' do
|
35
|
-
request.headers.delete :
|
35
|
+
request.headers.delete :http_authorization
|
36
36
|
expect(check_data(request)).to be_falsy
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
describe '#
|
40
|
+
describe '#valid_time?' do
|
41
41
|
it 'should allow recent enough requests' do
|
42
|
-
expect(
|
42
|
+
expect(valid_time?(request)).to be_truthy
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'should reject old requests' do
|
46
|
-
request.headers[:
|
47
|
-
expect(
|
46
|
+
request.headers[:http_x_saa_auth_time] = Time.new(2014, 11, 8).iso8601
|
47
|
+
expect(valid_time?(request)).to be_falsy
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
describe SimpleApiAuth do
|
2
|
+
describe SimpleApiAuth::Helpers::RequestNormalizer do
|
3
|
+
let(:normalizer) { SimpleApiAuth::Helpers::RequestNormalizer.new }
|
4
|
+
|
5
|
+
describe '#normalize_headers' do
|
6
|
+
let(:raw_headers) { mock_headers }
|
7
|
+
let(:headers) { normalizer.normalize_headers(raw_headers) }
|
8
|
+
|
9
|
+
it 'should normalize headers keys' do
|
10
|
+
[:http_authorization, :http_x_saa_auth_time, :http_x_saa_key].each do |key|
|
11
|
+
expect(headers).to include(key)
|
12
|
+
end
|
13
|
+
expect(headers.values).to eq(raw_headers.values)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#normalize' do
|
18
|
+
it 'should symbolize keys' do
|
19
|
+
expect(normalizer.normalize('foo')).to eq(:foo)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should work with symbols' do
|
23
|
+
expect(normalizer.normalize(:foo)).to eq(:foo)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should downcase' do
|
27
|
+
expect(normalizer.normalize('FOO')).to eq(:foo)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should replace hyphens' do
|
31
|
+
expect(normalizer.normalize('foo-bar')).to eq(:foo_bar)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should work with more complex keys' do
|
35
|
+
expect(normalizer.normalize('FOO-BAR-BAZ')).to eq(:foo_bar_baz)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#denormalize' do
|
40
|
+
it 'should return capitalized underscored values' do
|
41
|
+
expect(normalizer.denormalize(:foo_bar)).to eq('FOO_BAR')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,20 +1,26 @@
|
|
1
1
|
describe SimpleApiAuth do
|
2
2
|
describe SimpleApiAuth::Request do
|
3
|
-
include SimpleApiAuth::Helpers::Request
|
4
3
|
|
5
4
|
let(:base_request) { SimpleApiAuth::Request.create(rails_request) }
|
5
|
+
let(:normalizer) { SimpleApiAuth::Helpers::RequestNormalizer.new }
|
6
6
|
|
7
7
|
def check_request(request)
|
8
|
-
expect(request.headers).to eq(normalize_headers(mock_headers))
|
8
|
+
expect(request.headers).to eq(normalizer.normalize_headers(mock_headers))
|
9
9
|
expect(request.http_verb).to eq(:get)
|
10
10
|
end
|
11
11
|
|
12
12
|
describe '#initialize' do
|
13
|
-
let(:
|
13
|
+
let(:rails_req) { rails_request }
|
14
|
+
let(:request) { SimpleApiAuth::Request.create(rails_req) }
|
14
15
|
|
15
16
|
it 'should set headers and http verb' do
|
16
17
|
check_request(request)
|
17
18
|
end
|
19
|
+
|
20
|
+
it 'should work with added headers' do
|
21
|
+
rails_req.headers['RANDOM_HEADER'] = 'foobar'
|
22
|
+
expect(request.headers).to include(:random_header)
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
26
|
describe '#create' do
|
@@ -41,14 +47,28 @@ describe SimpleApiAuth do
|
|
41
47
|
end
|
42
48
|
|
43
49
|
it 'should return nil on wrong time' do
|
44
|
-
base_request.headers[:
|
50
|
+
base_request.headers[:http_x_saa_auth_time] = 'foobar'
|
45
51
|
expect(base_request.time).to be_nil
|
46
52
|
end
|
47
53
|
|
48
54
|
it 'should return nil on empty time' do
|
49
|
-
base_request.headers.delete :
|
55
|
+
base_request.headers.delete :http_x_saa_auth_time
|
50
56
|
expect(base_request.time).to be_nil
|
51
57
|
end
|
52
58
|
end
|
59
|
+
|
60
|
+
describe '#add_header' do
|
61
|
+
let(:base_request) { rails_request }
|
62
|
+
let(:request) { SimpleApiAuth::Request.create(base_request) }
|
63
|
+
before(:each) { request.add_header(:http_foo_bar, 'foobar') }
|
64
|
+
|
65
|
+
it 'should add header to request' do
|
66
|
+
expect(request.headers[:http_foo_bar]).to eq('foobar')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should add header to base request' do
|
70
|
+
expect(base_request.headers['HTTP_FOO_BAR']).to eq('foobar')
|
71
|
+
end
|
72
|
+
end
|
53
73
|
end
|
54
74
|
end
|
@@ -29,8 +29,15 @@ describe SimpleApiAuth do
|
|
29
29
|
|
30
30
|
describe '#sign' do
|
31
31
|
let(:secret_key) { 'ultra_secret_key' }
|
32
|
+
subject { signer.sign(request, secret_key) }
|
33
|
+
|
32
34
|
it 'should generate correct signature' do
|
33
|
-
expect(
|
35
|
+
expect(subject).to eq(mock_signature)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should fail when time is not present' do
|
39
|
+
request.headers.delete :http_x_saa_auth_time
|
40
|
+
expect { subject }.to raise_error(SimpleApiAuth::SigningError)
|
34
41
|
end
|
35
42
|
end
|
36
43
|
end
|
@@ -30,8 +30,8 @@ describe SimpleApiAuth do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
it 'should work with custom headers' do
|
33
|
-
SimpleApiAuth.config.header_keys[:
|
34
|
-
request.headers[:key] = request.headers.delete(:
|
33
|
+
SimpleApiAuth.config.header_keys[:saa_key] = :key
|
34
|
+
request.headers[:key] = request.headers.delete(:http_x_saa_key)
|
35
35
|
expect(subject).to eq('user_personal_key')
|
36
36
|
end
|
37
37
|
end
|
@@ -45,7 +45,7 @@ describe SimpleApiAuth do
|
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'should succeed with correct signature' do
|
48
|
-
request.headers[:
|
48
|
+
request.headers[:http_authorization] = "Signature: #{mock_signature}"
|
49
49
|
expect(result).to be_truthy
|
50
50
|
end
|
51
51
|
|
@@ -60,9 +60,9 @@ describe SimpleApiAuth do
|
|
60
60
|
query_string: 'foo=bar&bar=qux',
|
61
61
|
body: StringIO.new('somerandombody'),
|
62
62
|
headers: {
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
http_authorization: 'Signature: 6990ae83450457f6e1ca4a64b078751b12e8d429',
|
64
|
+
http_x_saa_auth_time: request_time.iso8601,
|
65
|
+
http_x_saa_key: 'wedontreallycarehere'
|
66
66
|
}
|
67
67
|
)
|
68
68
|
end
|
@@ -87,14 +87,14 @@ describe SimpleApiAuth do
|
|
87
87
|
let(:signature) { SimpleApiAuth.compute_signature(request, secret_key) }
|
88
88
|
|
89
89
|
it 'should return correct signature' do
|
90
|
-
request.headers[:
|
90
|
+
request.headers[:http_authorization] = "Signature: #{signature}"
|
91
91
|
expect(subject).to be_truthy
|
92
92
|
end
|
93
93
|
|
94
94
|
context 'with a different key' do
|
95
95
|
let(:secret_key) { 'another cool key' }
|
96
96
|
it 'should not return the same signature' do
|
97
|
-
request.headers[:
|
97
|
+
request.headers[:http_authorization] = "Signature: #{signature}"
|
98
98
|
expect(SimpleApiAuth.valid_signature?(request, base_secret)).to be_falsy
|
99
99
|
end
|
100
100
|
end
|
@@ -107,10 +107,26 @@ describe SimpleApiAuth do
|
|
107
107
|
end
|
108
108
|
|
109
109
|
it 'should add time header' do
|
110
|
-
request.headers.delete
|
110
|
+
request.headers.delete 'HTTP_X_SAA_AUTH_TIME'
|
111
111
|
SimpleApiAuth.sign!(request, secret_key)
|
112
112
|
expect(subject).to be_truthy
|
113
|
-
expect(request.headers[:
|
113
|
+
expect(request.headers[:http_x_saa_auth_time]).not_to be_nil
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'with rails request' do
|
117
|
+
let(:request) { rails_request }
|
118
|
+
|
119
|
+
it 'should work' do
|
120
|
+
SimpleApiAuth.sign!(request, secret_key)
|
121
|
+
expect(subject).to be_truthy
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should add headers with prefix' do
|
125
|
+
request.headers.delete 'HTTP_X_SAA_AUTH_TIME'
|
126
|
+
SimpleApiAuth.sign!(request, secret_key)
|
127
|
+
expect(subject).to be_truthy
|
128
|
+
expect(request.headers['HTTP_X_SAA_AUTH_TIME']).not_to be_nil
|
129
|
+
end
|
114
130
|
end
|
115
131
|
end
|
116
132
|
end
|
data/spec/support/mocks.rb
CHANGED
@@ -14,9 +14,9 @@ module SpecHelpers
|
|
14
14
|
|
15
15
|
def mock_headers
|
16
16
|
{
|
17
|
-
'
|
18
|
-
'
|
19
|
-
'
|
17
|
+
'HTTP_AUTHORIZATION' => 'Signature: dummy_signature',
|
18
|
+
'HTTP_X_SAA_AUTH_TIME' => request_time.iso8601,
|
19
|
+
'HTTP_X_SAA_KEY' => 'user_personal_key'
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
@@ -32,12 +32,12 @@ module SpecHelpers
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def mock_string_to_sign
|
35
|
-
mock_headers['
|
35
|
+
mock_headers['HTTP_X_SAA_AUTH_TIME'] + "\n" + mock_hashed_request
|
36
36
|
end
|
37
37
|
|
38
38
|
def mock_signature
|
39
39
|
date = mock_request.time.strftime('%Y%m%d')
|
40
|
-
Digest.hexencode("
|
40
|
+
Digest.hexencode("saa#{mock_secret_key}:#{date}:saa_request:#{mock_string_to_sign}")
|
41
41
|
end
|
42
42
|
|
43
43
|
def setup_dummy_signer
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple-api-auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Perez
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-11-
|
11
|
+
date: 2014-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -135,9 +135,10 @@ files:
|
|
135
135
|
- lib/simple-api-auth/authenticable.rb
|
136
136
|
- lib/simple-api-auth/authenticator.rb
|
137
137
|
- lib/simple-api-auth/config.rb
|
138
|
+
- lib/simple-api-auth/errors.rb
|
138
139
|
- lib/simple-api-auth/hashers/sha1_hasher.rb
|
139
140
|
- lib/simple-api-auth/helpers/auth_helpers.rb
|
140
|
-
- lib/simple-api-auth/helpers/
|
141
|
+
- lib/simple-api-auth/helpers/request_normalizer.rb
|
141
142
|
- lib/simple-api-auth/request.rb
|
142
143
|
- lib/simple-api-auth/signer.rb
|
143
144
|
- lib/simple-api-auth/version.rb
|
@@ -150,7 +151,7 @@ files:
|
|
150
151
|
- spec/lib/simple-api-auth/authenticator_spec.rb
|
151
152
|
- spec/lib/simple-api-auth/config_spec.rb
|
152
153
|
- spec/lib/simple-api-auth/helpers/auth_helpers_spec.rb
|
153
|
-
- spec/lib/simple-api-auth/helpers/
|
154
|
+
- spec/lib/simple-api-auth/helpers/request_normalizer_spec.rb
|
154
155
|
- spec/lib/simple-api-auth/request_spec.rb
|
155
156
|
- spec/lib/simple-api-auth/signer_spec.rb
|
156
157
|
- spec/lib/simple-api-auth_spec.rb
|
@@ -1,39 +0,0 @@
|
|
1
|
-
describe SimpleApiAuth do
|
2
|
-
describe SimpleApiAuth::Helpers::Request do
|
3
|
-
include SimpleApiAuth::Helpers::Request
|
4
|
-
|
5
|
-
describe '#normalize_headers' do
|
6
|
-
let(:raw_headers) { mock_headers }
|
7
|
-
let(:headers) { normalize_headers(raw_headers) }
|
8
|
-
|
9
|
-
it 'should normalize headers keys' do
|
10
|
-
[:authorization, :x_saa_auth_time, :x_saa_key].each do |key|
|
11
|
-
expect(headers).to include(key)
|
12
|
-
end
|
13
|
-
expect(headers.values).to eq(raw_headers.values)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
describe '#normalize' do
|
18
|
-
it 'should symbolize keys' do
|
19
|
-
expect(normalize('foo')).to eq(:foo)
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'should work with symbols' do
|
23
|
-
expect(normalize(:foo)).to eq(:foo)
|
24
|
-
end
|
25
|
-
|
26
|
-
it 'should downcase' do
|
27
|
-
expect(normalize('FOO')).to eq(:foo)
|
28
|
-
end
|
29
|
-
|
30
|
-
it 'should replace hyphens' do
|
31
|
-
expect(normalize('foo-bar')).to eq(:foo_bar)
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'should work with more complex keys' do
|
35
|
-
expect(normalize('FOO-BAR-BAZ')).to eq(:foo_bar_baz)
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|