grape_api_signature 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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