grape_api_signature 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.consolerc +3 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +0 -0
- data/.travis.yml +6 -0
- data/.versions.conf +4 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +146 -0
- data/Rakefile +14 -0
- data/app/assets/javascripts/aws-signature.js.coffee +177 -0
- data/grape_api_signature.gemspec +54 -0
- data/lib/grape_api_signature/authorization.rb +79 -0
- data/lib/grape_api_signature/aws_auth_parser.rb +57 -0
- data/lib/grape_api_signature/aws_authorization.rb +40 -0
- data/lib/grape_api_signature/aws_digester.rb +27 -0
- data/lib/grape_api_signature/aws_request.rb +63 -0
- data/lib/grape_api_signature/aws_signer.rb +76 -0
- data/lib/grape_api_signature/middleware/auth.rb +105 -0
- data/lib/grape_api_signature/middleware/grape_auth.rb +44 -0
- data/lib/grape_api_signature/rails/engine.rb +6 -0
- data/lib/grape_api_signature/rspec.rb +49 -0
- data/lib/grape_api_signature/version.rb +3 -0
- data/lib/grape_api_signature.rb +21 -0
- data/spec/acceptance/.gitkeep +0 -0
- data/spec/acceptance/lib/grape_api_signature/aws_request_spec.rb +39 -0
- data/spec/acceptance/lib/grape_api_signature/aws_signer_spec.rb +54 -0
- data/spec/acceptance/lib/grape_api_signature/middleware/auth_spec.rb +60 -0
- data/spec/acceptance/lib/grape_api_signature/middleware/grape_auth_spec.rb +83 -0
- data/spec/acceptance/support/.keep +0 -0
- data/spec/acceptance/support/api.rb +5 -0
- data/spec/acceptance/support/aws_helper.rb +30 -0
- data/spec/acceptance/support/feature.rb +31 -0
- data/spec/acceptance_spec_helper.rb +3 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.authz +1 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.creq +9 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.req +7 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.sreq +8 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.sts +4 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-value-multiline.req +7 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.authz +1 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.creq +9 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.req +8 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.sreq +9 -0
- data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.sts +4 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.authz +1 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.creq +8 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.req +4 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.sts +4 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.authz +1 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.creq +8 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.req +4 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.creq +9 -0
- data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.req +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.sreq +6 -0
- data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-relative.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slash.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slashes.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slashes.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slashes.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slashes.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-slashes.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-space.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-space.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-space.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-space.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-space.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-unreserved.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-unreserved.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-unreserved.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-unreserved.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-unreserved.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-utf8.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-utf8.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-utf8.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-utf8.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-utf8.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/get-vanilla.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.creq +9 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.req +5 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.sreq +6 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.creq +9 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.req +5 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.sreq +6 -0
- data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla.creq +8 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla.req +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla.sreq +5 -0
- data/spec/fixtures/aws4_test_suite/pass/post-vanilla.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.creq +9 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.req +6 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.sreq +7 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.sts +4 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.authz +1 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.creq +9 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.req +6 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.sreq +7 -0
- data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.sts +4 -0
- data/spec/integration/.gitkeep +0 -0
- data/spec/integration/support/.keep +0 -0
- data/spec/integration_spec_helper.rb +3 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/support/.gitkeep +0 -0
- data/spec/unit/.gitkeep +0 -0
- data/spec/unit/lib/grape_api_signature/authorization_spec.rb +79 -0
- data/spec/unit/lib/grape_api_signature/aws_auth_parser_spec.rb +25 -0
- data/spec/unit/support/.keep +0 -0
- data/spec/unit_spec_helper.rb +3 -0
- data/vendor/assets/javascripts/hmac-sha256.js +18 -0
- metadata +692 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ea25995d332c08dbd8b0843504808eb5b277e2c5
|
4
|
+
data.tar.gz: 4901ff5161b4be67a5c60f9e4b18d729c1002425
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 914c187ed3d3b2c26fa8e001a26bbc745b293c985d30d2918bd30a33392228a673eb2decc29fad555ab8657786a8e88db1b9b735e61ca490c57b1fb291f0c19a
|
7
|
+
data.tar.gz: f837467539c385f898aadd2522607b21fda9bb71f583a4bc6a9177c46bd3908a34c299084cc4ea91c7a7139073893b81c99b5f9ea2c4cec350e03994845f0c87
|
data/.consolerc
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.idea
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
|
4
|
+
LineLength:
|
5
|
+
Max: 160
|
6
|
+
|
7
|
+
Documentation:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
ParameterLists:
|
11
|
+
Max: 15
|
12
|
+
|
13
|
+
MethodLength:
|
14
|
+
Max: 50
|
15
|
+
|
16
|
+
# @see http://www.rubytapas.com/episodes/24-Incidental-Change
|
17
|
+
TrailingComma:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
RegexpLiteral:
|
21
|
+
Enabled: false
|
data/.rubocop_todo.yml
ADDED
File without changes
|
data/.travis.yml
ADDED
data/.versions.conf
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in grape_api_signature.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'simplecov', require: false, group: :test
|
7
|
+
gem 'coveralls', require: false
|
8
|
+
gem 'rubocop', require: false
|
9
|
+
|
10
|
+
gem 'grape', github: 'intridea/grape'
|
11
|
+
gem 'grape-entity', github: 'intridea/grape-entity'
|
12
|
+
|
13
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 FABER Lotto GmbH & Co. KG
|
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,146 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/grape_api_signature.svg)](http://badge.fury.io/rb/grape_api_signature)
|
2
|
+
[![Build Status](https://travis-ci.org/faber-lotto/grape_api_signature.svg?branch=master)](https://travis-ci.org/faber-lotto/grape_api_signature)
|
3
|
+
[![Code Climate](https://codeclimate.com/github/faber-lotto/grape_api_signature.png)](https://codeclimate.com/github/faber-lotto/grape_api_signature)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/faber-lotto/grape_api_signature/badge.png?branch=master)](https://coveralls.io/r/faber-lotto/grape_api_signature?branch=master)
|
5
|
+
|
6
|
+
# GrapeAPISignature
|
7
|
+
|
8
|
+
`GrapeAPISignature` provides a [AWS 4 style](http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
|
9
|
+
Authentication middleware to be used with [grape](https://github.com/intridea/grape).
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'grape_api_signature'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install grape_api_signature
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### In your API
|
28
|
+
|
29
|
+
Example usage:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
|
33
|
+
class YourAPI < Grape::API
|
34
|
+
Grape::Middleware::Auth::Strategies.add(:aws4_auth,
|
35
|
+
GrapeAPISignature::Middleware::GrapeAuth,
|
36
|
+
->(options) { [options[:max_request_age] || 900, options[:user_setter]] })
|
37
|
+
|
38
|
+
GrapeAPISignature::Middleware::GrapeAuth.default_authenticator do |_access_key, _region, _service|
|
39
|
+
user = ... # find the user, this block is executed in the context of your Endpoint
|
40
|
+
{ user: user, secret_key: user.key }
|
41
|
+
end
|
42
|
+
|
43
|
+
helpers do
|
44
|
+
attr_accessor :current_user
|
45
|
+
end
|
46
|
+
|
47
|
+
auth :aws4_auth,
|
48
|
+
max_request_age: 100,
|
49
|
+
user_setter: :'current_user=',
|
50
|
+
&GrapeAPISignature::Middleware::GrapeAuth.default_authenticator
|
51
|
+
|
52
|
+
get '/aws4_authorized' do
|
53
|
+
'DONE'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
```
|
58
|
+
|
59
|
+
Options:
|
60
|
+
|
61
|
+
* `max_request_age` => Time in seconds how long a request is valid
|
62
|
+
* `user_setter` => When provided this is be used to set the user on your Endtpoint instance if the validations was
|
63
|
+
successful
|
64
|
+
|
65
|
+
If the validation was successful `env['REMOTE_USER']` is set to the access_key.
|
66
|
+
|
67
|
+
If the validation was not successful HTTP-Status 401 is set via `Grape#error!`
|
68
|
+
when the signature was wrong and 400 if `Scheme` does not match 'AWS4-HMAC-SHA256'
|
69
|
+
|
70
|
+
### In your rspecs
|
71
|
+
|
72
|
+
The following code only supports 'application/json' as Content-Type.
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
|
76
|
+
# spec_helper.rb
|
77
|
+
|
78
|
+
require 'grape_api_signature/rspec'
|
79
|
+
|
80
|
+
# in your spec
|
81
|
+
|
82
|
+
describe 'your wishes' do
|
83
|
+
include GrapeAPISignature::RSpec
|
84
|
+
|
85
|
+
let(:secret_key) { 'SUPERSUPERSUPERSECRET' }
|
86
|
+
let(:access_key) { 'MyUser' }
|
87
|
+
|
88
|
+
|
89
|
+
# if you don't use Rails
|
90
|
+
let(:hostname) { Rack::Test::DEFAULT_HOST }
|
91
|
+
let(:port) { 80 }
|
92
|
+
let(:host) { "#{hostname}:#{port}" }
|
93
|
+
|
94
|
+
def https?
|
95
|
+
port == 443
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should do' do
|
99
|
+
get_with_auth '/'
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should also do' do
|
103
|
+
post_with_auth '/', request_params
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
```
|
109
|
+
|
110
|
+
### In your Rails-App
|
111
|
+
|
112
|
+
This gem provides a coffee script to authenticate swagger demo requests via AWS 4.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
|
116
|
+
# application.js.coffee
|
117
|
+
|
118
|
+
#= require aws-signature
|
119
|
+
|
120
|
+
```
|
121
|
+
|
122
|
+
```html
|
123
|
+
# your swagger index.html, or what you use
|
124
|
+
|
125
|
+
<div id="header">
|
126
|
+
<div class="swagger-ui-wrap">
|
127
|
+
<form id="api_selector">
|
128
|
+
<div class="input">
|
129
|
+
<input type="text" placeholder="Username" id="input_apiUser" name="user">
|
130
|
+
</div>
|
131
|
+
<div class="input">
|
132
|
+
<input type="password" placeholder="Password" id="input_apiPassword" name="password">
|
133
|
+
</div>
|
134
|
+
</form>
|
135
|
+
</div>
|
136
|
+
</div>
|
137
|
+
|
138
|
+
```
|
139
|
+
|
140
|
+
## Contributing
|
141
|
+
|
142
|
+
1. Fork it ( https://github.com/faber-lotto/grape_api_signature/fork )
|
143
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
144
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
145
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
146
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rubocop/rake_task'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
RuboCop::RakeTask.new
|
7
|
+
|
8
|
+
task default: :spec
|
9
|
+
|
10
|
+
desc 'Run RSpec with code coverage'
|
11
|
+
task :coverage do
|
12
|
+
ENV['SIMPLE_COVERAGE'] = 'true'
|
13
|
+
Rake::Task['spec'].execute
|
14
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# Place all the behaviors and hooks related to the matching controller here.
|
2
|
+
# All this logic will automatically be available in application.js.
|
3
|
+
# You can use CoffeeScript in this file: http://coffeescript.org/
|
4
|
+
|
5
|
+
#=require hmac-sha256
|
6
|
+
|
7
|
+
root = exports ? this
|
8
|
+
|
9
|
+
do ($=jQuery) ->
|
10
|
+
$ ->
|
11
|
+
class AWS4Authorization
|
12
|
+
constructor: (@name) ->
|
13
|
+
@algorithm = 'AWS4-HMAC-SHA256'
|
14
|
+
|
15
|
+
apply: (obj, authorizations) ->
|
16
|
+
@password = $('#input_apiPassword')[0].value
|
17
|
+
@access_key_id = $('#input_apiUser')[0].value
|
18
|
+
|
19
|
+
@datetime = @dateStamp()
|
20
|
+
@region = 'europe'
|
21
|
+
@url = new URL(obj.url)
|
22
|
+
@service = @url.hostname.split('.',2)[0]
|
23
|
+
@pathname = @url.pathname
|
24
|
+
@search = @query_sorted(@url.searchParams.toString())
|
25
|
+
@method = obj.method.toUpperCase()
|
26
|
+
@req_headers = $.extend({}, obj.headers)
|
27
|
+
@body = obj.body ? ''
|
28
|
+
|
29
|
+
@req_headers['X-Amz-Date'] = @datetime
|
30
|
+
|
31
|
+
obj.headers["Authorization"] = @authorization()
|
32
|
+
obj.headers['X-Amz-Date'] = @datetime
|
33
|
+
obj.headers['X-Amz-Algorithm'] = @algorithm
|
34
|
+
obj.headers['X-Amz-SignedHeaders'] = @signed_headers()
|
35
|
+
|
36
|
+
console.log "String to sign #{@string_to_sign()}"
|
37
|
+
console.log "Canonical string #{@canonical_string()}"
|
38
|
+
|
39
|
+
hash = @signature_key(@password, @datetime, @region, @service)
|
40
|
+
|
41
|
+
console.log 'key: ' + hash.toString(CryptoJS.enc.Hex)
|
42
|
+
|
43
|
+
|
44
|
+
authorization: ->
|
45
|
+
parts = []
|
46
|
+
cred_string = @credential_string(@datetime)
|
47
|
+
parts.push(@algorithm + ' Credential=' +
|
48
|
+
@access_key_id + '/' + cred_string);
|
49
|
+
parts.push('SignedHeaders=' + @signed_headers())
|
50
|
+
parts.push('Signature=' + @signature())
|
51
|
+
|
52
|
+
parts.join(', ')
|
53
|
+
|
54
|
+
signature: ->
|
55
|
+
hash = @signature_key(@password, @datetime, @region, @service)
|
56
|
+
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, hash)
|
57
|
+
hmac.update @string_to_sign()
|
58
|
+
|
59
|
+
hash= hmac.finalize()
|
60
|
+
# hash = CryptoJS.HmacSHA256(@string_to_sign(), key)
|
61
|
+
hash.toString(CryptoJS.enc.Hex)
|
62
|
+
|
63
|
+
string_to_sign: () ->
|
64
|
+
parts = []
|
65
|
+
parts.push('AWS4-HMAC-SHA256')
|
66
|
+
parts.push(@datetime)
|
67
|
+
parts.push(@credential_string())
|
68
|
+
parts.push(@hex_encoded_hash(@canonical_string()))
|
69
|
+
parts.join('\n')
|
70
|
+
|
71
|
+
credential_string: () ->
|
72
|
+
parts = []
|
73
|
+
parts.push(@datetime.substr(0, 8))
|
74
|
+
parts.push(@region)
|
75
|
+
parts.push(@service)
|
76
|
+
parts.push('aws4_request')
|
77
|
+
parts.join('/')
|
78
|
+
|
79
|
+
canonical_string: ->
|
80
|
+
parts = []
|
81
|
+
|
82
|
+
req_headers = @req_headers
|
83
|
+
|
84
|
+
parts.push(@method)
|
85
|
+
parts.push(@pathname)
|
86
|
+
parts.push(@search)
|
87
|
+
parts.push(@canonical_headers() + '\n')
|
88
|
+
parts.push(@signed_headers())
|
89
|
+
parts.push(@hex_encoded_body_hash())
|
90
|
+
parts.join('\n')
|
91
|
+
|
92
|
+
canonical_headers: () ->
|
93
|
+
headers = []
|
94
|
+
for header, item of @req_headers
|
95
|
+
header = header.toLowerCase()
|
96
|
+
|
97
|
+
if @is_signable_header(header)
|
98
|
+
item = @canonical_header_values(item.toString())
|
99
|
+
headers.push(header + ':' + item)
|
100
|
+
|
101
|
+
headers.sort((a, b) ->
|
102
|
+
if (a.toLowerCase() < b.toLowerCase()) == true
|
103
|
+
-1
|
104
|
+
else
|
105
|
+
1
|
106
|
+
)
|
107
|
+
|
108
|
+
headers.join('\n')
|
109
|
+
|
110
|
+
query_sorted: (query_str) ->
|
111
|
+
queries = query_str.split('&')
|
112
|
+
|
113
|
+
queries.sort((a, b) ->
|
114
|
+
if (a.toLowerCase() < b.toLowerCase()) == true
|
115
|
+
-1
|
116
|
+
else
|
117
|
+
1
|
118
|
+
)
|
119
|
+
|
120
|
+
queries.join('&')
|
121
|
+
|
122
|
+
|
123
|
+
canonical_header_values: (value) ->
|
124
|
+
value.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '')
|
125
|
+
|
126
|
+
hex_encoded_body_hash: ->
|
127
|
+
@hex_encoded_hash(@body)
|
128
|
+
|
129
|
+
hex_encoded_hash: (msg)->
|
130
|
+
hash = CryptoJS.SHA256(msg)
|
131
|
+
hash.toString(CryptoJS.enc.Hex)
|
132
|
+
|
133
|
+
signed_headers: ()->
|
134
|
+
keys = []
|
135
|
+
for header, item of @req_headers
|
136
|
+
header = header.toLowerCase()
|
137
|
+
if @is_signable_header(header)
|
138
|
+
keys.push(header)
|
139
|
+
|
140
|
+
keys.sort().join(';')
|
141
|
+
|
142
|
+
is_signable_header: (header)->
|
143
|
+
not_signable_headers = ['authorization', 'content-length', 'user-agent']
|
144
|
+
not_signable_headers.indexOf(header) < 0
|
145
|
+
|
146
|
+
dateStamp: ->
|
147
|
+
(new Date()).toISOString().replace(/[:\-]|\.\d{3}/g, '')
|
148
|
+
|
149
|
+
signature_key: (key, dateStamp, regionName, serviceName) ->
|
150
|
+
dateStamp = dateStamp.substr(0, 8)
|
151
|
+
keyWords = CryptoJS.enc.Utf8.parse("AWS4" + key)
|
152
|
+
|
153
|
+
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, keyWords)
|
154
|
+
hmac.update dateStamp
|
155
|
+
hash= hmac.finalize()
|
156
|
+
|
157
|
+
console.log "Date: #{dateStamp} #{hash.toString(CryptoJS.enc.Hex)}"
|
158
|
+
|
159
|
+
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, hash)
|
160
|
+
hmac.update regionName
|
161
|
+
hash= hmac.finalize()
|
162
|
+
|
163
|
+
console.log "Region: #{regionName} #{hash.toString(CryptoJS.enc.Hex)}"
|
164
|
+
|
165
|
+
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, hash)
|
166
|
+
hmac.update serviceName
|
167
|
+
hash= hmac.finalize()
|
168
|
+
|
169
|
+
console.log "Service: #{serviceName} #{hash.toString(CryptoJS.enc.Hex)}"
|
170
|
+
|
171
|
+
hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, hash)
|
172
|
+
hmac.update "aws4_request"
|
173
|
+
|
174
|
+
hmac.finalize()
|
175
|
+
|
176
|
+
|
177
|
+
root.authorizations.add("aws4_authorization", new AWS4Authorization("aws4_authorization"))
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'grape_api_signature/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'grape_api_signature'
|
8
|
+
spec.version = GrapeAPISignature::VERSION
|
9
|
+
spec.authors = ['Dieter Späth']
|
10
|
+
spec.email = ['d.spaeth@faber.de']
|
11
|
+
spec.summary = "Add AWS Signature 4 style authentication to grape API's."
|
12
|
+
spec.description = "Add AWS Signature 4 style authentication to grape API's."
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'rack', '>= 1.3.0'
|
23
|
+
spec.add_runtime_dependency 'rack-mount'
|
24
|
+
spec.add_runtime_dependency 'rack-accept'
|
25
|
+
spec.add_runtime_dependency 'activesupport', '>=4.1.0'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler'
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'grape'
|
31
|
+
spec.add_development_dependency 'grape-entity'
|
32
|
+
|
33
|
+
spec.add_development_dependency 'thin'
|
34
|
+
spec.add_development_dependency 'rack-test'
|
35
|
+
|
36
|
+
spec.add_development_dependency 'rspec', '3.0'
|
37
|
+
# show nicely how many specs have to be run
|
38
|
+
spec.add_development_dependency 'fuubar'
|
39
|
+
# extended console
|
40
|
+
spec.add_development_dependency 'pry'
|
41
|
+
spec.add_development_dependency 'pry-remote'
|
42
|
+
# https://github.com/pry/pry-stack_explorer
|
43
|
+
spec.add_development_dependency 'pry-stack_explorer'
|
44
|
+
# https://github.com/deivid-rodriguez/pry-byebug
|
45
|
+
# pre-debugger / debugger does not work with ruby 2.x
|
46
|
+
spec.add_development_dependency 'pry-byebug'
|
47
|
+
|
48
|
+
# automatic execute tasks on file changes
|
49
|
+
spec.add_development_dependency 'guard'
|
50
|
+
spec.add_development_dependency 'guard-rspec'
|
51
|
+
spec.add_development_dependency 'guard-bundler'
|
52
|
+
spec.add_development_dependency 'rb-fsevent'
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module GrapeAPISignature
|
2
|
+
class Authorization
|
3
|
+
attr_accessor :request_method, :headers, :body, :auth_header,
|
4
|
+
:uri, :secret_key, :authorization, :max_request_age_in_sec
|
5
|
+
|
6
|
+
def initialize(request_method, headers, uri, body, max_request_age_in_sec = 900)
|
7
|
+
self.request_method = request_method.upcase
|
8
|
+
self.headers = headers.each_with_object({}) { |(key, value), result_hash| result_hash[key.downcase] = value }
|
9
|
+
self.body = body
|
10
|
+
self.auth_header = {}
|
11
|
+
self.uri = uri
|
12
|
+
self.max_request_age_in_sec = max_request_age_in_sec
|
13
|
+
self.authorization = GrapeAPISignature::AWSAuthParser.parse(self.headers['authorization']) if auth_header?
|
14
|
+
end
|
15
|
+
|
16
|
+
def user_id
|
17
|
+
authorization.access_key
|
18
|
+
end
|
19
|
+
|
20
|
+
def region
|
21
|
+
authorization.region
|
22
|
+
end
|
23
|
+
|
24
|
+
def service
|
25
|
+
authorization.service
|
26
|
+
end
|
27
|
+
|
28
|
+
def datetime
|
29
|
+
(headers['date'] || headers['x-amz-date'] || max_request_age - 1).to_time
|
30
|
+
end
|
31
|
+
|
32
|
+
def signed_headers
|
33
|
+
return {} unless authorization.signed_headers.present?
|
34
|
+
headers.slice(*authorization.signed_headers)
|
35
|
+
end
|
36
|
+
|
37
|
+
def calculated_signature(secret_key)
|
38
|
+
signer = GrapeAPISignature::AWSSigner.new(
|
39
|
+
access_key: user_id,
|
40
|
+
secret_key: secret_key,
|
41
|
+
region: authorization.region
|
42
|
+
)
|
43
|
+
|
44
|
+
signer.signature_only(request_method, uri, signed_headers, body)
|
45
|
+
end
|
46
|
+
|
47
|
+
def auth_header?
|
48
|
+
headers['authorization'].present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def authentic?(secret_key)
|
52
|
+
return false if secret_key.nil?
|
53
|
+
|
54
|
+
auth_header? && signatures_match?(secret_key) && !request_too_old?
|
55
|
+
end
|
56
|
+
|
57
|
+
def signatures_match?(secret_key)
|
58
|
+
authorization.signature.present? && secure_compare(calculated_signature(secret_key), authorization.signature)
|
59
|
+
end
|
60
|
+
|
61
|
+
def request_too_old?
|
62
|
+
datetime.utc < max_request_age
|
63
|
+
end
|
64
|
+
|
65
|
+
def max_request_age
|
66
|
+
Time.now.utc - max_request_age_in_sec
|
67
|
+
end
|
68
|
+
|
69
|
+
def secure_compare(a, b)
|
70
|
+
return false unless a.to_s.bytesize == b.to_s.bytesize
|
71
|
+
|
72
|
+
l = a.unpack 'C*'
|
73
|
+
|
74
|
+
res = 0
|
75
|
+
b.each_byte { |byte| res |= byte ^ l.shift }
|
76
|
+
res == 0
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module GrapeAPISignature
|
2
|
+
class AWSAuthParser
|
3
|
+
attr_accessor :str, :authorization
|
4
|
+
|
5
|
+
def self.parse(str)
|
6
|
+
new(str).parse
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(str)
|
10
|
+
self.str = str
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
self.authorization = AWSAuthorization.new(access_key, credential, signed_headers, signature)
|
15
|
+
end
|
16
|
+
|
17
|
+
def access_key
|
18
|
+
credential_str.split('/', 2)[0]
|
19
|
+
end
|
20
|
+
|
21
|
+
def credential
|
22
|
+
credential_str.split('/', 2)[1]
|
23
|
+
end
|
24
|
+
|
25
|
+
def signed_headers
|
26
|
+
(params['SignedHeaders'] || '').split(';').map(&:strip)
|
27
|
+
end
|
28
|
+
|
29
|
+
def signature
|
30
|
+
params['Signature'] || 'NOT_PROVIDED'
|
31
|
+
end
|
32
|
+
|
33
|
+
def credential_str
|
34
|
+
params['Credential'] || 'NOT_PROVIDED/NOT_PROVIDED/NOT_PROVIDED/NOT_PROVIDED/NOT_PROVIDED'
|
35
|
+
end
|
36
|
+
|
37
|
+
def params
|
38
|
+
@params ||= parse_params
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_params
|
42
|
+
param_str.split(',').each_with_object({}) do |data, result|
|
43
|
+
(key, value) = data.split('=')
|
44
|
+
value ||= ''
|
45
|
+
result[key.strip] = value.strip
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def sig_type
|
50
|
+
@sig_type ||= str.split(' ', 2)[0]
|
51
|
+
end
|
52
|
+
|
53
|
+
def param_str
|
54
|
+
@param_str ||= str.split(' ', 2)[1]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module GrapeAPISignature
|
2
|
+
class AWSAuthorization
|
3
|
+
attr_accessor :access_key,
|
4
|
+
:credential_string,
|
5
|
+
:signed_headers,
|
6
|
+
:signature
|
7
|
+
|
8
|
+
attr_reader :date,
|
9
|
+
:region,
|
10
|
+
:service
|
11
|
+
|
12
|
+
def initialize(access_key, credential_string, signed_headers, signature)
|
13
|
+
self.access_key = access_key
|
14
|
+
self.credential_string = credential_string
|
15
|
+
self.signed_headers = signed_headers
|
16
|
+
self.signature = signature
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
[
|
21
|
+
"AWS4-HMAC-SHA256 Credential=#{access_key}/#{credential_string}",
|
22
|
+
"SignedHeaders=#{signed_headers_str}",
|
23
|
+
"Signature=#{signature}"
|
24
|
+
].join(', ')
|
25
|
+
end
|
26
|
+
|
27
|
+
def signed_headers=(signed_headers)
|
28
|
+
@signed_headers = signed_headers.map(&:to_s).map(&:downcase).sort
|
29
|
+
end
|
30
|
+
|
31
|
+
def credential_string=(credential_string)
|
32
|
+
@credential_string = credential_string || (['NOT_PROVIDED'] * 4).join('/')
|
33
|
+
(@date, @region, @service, _) = @credential_string.split('/', 4)
|
34
|
+
end
|
35
|
+
|
36
|
+
def signed_headers_str
|
37
|
+
signed_headers.join(';')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|