simple-api-auth 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 93fd0b3729fe8e56836fe64f413fe653ae220cee
4
- data.tar.gz: 6d34da98cfa5b789cbeea5b921b1b257c14bcf91
3
+ metadata.gz: eb9c0b2676ac9e4687ddb0800f62e421dcb1d4f9
4
+ data.tar.gz: 51ddeed3eece0be306e11bc32ef32c170948a479
5
5
  SHA512:
6
- metadata.gz: 286a1234f05dda3f6ade05dfd5fbdf35b43ca8a03c5821f94720c1150d941dd805b92e198de89a5cbd9871edd7230cbe0c171ea218da74855d69267789822f42
7
- data.tar.gz: c471d4bf302effaff92515fbfe17e5f4373f5dc59c91f8038eee359597729fd6d6c34bf4b8a8b761b887f05b8f812a010c53a436f685fc16e3ee5cbb8d449d9b
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 `ssa_key` and `ssa_secret` defined as strings
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 ssa_key: :resource_key_field, ssa_secret: :secret_token
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 `ssa_key`
55
- acts_as_api_authenticable auto_generate: :ssa_key
56
- # this will generate a after_initialize for both `ssa_key` and `ssa_secret`
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 `:ssa_key` and `:ssa_secret` even if you change the key column name.
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
- ssa_key = SimpleApiAuth.extract_key(request)
77
- secret_key = your_logic_to_get_secret_key(ssa_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 = { ssa_key: :ssa_key, ssa_secret: :ssa_secret, auto_generate: false }
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.ssa_options = ssa_options.merge(options)
9
+ self.saa_options = saa_options.merge(options)
10
10
  else
11
11
  extend ClassMethods
12
12
  include InstanceMethods
13
- self.ssa_options = SimpleApiAuth.config.make_model_options(options)
14
- ssa_options[:auto_generate].each do |field|
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 :ssa_options
21
+ attr_accessor :saa_options
22
22
 
23
23
  def api_authenticable?
24
24
  true
25
25
  end
26
26
 
27
- def ssa_authenticate(request)
27
+ def saa_authenticate(request)
28
28
  request = SimpleApiAuth::Request.create(request)
29
- entity = ssa_find(request)
29
+ entity = saa_find(request)
30
30
  return false if entity.nil?
31
- secret_key = entity.send(ssa_options[:ssa_secret])
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 ssa_find(request)
36
+ def saa_find(request)
37
37
  key = SimpleApiAuth.extract_key(request)
38
- find_by(ssa_options[:ssa_key] => key)
38
+ find_by(saa_options[:saa_key] => key)
39
39
  end
40
40
 
41
- def generate_ssa_key(options = {})
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?(ssa_options[:ssa_key] => key)
45
+ break key unless exists?(saa_options[:saa_key] => key)
46
46
  end
47
47
  end
48
48
 
49
- def generate_ssa_secret(options = {})
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 assign_ssa_key(options = {})
57
- assign_ssa(:ssa_key, options)
56
+ def assign_saa_key(options = {})
57
+ assign_saa(:saa_key, options)
58
58
  end
59
59
 
60
- def assign_ssa_secret(options = {})
61
- assign_ssa(:ssa_secret, options)
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 assign_ssa(field, options = {})
67
- key_name = self.class.ssa_options[field]
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 if !check_data(request) || too_old?(request)
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] ? [:ssa_key, :ssa_secret] : []
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
- key: :x_saa_key,
48
- time: :x_saa_auth_time,
49
- authorization: :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
- ssa_key: :ssa_key,
56
- ssa_secret: :ssa_secret,
56
+ saa_key: :saa_key,
57
+ saa_secret: :saa_secret,
57
58
  auto_generate: []
58
59
  }
59
60
  end
@@ -0,0 +1,4 @@
1
+ module SimpleApiAuth
2
+ class SigningError < StandardError
3
+ end
4
+ end
@@ -25,16 +25,30 @@ module SimpleApiAuth
25
25
 
26
26
  def check_data(request)
27
27
  required_headers.each do |k, _|
28
- return false unless request.headers.key?(k)
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 too_old?(request)
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 false if request_time.nil?
42
+ return log_and_fail('request time not found') if request_time.nil?
36
43
  difference = Time.now - request_time
37
- difference < 0 || difference > request_timeout
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
- module Request
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(s)
14
- s.to_s.downcase.gsub(/-/, '_').to_sym
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
- include SimpleApiAuth::Helpers::Request
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.each do |k, v|
9
- send("#{k}=", v)
10
- end
11
- self.headers = normalize_headers(headers)
12
- self.http_verb = normalize(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[:time]
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
- request
25
- else
26
- options = {}
27
- SimpleApiAuth.config.request_fields.each do |k, v|
28
- options[k] = request.send(v)
29
- end
30
- Request.new(options)
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
- signing_key = make_singing_key(request, secret_key)
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 make_singing_key(request, secret_key)
40
+ def make_signing_key(request, secret_key)
39
41
  date = request.time.strftime('%Y%m%d')
40
- hashed_date = hasher.hmac('ssa' + secret_key, date)
41
- hasher.hmac(hashed_date, 'ssa_request')
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)
@@ -1,6 +1,6 @@
1
1
  module SimpleApiAuth
2
2
  MAJOR = 0
3
3
  MINOR = 1
4
- PATCH = 1
4
+ PATCH = 2
5
5
  VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
6
6
  end
@@ -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[:key]]
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
- header_name = SimpleApiAuth.config.request_fields[:headers]
41
- request.send(header_name)[SimpleApiAuth.config.header_keys[:time]] = Time.now.utc.iso8601
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
- authorization_key = SimpleApiAuth.config.header_keys[:authorization]
44
- request.send(header_name)[authorization_key] = "Signature: #{signature}"
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 ssa_key: :ssa_key, ssa_secret: :ssa_secret
7
- acts_as_api_authenticable ssa_key: :ssa_key, ssa_secret: :overriden_secret
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
@@ -1,7 +1,7 @@
1
1
  ActiveRecord::Schema.define version: 0 do
2
2
  create_table :authenticable_models do |t|
3
- t.string :ssa_key
4
- t.string :ssa_secret
3
+ t.string :saa_key
4
+ t.string :saa_secret
5
5
  end
6
- add_index :authenticable_models, :ssa_key, unique: true
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(:ssa_options) }
8
- it { should respond_to(:ssa_authenticate) }
9
- it { should respond_to(:ssa_find) }
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.ssa_options[:ssa_key]).to eq(:ssa_key)
13
- expect(clazz.ssa_options[:ssa_secret]).to eq(:ssa_secret)
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(:ssa_options) }
19
+ it { should respond_to(:saa_options) }
20
20
  it 'should have overriden attributes' do
21
- expect(clazz.ssa_options[:ssa_secret]).to eq(:overriden_secret)
21
+ expect(clazz.saa_options[:saa_secret]).to eq(:overriden_secret)
22
22
  end
23
23
  end
24
24
 
25
- describe '#ssa_find' do
25
+ describe '#saa_find' do
26
26
  let(:request) { mock_request }
27
27
 
28
- subject { clazz.ssa_find(request) }
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(ssa_key: 'user_personal_key')
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 '#ssa_authenticate' do
40
+ describe '#saa_authenticate' do
41
41
  let(:request) { mock_request }
42
42
  before(:each) do
43
- request.headers[:authorization] = "Signature: #{mock_signature}"
43
+ request.headers[:http_authorization] = "Signature: #{mock_signature}"
44
44
  end
45
45
 
46
- subject { clazz.ssa_authenticate(request) }
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(ssa_key: 'user_personal_key', ssa_secret: 'something else')
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
- ssa_key: 'user_personal_key', ssa_secret: 'ultra_secret_key')
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
- { 'generate_ssa_key' => 5, 'generate_ssa_secret' => 64 }.each do |method, value|
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.new }
81
- its(:ssa_key) { should_not be_nil }
82
- its(:ssa_secret) { should_not be_nil }
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(:assign_ssa_key) }
90
- it { should respond_to(:assign_ssa_secret) }
91
- its(:ssa_key) { should be_nil }
92
- its(:ssa_secret) { should be_nil }
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
- [:assign_ssa_key, :assign_ssa_secret].each do |method|
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 'X-Saa-Key'
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[:x_saa_auth_time] = outdated_time.iso8601
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) { { ssa_key: :foobar } }
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[:ssa_key]).to eq(:foobar)
21
- expect(options[:ssa_secret]).to eq(:ssa_secret)
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] = :ssa_key
28
- expect(options[:auto_generate]).to eq([:ssa_key])
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([:ssa_key, :ssa_secret])
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] = [:ssa_secret]
43
- expect(options[:auto_generate]).to eq([:ssa_secret])
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(:headers) { normalize_headers(mock_headers) }
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[:authorization] = 'Signatur: foobar'
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 :authorization
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 '#too_old?' do
40
+ describe '#valid_time?' do
41
41
  it 'should allow recent enough requests' do
42
- expect(too_old?(request)).to be_falsy
42
+ expect(valid_time?(request)).to be_truthy
43
43
  end
44
44
 
45
45
  it 'should reject old requests' do
46
- request.headers[:x_saa_auth_time] = Time.new(2014, 11, 8).iso8601
47
- expect(too_old?(request)).to be_truthy
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(:request) { SimpleApiAuth::Request.create(rails_request) }
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[:x_saa_auth_time] = 'foobar'
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 :x_saa_auth_time
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(signer.sign(request, secret_key)).to eq(mock_signature)
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[:key] = :key
34
- request.headers[:key] = request.headers.delete(:x_saa_key)
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[:authorization] = "Signature: #{mock_signature}"
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
- authorization: 'Signature: 7c171d095fd65b7078afd13a6b3bd4ecfe596552',
64
- x_saa_auth_time: request_time.iso8601,
65
- x_saa_key: 'wedontreallycarehere'
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[:authorization] = "Signature: #{signature}"
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[:authorization] = "Signature: #{signature}"
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 :x_saa_auth_time
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[:x_saa_auth_time]).not_to be_nil
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
@@ -14,9 +14,9 @@ module SpecHelpers
14
14
 
15
15
  def mock_headers
16
16
  {
17
- 'Authorization' => 'Signature: dummy_signature',
18
- 'X-Saa-Auth-Time' => request_time.iso8601,
19
- 'X-Saa-Key' => 'user_personal_key'
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['X-Saa-Auth-Time'] + "\n" + mock_hashed_request
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("ssa#{mock_secret_key}:#{date}:ssa_request:#{mock_string_to_sign}")
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.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-09 00:00:00.000000000 Z
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/request_helpers.rb
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/request_helpers_spec.rb
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