grape_api_signature 0.0.1

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.
Files changed (199) hide show
  1. checksums.yaml +7 -0
  2. data/.consolerc +3 -0
  3. data/.gitignore +23 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +21 -0
  6. data/.rubocop_todo.yml +0 -0
  7. data/.travis.yml +6 -0
  8. data/.versions.conf +4 -0
  9. data/Gemfile +13 -0
  10. data/LICENSE.txt +22 -0
  11. data/README.md +146 -0
  12. data/Rakefile +14 -0
  13. data/app/assets/javascripts/aws-signature.js.coffee +177 -0
  14. data/grape_api_signature.gemspec +54 -0
  15. data/lib/grape_api_signature/authorization.rb +79 -0
  16. data/lib/grape_api_signature/aws_auth_parser.rb +57 -0
  17. data/lib/grape_api_signature/aws_authorization.rb +40 -0
  18. data/lib/grape_api_signature/aws_digester.rb +27 -0
  19. data/lib/grape_api_signature/aws_request.rb +63 -0
  20. data/lib/grape_api_signature/aws_signer.rb +76 -0
  21. data/lib/grape_api_signature/middleware/auth.rb +105 -0
  22. data/lib/grape_api_signature/middleware/grape_auth.rb +44 -0
  23. data/lib/grape_api_signature/rails/engine.rb +6 -0
  24. data/lib/grape_api_signature/rspec.rb +49 -0
  25. data/lib/grape_api_signature/version.rb +3 -0
  26. data/lib/grape_api_signature.rb +21 -0
  27. data/spec/acceptance/.gitkeep +0 -0
  28. data/spec/acceptance/lib/grape_api_signature/aws_request_spec.rb +39 -0
  29. data/spec/acceptance/lib/grape_api_signature/aws_signer_spec.rb +54 -0
  30. data/spec/acceptance/lib/grape_api_signature/middleware/auth_spec.rb +60 -0
  31. data/spec/acceptance/lib/grape_api_signature/middleware/grape_auth_spec.rb +83 -0
  32. data/spec/acceptance/support/.keep +0 -0
  33. data/spec/acceptance/support/api.rb +5 -0
  34. data/spec/acceptance/support/aws_helper.rb +30 -0
  35. data/spec/acceptance/support/feature.rb +31 -0
  36. data/spec/acceptance_spec_helper.rb +3 -0
  37. data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.authz +1 -0
  38. data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.creq +9 -0
  39. data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.req +7 -0
  40. data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.sreq +8 -0
  41. data/spec/fixtures/aws4_test_suite/fail/get-header-key-duplicate.sts +4 -0
  42. data/spec/fixtures/aws4_test_suite/fail/get-header-value-multiline.req +7 -0
  43. data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.authz +1 -0
  44. data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.creq +9 -0
  45. data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.req +8 -0
  46. data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.sreq +9 -0
  47. data/spec/fixtures/aws4_test_suite/fail/get-header-value-order.sts +4 -0
  48. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.authz +1 -0
  49. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.creq +8 -0
  50. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.req +4 -0
  51. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.sreq +5 -0
  52. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-nonunreserved.sts +4 -0
  53. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.authz +1 -0
  54. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.creq +8 -0
  55. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.req +4 -0
  56. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.sreq +5 -0
  57. data/spec/fixtures/aws4_test_suite/fail/post-vanilla-query-space.sts +4 -0
  58. data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.authz +1 -0
  59. data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.creq +9 -0
  60. data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.req +5 -0
  61. data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.sreq +6 -0
  62. data/spec/fixtures/aws4_test_suite/pass/get-header-value-trim.sts +4 -0
  63. data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.authz +1 -0
  64. data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.creq +8 -0
  65. data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.req +4 -0
  66. data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.sreq +5 -0
  67. data/spec/fixtures/aws4_test_suite/pass/get-relative-relative.sts +4 -0
  68. data/spec/fixtures/aws4_test_suite/pass/get-relative.authz +1 -0
  69. data/spec/fixtures/aws4_test_suite/pass/get-relative.creq +8 -0
  70. data/spec/fixtures/aws4_test_suite/pass/get-relative.req +4 -0
  71. data/spec/fixtures/aws4_test_suite/pass/get-relative.sreq +5 -0
  72. data/spec/fixtures/aws4_test_suite/pass/get-relative.sts +4 -0
  73. data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.authz +1 -0
  74. data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.creq +8 -0
  75. data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.req +4 -0
  76. data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.sreq +5 -0
  77. data/spec/fixtures/aws4_test_suite/pass/get-slash-dot-slash.sts +4 -0
  78. data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.authz +1 -0
  79. data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.creq +8 -0
  80. data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.req +4 -0
  81. data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.sreq +5 -0
  82. data/spec/fixtures/aws4_test_suite/pass/get-slash-pointless-dot.sts +4 -0
  83. data/spec/fixtures/aws4_test_suite/pass/get-slash.authz +1 -0
  84. data/spec/fixtures/aws4_test_suite/pass/get-slash.creq +8 -0
  85. data/spec/fixtures/aws4_test_suite/pass/get-slash.req +4 -0
  86. data/spec/fixtures/aws4_test_suite/pass/get-slash.sreq +5 -0
  87. data/spec/fixtures/aws4_test_suite/pass/get-slash.sts +4 -0
  88. data/spec/fixtures/aws4_test_suite/pass/get-slashes.authz +1 -0
  89. data/spec/fixtures/aws4_test_suite/pass/get-slashes.creq +8 -0
  90. data/spec/fixtures/aws4_test_suite/pass/get-slashes.req +4 -0
  91. data/spec/fixtures/aws4_test_suite/pass/get-slashes.sreq +5 -0
  92. data/spec/fixtures/aws4_test_suite/pass/get-slashes.sts +4 -0
  93. data/spec/fixtures/aws4_test_suite/pass/get-space.authz +1 -0
  94. data/spec/fixtures/aws4_test_suite/pass/get-space.creq +8 -0
  95. data/spec/fixtures/aws4_test_suite/pass/get-space.req +4 -0
  96. data/spec/fixtures/aws4_test_suite/pass/get-space.sreq +5 -0
  97. data/spec/fixtures/aws4_test_suite/pass/get-space.sts +4 -0
  98. data/spec/fixtures/aws4_test_suite/pass/get-unreserved.authz +1 -0
  99. data/spec/fixtures/aws4_test_suite/pass/get-unreserved.creq +8 -0
  100. data/spec/fixtures/aws4_test_suite/pass/get-unreserved.req +4 -0
  101. data/spec/fixtures/aws4_test_suite/pass/get-unreserved.sreq +5 -0
  102. data/spec/fixtures/aws4_test_suite/pass/get-unreserved.sts +4 -0
  103. data/spec/fixtures/aws4_test_suite/pass/get-utf8.authz +1 -0
  104. data/spec/fixtures/aws4_test_suite/pass/get-utf8.creq +8 -0
  105. data/spec/fixtures/aws4_test_suite/pass/get-utf8.req +4 -0
  106. data/spec/fixtures/aws4_test_suite/pass/get-utf8.sreq +5 -0
  107. data/spec/fixtures/aws4_test_suite/pass/get-utf8.sts +4 -0
  108. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.authz +1 -0
  109. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.creq +8 -0
  110. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.req +4 -0
  111. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.sreq +5 -0
  112. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-empty-query-key.sts +4 -0
  113. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.authz +1 -0
  114. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.creq +8 -0
  115. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.req +4 -0
  116. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.sreq +5 -0
  117. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key-case.sts +4 -0
  118. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.authz +1 -0
  119. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.creq +8 -0
  120. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.req +4 -0
  121. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.sreq +5 -0
  122. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-key.sts +4 -0
  123. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.authz +1 -0
  124. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.creq +8 -0
  125. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.req +4 -0
  126. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.sreq +5 -0
  127. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-order-value.sts +4 -0
  128. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.authz +1 -0
  129. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.creq +8 -0
  130. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.req +4 -0
  131. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.sreq +5 -0
  132. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query-unreserved.sts +4 -0
  133. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.authz +1 -0
  134. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.creq +8 -0
  135. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.req +4 -0
  136. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.sreq +5 -0
  137. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-query.sts +4 -0
  138. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.authz +1 -0
  139. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.creq +8 -0
  140. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.req +4 -0
  141. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.sreq +5 -0
  142. data/spec/fixtures/aws4_test_suite/pass/get-vanilla-ut8-query.sts +4 -0
  143. data/spec/fixtures/aws4_test_suite/pass/get-vanilla.authz +1 -0
  144. data/spec/fixtures/aws4_test_suite/pass/get-vanilla.creq +8 -0
  145. data/spec/fixtures/aws4_test_suite/pass/get-vanilla.req +4 -0
  146. data/spec/fixtures/aws4_test_suite/pass/get-vanilla.sreq +5 -0
  147. data/spec/fixtures/aws4_test_suite/pass/get-vanilla.sts +4 -0
  148. data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.authz +1 -0
  149. data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.creq +8 -0
  150. data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.req +4 -0
  151. data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.sreq +5 -0
  152. data/spec/fixtures/aws4_test_suite/pass/post-header-key-case.sts +4 -0
  153. data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.authz +1 -0
  154. data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.creq +9 -0
  155. data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.req +5 -0
  156. data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.sreq +6 -0
  157. data/spec/fixtures/aws4_test_suite/pass/post-header-key-sort.sts +4 -0
  158. data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.authz +1 -0
  159. data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.creq +9 -0
  160. data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.req +5 -0
  161. data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.sreq +6 -0
  162. data/spec/fixtures/aws4_test_suite/pass/post-header-value-case.sts +4 -0
  163. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.authz +1 -0
  164. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.creq +8 -0
  165. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.req +4 -0
  166. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.sreq +5 -0
  167. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-empty-query-value.sts +4 -0
  168. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.authz +1 -0
  169. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.creq +8 -0
  170. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.req +4 -0
  171. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.sreq +5 -0
  172. data/spec/fixtures/aws4_test_suite/pass/post-vanilla-query.sts +4 -0
  173. data/spec/fixtures/aws4_test_suite/pass/post-vanilla.authz +1 -0
  174. data/spec/fixtures/aws4_test_suite/pass/post-vanilla.creq +8 -0
  175. data/spec/fixtures/aws4_test_suite/pass/post-vanilla.req +4 -0
  176. data/spec/fixtures/aws4_test_suite/pass/post-vanilla.sreq +5 -0
  177. data/spec/fixtures/aws4_test_suite/pass/post-vanilla.sts +4 -0
  178. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.authz +1 -0
  179. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.creq +9 -0
  180. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.req +6 -0
  181. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.sreq +7 -0
  182. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded-parameters.sts +4 -0
  183. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.authz +1 -0
  184. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.creq +9 -0
  185. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.req +6 -0
  186. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.sreq +7 -0
  187. data/spec/fixtures/aws4_test_suite/pass/post-x-www-form-urlencoded.sts +4 -0
  188. data/spec/integration/.gitkeep +0 -0
  189. data/spec/integration/support/.keep +0 -0
  190. data/spec/integration_spec_helper.rb +3 -0
  191. data/spec/spec_helper.rb +45 -0
  192. data/spec/support/.gitkeep +0 -0
  193. data/spec/unit/.gitkeep +0 -0
  194. data/spec/unit/lib/grape_api_signature/authorization_spec.rb +79 -0
  195. data/spec/unit/lib/grape_api_signature/aws_auth_parser_spec.rb +25 -0
  196. data/spec/unit/support/.keep +0 -0
  197. data/spec/unit_spec_helper.rb +3 -0
  198. data/vendor/assets/javascripts/hmac-sha256.js +18 -0
  199. 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
@@ -0,0 +1,3 @@
1
+ # This file is automatically loaded when `bundle console` is run. You can add
2
+ # fixtures and/or initialization code here to make experimenting with your gem
3
+ # easier.
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
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
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
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1
4
+ - 2.0
5
+ script: bundle exec rake spec rubocop
6
+ cache: bundler
data/.versions.conf ADDED
@@ -0,0 +1,4 @@
1
+ ruby=ruby-2.1.0
2
+ ruby-gemset=gem_grape_api_signature
3
+ #ruby-gem-install=bundler rake
4
+ #ruby-bundle-install=true
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