simple-api-auth 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +14 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +16 -0
  7. data/Gemfile +12 -0
  8. data/Guardfile +5 -0
  9. data/LICENSE +22 -0
  10. data/README.md +169 -0
  11. data/Rakefile +7 -0
  12. data/lib/simple-api-auth/authenticable.rb +72 -0
  13. data/lib/simple-api-auth/authenticator.rb +32 -0
  14. data/lib/simple-api-auth/config.rb +61 -0
  15. data/lib/simple-api-auth/hashers/sha1_hasher.rb +14 -0
  16. data/lib/simple-api-auth/helpers/auth_helpers.rb +49 -0
  17. data/lib/simple-api-auth/helpers/request_helpers.rb +18 -0
  18. data/lib/simple-api-auth/request.rb +34 -0
  19. data/lib/simple-api-auth/signer.rb +53 -0
  20. data/lib/simple-api-auth/version.rb +6 -0
  21. data/lib/simple-api-auth.rb +47 -0
  22. data/simple-api-auth.gemspec +25 -0
  23. data/spec/extensions/string_spec.rb +13 -0
  24. data/spec/internal/app/models/models.rb +13 -0
  25. data/spec/internal/config/database.yml +4 -0
  26. data/spec/internal/db/schema.rb +7 -0
  27. data/spec/lib/simple-api-auth/authenticable_spec.rb +106 -0
  28. data/spec/lib/simple-api-auth/authenticator_spec.rb +35 -0
  29. data/spec/lib/simple-api-auth/config_spec.rb +48 -0
  30. data/spec/lib/simple-api-auth/helpers/auth_helpers_spec.rb +61 -0
  31. data/spec/lib/simple-api-auth/helpers/request_helpers_spec.rb +39 -0
  32. data/spec/lib/simple-api-auth/request_spec.rb +49 -0
  33. data/spec/lib/simple-api-auth/signer_spec.rb +37 -0
  34. data/spec/lib/simple-api-auth_spec.rb +110 -0
  35. data/spec/spec_helper.rb +31 -0
  36. data/spec/support/auth.rb +13 -0
  37. data/spec/support/database.rb +14 -0
  38. data/spec/support/database_cleaner.rb +19 -0
  39. data/spec/support/extensions.rb +10 -0
  40. data/spec/support/mocks.rb +67 -0
  41. data/spec/support/requests.rb +51 -0
  42. 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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ Documentation:
2
+ Enabled: false
3
+ Style/FileName:
4
+ Enabled: false
5
+
6
+ Metrics/LineLength:
7
+ Max: 100
8
+
9
+ AllCops:
10
+ Exclude:
11
+ - 'db/**/*'
12
+ - 'config/**/*'
13
+ - 'bin/**/*'
14
+ - 'Guardfile'
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
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'guard', require: false
7
+ gem 'guard-rspec', require: false
8
+ end
9
+
10
+ group :test do
11
+ gem 'codeclimate-test-reporter', require: false
12
+ end
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(/^spec\/.+_spec\.rb$/)
3
+ watch(/^lib\/(.+)\.rb$/) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+ end
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,7 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
@@ -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,14 @@
1
+ module SimpleApiAuth
2
+ module Hasher
3
+ class SHA1
4
+ def hash(value)
5
+ Digest::SHA1.digest(value)
6
+ end
7
+
8
+ def hmac(key, message)
9
+ digest = OpenSSL::Digest.new('sha1')
10
+ OpenSSL::HMAC.digest(digest, key, message)
11
+ end
12
+ end
13
+ end
14
+ 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,6 @@
1
+ module SimpleApiAuth
2
+ MAJOR = 0
3
+ MINOR = 1
4
+ PATCH = 0
5
+ VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
6
+ 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)