simple-api-auth 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/claudetech/ruby-simple-api-auth.svg?branch=master)](https://travis-ci.org/claudetech/ruby-simple-api-auth) [![Coverage Status](https://coveralls.io/repos/claudetech/ruby-simple-api-auth/badge.png?branch=master)](https://coveralls.io/r/claudetech/ruby-simple-api-auth?branch=master) [![Code Climate](https://codeclimate.com/github/claudetech/ruby-simple-api-auth/badges/gpa.svg)](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)
|