simple-api-auth 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +12 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +169 -0
- data/Rakefile +7 -0
- data/lib/simple-api-auth/authenticable.rb +72 -0
- data/lib/simple-api-auth/authenticator.rb +32 -0
- data/lib/simple-api-auth/config.rb +61 -0
- data/lib/simple-api-auth/hashers/sha1_hasher.rb +14 -0
- data/lib/simple-api-auth/helpers/auth_helpers.rb +49 -0
- data/lib/simple-api-auth/helpers/request_helpers.rb +18 -0
- data/lib/simple-api-auth/request.rb +34 -0
- data/lib/simple-api-auth/signer.rb +53 -0
- data/lib/simple-api-auth/version.rb +6 -0
- data/lib/simple-api-auth.rb +47 -0
- data/simple-api-auth.gemspec +25 -0
- data/spec/extensions/string_spec.rb +13 -0
- data/spec/internal/app/models/models.rb +13 -0
- data/spec/internal/config/database.yml +4 -0
- data/spec/internal/db/schema.rb +7 -0
- data/spec/lib/simple-api-auth/authenticable_spec.rb +106 -0
- data/spec/lib/simple-api-auth/authenticator_spec.rb +35 -0
- data/spec/lib/simple-api-auth/config_spec.rb +48 -0
- data/spec/lib/simple-api-auth/helpers/auth_helpers_spec.rb +61 -0
- data/spec/lib/simple-api-auth/helpers/request_helpers_spec.rb +39 -0
- data/spec/lib/simple-api-auth/request_spec.rb +49 -0
- data/spec/lib/simple-api-auth/signer_spec.rb +37 -0
- data/spec/lib/simple-api-auth_spec.rb +110 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/auth.rb +13 -0
- data/spec/support/database.rb +14 -0
- data/spec/support/database_cleaner.rb +19 -0
- data/spec/support/extensions.rb +10 -0
- data/spec/support/mocks.rb +67 -0
- data/spec/support/requests.rb +51 -0
- metadata +189 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4b66b03e6dde55a63109efefcf8571850b7c471e
|
4
|
+
data.tar.gz: 8409349483eea331a07698ccf8c25de59ad14fb4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1696e31b7c4af412db8382b66ed70727422dfdb0c6a2225331f96c9d94eb4dc4e35cd04dde3e970963aee49545947b834765d2557c8663cce24f5ba80ed81119
|
7
|
+
data.tar.gz: 2123dd8a3fe6c5eac4c430e1ac80594d18596d7fe0df9eaf8ecddd7bfcbc8c29d88c96b4698fffa4fca2a4e8dae975ce07519aae464cbf17f153ef17161b980d
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.4
|
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
language: ruby
|
2
|
+
cache: bundler
|
3
|
+
rvm:
|
4
|
+
- 1.9.3-p484
|
5
|
+
- 2.0.0-p353
|
6
|
+
- 2.1.0
|
7
|
+
- 2.1.2
|
8
|
+
- 2.1.3
|
9
|
+
- 2.1.4
|
10
|
+
notifications:
|
11
|
+
email: false
|
12
|
+
slack:
|
13
|
+
secure: n7JigGAnMzisSryncblhmgh+/WoiOJJ3VoMiS6gYufJEjoiAByiOGKuMDYld/IfIZ9laD/XFx2l4aAl7QwCgae/EaKaZQeEoVSpIqacwsPNsQwqXf0IehgYv7/vykihYlFLu6eOCoqOXgDDWxP3Knned251MYzp8EcjNkUtZMrU=
|
14
|
+
addons:
|
15
|
+
code_climate:
|
16
|
+
repo_token: 9bd643c8090c8ef179d2b7d20d41d2e4399c031f621efdb125c1dc2ca5d9e6cc
|
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Claude Tech
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
# simple-api-auth [](https://travis-ci.org/claudetech/ruby-simple-api-auth) [](https://coveralls.io/r/claudetech/ruby-simple-api-auth?branch=master) [](https://codeclimate.com/github/claudetech/ruby-simple-api-auth)
|
2
|
+
|
3
|
+
This gem provides a very basic token based authentication
|
4
|
+
to use for API.
|
5
|
+
This is meant to be used as a lightweight authentication
|
6
|
+
solution when OAuth2 is too much.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'simple-api-auth'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install simple-api-auth
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
This gem works out of the box with `ActiveRecord` and can be used stand alone
|
27
|
+
with a little more work.
|
28
|
+
|
29
|
+
### ActiveRecord usage
|
30
|
+
|
31
|
+
Just include `acts_as_api_authenticable` in your model.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class User < ActiveRecord::Base
|
35
|
+
acts_as_api_authenticable
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
you will need to have `ssa_key` and `ssa_secret` defined as strings
|
40
|
+
in your database for this to work. If you want to change the columns name,
|
41
|
+
you can pass them in option.
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class User < ActiveRecord::Base
|
45
|
+
acts_as_api_authenticable ssa_key: :resource_key_field, ssa_secret: :secret_token
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
If you want the keys to be generated when you create a new instance of the model, you can pass `:auto_generate` with either `true`, or the field you want
|
50
|
+
to generate.
|
51
|
+
|
52
|
+
```ruby
|
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`
|
57
|
+
acts_as_api_authenticable auto_generate: true
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
Note that the keys for autogenerate should be `:ssa_key` and `:ssa_secret` even if you change the key column name.
|
62
|
+
|
63
|
+
You can then use
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
User.authenticate(request)
|
67
|
+
```
|
68
|
+
|
69
|
+
this will return the user if the request is valid, or `nil` otherwise.
|
70
|
+
|
71
|
+
### Standalone usage
|
72
|
+
|
73
|
+
This gem can easily used without `ActiveRecord`.
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
ssa_key = SimpleApiAuth.extract_key(request)
|
77
|
+
secret_key = your_logic_to_get_secret_key(ssa_key)
|
78
|
+
valid = SimpleApiAuth.valid_signature?(request, secret_key)
|
79
|
+
```
|
80
|
+
|
81
|
+
### Configuration
|
82
|
+
|
83
|
+
The library accepts the following configurations
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
SimpleApiAuth.configure do |config|
|
87
|
+
# values used as default with `acts_as_api_authenticable`
|
88
|
+
config.model_defaults = { ssa_key: :ssa_key, ssa_secret: :ssa_secret, auto_generate: false }
|
89
|
+
|
90
|
+
# the normalized keys for the HTTP headers
|
91
|
+
config.header_keys = { key: :x_saa_key, time: :x_saa_auth_time, authorization: :authorization
|
92
|
+
}
|
93
|
+
|
94
|
+
# the methods name for the HTTP request used
|
95
|
+
config.request_fields = {
|
96
|
+
headers: :headers,
|
97
|
+
http_verb: :method,
|
98
|
+
uri: :path,
|
99
|
+
query_string: :query_string,
|
100
|
+
body: :body
|
101
|
+
}
|
102
|
+
|
103
|
+
# the allowed HTTP methods
|
104
|
+
config.allowed_methods = [:get, :post, :put, :patch, :delete]
|
105
|
+
|
106
|
+
# the required headers for the HTTP request
|
107
|
+
config.required_headers = config.header_keys.values
|
108
|
+
|
109
|
+
# the class to use to hash requests
|
110
|
+
# it must contain a `hmac` and a `hash` method
|
111
|
+
config.hasher = SimpleApiAuth::Hasher::SHA1
|
112
|
+
|
113
|
+
# the class to use to sign requests
|
114
|
+
# it must contain a `sign` method
|
115
|
+
config.signer = SimpleApiAuth::Signer
|
116
|
+
config.request_timeout = 5
|
117
|
+
config.logger = nil
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
|
122
|
+
### Supported frameworks
|
123
|
+
|
124
|
+
This library should be configurable enough to work with more or less any framework.
|
125
|
+
It will work out of the box for Rails, and will just need a little setup
|
126
|
+
to work with `request` objects with a different API.
|
127
|
+
|
128
|
+
For example, to work with Sinatra request, the following can be passed
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
SimpleApiAuth.configure do |config|
|
132
|
+
config.request_fields.merge! {
|
133
|
+
headers: :env,
|
134
|
+
http_verb: :request_method,
|
135
|
+
uri: :path_info
|
136
|
+
}
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
Note that `body` should return something with a `read` method,
|
141
|
+
and `query_string` should contain the URI encoded query string.
|
142
|
+
|
143
|
+
## Clients
|
144
|
+
|
145
|
+
This library can also be used as a client. You can compute the request
|
146
|
+
signature with
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
signature = SimpleApiAuth.compute_signature(request, secret_key)
|
150
|
+
```
|
151
|
+
|
152
|
+
or directly sign the request with
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
SimpleApiAuth.sign!(request, secret_key)
|
156
|
+
```
|
157
|
+
|
158
|
+
A JS client, with an AngularJS module intregrated with the `$http` service
|
159
|
+
is in progress and should be available soon (with a few weeks).
|
160
|
+
|
161
|
+
Contributions are very welcome for other clients.
|
162
|
+
|
163
|
+
## Contributing
|
164
|
+
|
165
|
+
1. Fork it ( https://github.com/claudetech/simple-api-auth/fork )
|
166
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
167
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
168
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
169
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module SimpleApiAuth
|
2
|
+
module Authenticable
|
3
|
+
def api_authenticable?
|
4
|
+
false
|
5
|
+
end
|
6
|
+
|
7
|
+
def acts_as_api_authenticable(options = {})
|
8
|
+
if api_authenticable?
|
9
|
+
self.ssa_options = ssa_options.merge(options)
|
10
|
+
else
|
11
|
+
extend ClassMethods
|
12
|
+
include InstanceMethods
|
13
|
+
self.ssa_options = SimpleApiAuth.config.make_model_options(options)
|
14
|
+
ssa_options[:auto_generate].each do |field|
|
15
|
+
send(:after_initialize, "assign_#{field}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
attr_accessor :ssa_options
|
22
|
+
|
23
|
+
def api_authenticable?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def ssa_authenticate(request)
|
28
|
+
request = SimpleApiAuth::Request.create(request)
|
29
|
+
entity = ssa_find(request)
|
30
|
+
return false if entity.nil?
|
31
|
+
secret_key = entity.send(ssa_options[:ssa_secret])
|
32
|
+
return false unless SimpleApiAuth.valid_signature?(request, secret_key)
|
33
|
+
entity
|
34
|
+
end
|
35
|
+
|
36
|
+
def ssa_find(request)
|
37
|
+
key = SimpleApiAuth.extract_key(request)
|
38
|
+
find_by(ssa_options[:ssa_key] => key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def generate_ssa_key(options = {})
|
42
|
+
length = options[:length] || (Math.log(count + 1, 64) + 5)
|
43
|
+
loop do
|
44
|
+
key = SecureRandom.urlsafe_base64(length)
|
45
|
+
break key unless exists?(ssa_options[:ssa_key] => key)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def generate_ssa_secret(options = {})
|
50
|
+
length = options[:length] || 64
|
51
|
+
SecureRandom.urlsafe_base64(length)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module InstanceMethods
|
56
|
+
def assign_ssa_key(options = {})
|
57
|
+
assign_ssa(:ssa_key, options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def assign_ssa_secret(options = {})
|
61
|
+
assign_ssa(:ssa_secret, options)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def assign_ssa(field, options = {})
|
67
|
+
key_name = self.class.ssa_options[field]
|
68
|
+
send("#{key_name}=", self.class.send("generate_#{field}", options))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module SimpleApiAuth
|
2
|
+
class Authenticator
|
3
|
+
include SimpleApiAuth::Helpers::Auth
|
4
|
+
|
5
|
+
attr_accessor :request, :signer
|
6
|
+
|
7
|
+
def initialize(request, secret_key, options = {})
|
8
|
+
self.request = SimpleApiAuth::Request.create(request)
|
9
|
+
self.signer = SimpleApiAuth.config.signer.new
|
10
|
+
@options = options
|
11
|
+
@secret_key = secret_key
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid_signature?
|
15
|
+
return false if !check_data(request) || too_old?(request)
|
16
|
+
signed_request = signer.sign(request, @secret_key)
|
17
|
+
SimpleApiAuth.log(Logger::DEBUG, "Signed request: #{signed_request}")
|
18
|
+
SimpleApiAuth.log(Logger::DEBUG, "User signature: #{signature}")
|
19
|
+
secure_equals?(signed_request, signature, @secret_key)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def signature
|
25
|
+
extract_signature(headers)
|
26
|
+
end
|
27
|
+
|
28
|
+
def headers
|
29
|
+
request.headers
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module SimpleApiAuth
|
2
|
+
class Config
|
3
|
+
attr_accessor :request_fields, :allowed_methods
|
4
|
+
attr_accessor :signer, :request_timeout, :required_headers, :hasher
|
5
|
+
attr_accessor :logger, :header_keys, :model_defaults
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
reset!
|
9
|
+
end
|
10
|
+
|
11
|
+
def reset!
|
12
|
+
self.model_defaults = model_default_values
|
13
|
+
self.header_keys = default_header_keys
|
14
|
+
self.request_fields = default_request_fields
|
15
|
+
self.allowed_methods = [:get, :post, :put, :patch, :delete]
|
16
|
+
self.required_headers = default_header_keys.values
|
17
|
+
self.hasher = SimpleApiAuth::Hasher::SHA1
|
18
|
+
self.signer = SimpleApiAuth::Signer
|
19
|
+
self.request_timeout = 5
|
20
|
+
self.logger = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def make_model_options(options)
|
24
|
+
options = model_defaults.merge(options)
|
25
|
+
if options[:auto_generate].is_a?(Symbol)
|
26
|
+
options[:auto_generate] = [options[:auto_generate]]
|
27
|
+
elsif !options[:auto_generate].is_a?(Array)
|
28
|
+
options[:auto_generate] = options[:auto_generate] ? [:ssa_key, :ssa_secret] : []
|
29
|
+
end
|
30
|
+
options
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def default_request_fields
|
36
|
+
{
|
37
|
+
headers: :headers,
|
38
|
+
http_verb: :method,
|
39
|
+
uri: :path,
|
40
|
+
query_string: :query_string,
|
41
|
+
body: :body
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_header_keys
|
46
|
+
{
|
47
|
+
key: :x_saa_key,
|
48
|
+
time: :x_saa_auth_time,
|
49
|
+
authorization: :authorization
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def model_default_values
|
54
|
+
{
|
55
|
+
ssa_key: :ssa_key,
|
56
|
+
ssa_secret: :ssa_secret,
|
57
|
+
auto_generate: []
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SimpleApiAuth
|
2
|
+
module Helpers
|
3
|
+
module Auth
|
4
|
+
def extract_signature(headers)
|
5
|
+
header_key = SimpleApiAuth.config.header_keys[:authorization]
|
6
|
+
match = /Signature: (.+)/.match(headers[header_key])
|
7
|
+
match && match[1]
|
8
|
+
end
|
9
|
+
|
10
|
+
def required_headers
|
11
|
+
options[:required_headers] || SimpleApiAuth.config.required_headers
|
12
|
+
end
|
13
|
+
|
14
|
+
def request_timeout
|
15
|
+
(options[:request_timeout] || SimpleApiAuth.config.request_timeout) * 60
|
16
|
+
end
|
17
|
+
|
18
|
+
def allowed_methods
|
19
|
+
options[:allowed_methods] || SimpleApiAuth.config.allowed_methods
|
20
|
+
end
|
21
|
+
|
22
|
+
def options
|
23
|
+
@options || {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def check_data(request)
|
27
|
+
required_headers.each do |k, _|
|
28
|
+
return false unless request.headers.key?(k)
|
29
|
+
end
|
30
|
+
allowed_methods.include?(request.http_verb)
|
31
|
+
end
|
32
|
+
|
33
|
+
def too_old?(request)
|
34
|
+
request_time = request.time
|
35
|
+
return false if request_time.nil?
|
36
|
+
difference = Time.now - request_time
|
37
|
+
difference < 0 || difference > request_timeout
|
38
|
+
end
|
39
|
+
|
40
|
+
def secure_equals?(m1, m2, key)
|
41
|
+
sha1_hmac(key, m1) == sha1_hmac(key, m2)
|
42
|
+
end
|
43
|
+
|
44
|
+
def sha1_hmac(key, message)
|
45
|
+
SimpleApiAuth::Hasher::SHA1.new.hmac(key, message)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SimpleApiAuth
|
2
|
+
module Helpers
|
3
|
+
module Request
|
4
|
+
def normalize_headers(headers)
|
5
|
+
normalized_headers = {}
|
6
|
+
headers.each do |key, value|
|
7
|
+
normalized_key = normalize(key)
|
8
|
+
normalized_headers[normalized_key] = value
|
9
|
+
end
|
10
|
+
normalized_headers
|
11
|
+
end
|
12
|
+
|
13
|
+
def normalize(s)
|
14
|
+
s.to_s.downcase.gsub(/-/, '_').to_sym
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SimpleApiAuth
|
2
|
+
class Request
|
3
|
+
include SimpleApiAuth::Helpers::Request
|
4
|
+
|
5
|
+
attr_accessor :headers, :http_verb, :query_string, :uri, :body
|
6
|
+
|
7
|
+
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)
|
13
|
+
end
|
14
|
+
|
15
|
+
def time
|
16
|
+
header_key = SimpleApiAuth.config.header_keys[:time]
|
17
|
+
Time.parse(headers[header_key])
|
18
|
+
rescue ArgumentError
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
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)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module SimpleApiAuth
|
2
|
+
class Signer
|
3
|
+
attr_accessor :hasher
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
hahser_class = options[:hasher] || SimpleApiAuth.config.hasher
|
7
|
+
self.hasher = hahser_class.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def sign(request, secret_key)
|
11
|
+
signing_key = make_singing_key(request, secret_key)
|
12
|
+
SimpleApiAuth.log(Logger::DEBUG, "Signing key(hex): #{Digest.hexencode(signing_key)}")
|
13
|
+
|
14
|
+
string_to_sign = make_string_to_sign(request)
|
15
|
+
SimpleApiAuth.log(Logger::DEBUG, "String to sign: #{string_to_sign}")
|
16
|
+
|
17
|
+
signature = hasher.hmac(signing_key, string_to_sign)
|
18
|
+
Digest.hexencode(signature)
|
19
|
+
end
|
20
|
+
|
21
|
+
def make_string_to_sign(request)
|
22
|
+
hashed_request = make_hashed_request(request)
|
23
|
+
SimpleApiAuth.log(Logger::DEBUG, "Hashed request: #{hashed_request}")
|
24
|
+
[
|
25
|
+
request.time.iso8601,
|
26
|
+
hashed_request
|
27
|
+
].join("\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
def make_hashed_request(request)
|
31
|
+
canonical_request_string = make_canonical_request(request)
|
32
|
+
SimpleApiAuth.log(Logger::DEBUG, "Canonical request string: #{canonical_request_string}")
|
33
|
+
Digest.hexencode(hasher.hash(canonical_request_string))
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def make_singing_key(request, secret_key)
|
39
|
+
date = request.time.strftime('%Y%m%d')
|
40
|
+
hashed_date = hasher.hmac('ssa' + secret_key, date)
|
41
|
+
hasher.hmac(hashed_date, 'ssa_request')
|
42
|
+
end
|
43
|
+
|
44
|
+
def make_canonical_request(request)
|
45
|
+
[
|
46
|
+
request.http_verb,
|
47
|
+
URI.encode(request.uri),
|
48
|
+
URI.encode(request.query_string),
|
49
|
+
Digest.hexencode(hasher.hash(request.body.read))
|
50
|
+
].join("\n")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
['extensions/', 'helpers/', '', 'hashers/'].each do |path|
|
4
|
+
files = Dir[File.expand_path("simple-api-auth/#{path}*.rb", File.dirname(__FILE__))]
|
5
|
+
files.each do |m|
|
6
|
+
require m
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module SimpleApiAuth
|
11
|
+
def self.config
|
12
|
+
@config ||= Config.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.configure
|
16
|
+
yield config
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.log(severity, message = nil, progname = nil, &block)
|
20
|
+
config.logger.log(severity, message, progname, &block) unless config.logger.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.extract_key(request)
|
24
|
+
request = SimpleApiAuth::Request.create(request)
|
25
|
+
request.headers[SimpleApiAuth.config.header_keys[:key]]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.valid_signature?(request, secret_key, options = {})
|
29
|
+
authenticator = Authenticator.new(request, secret_key, options)
|
30
|
+
authenticator.valid_signature?
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.compute_signature(request, secret_key, options = {})
|
34
|
+
signer = SimpleApiAuth.config.signer.new(options)
|
35
|
+
signer.sign(request, secret_key)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.sign!(request, secret_key, options = {})
|
39
|
+
signature = compute_signature(request, secret_key, options)
|
40
|
+
header_name = SimpleApiAuth.config.request_fields[:headers]
|
41
|
+
authorization_key = SimpleApiAuth.config.header_keys[:authorization]
|
42
|
+
request.send(header_name)[authorization_key] = "Signature: #{signature}"
|
43
|
+
request
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ActiveRecord::Base.send(:extend, SimpleApiAuth::Authenticable) if defined?(ActiveRecord::Base)
|