drillbit 1.1.0 → 2.0.0

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 (49) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/lib/drillbit.rb +3 -1
  5. data/lib/drillbit/authorizable_resource.rb +6 -0
  6. data/lib/drillbit/authorizers/parameters/filtering.rb +1 -0
  7. data/lib/drillbit/authorizers/parameters/resource.rb +59 -0
  8. data/lib/drillbit/errors/invalid_request_body.rb +29 -0
  9. data/lib/drillbit/middleware/api_request_validator.rb +40 -0
  10. data/lib/drillbit/middleware/parameter_parser.rb +61 -0
  11. data/lib/drillbit/middleware/token_processor.rb +26 -0
  12. data/lib/drillbit/requests/base.rb +8 -5
  13. data/lib/drillbit/responses/invalid_request_body.rb +18 -0
  14. data/lib/drillbit/tokens/json_web_token.rb +9 -1
  15. data/lib/drillbit/version.rb +1 -1
  16. data/spec/drillbit/accept_header_spec.rb +2 -2
  17. data/spec/drillbit/authorizers/parameters/filtering_spec.rb +4 -4
  18. data/spec/drillbit/authorizers/parameters/resource_spec.rb +4 -4
  19. data/spec/drillbit/authorizers/parameters_spec.rb +3 -3
  20. data/spec/drillbit/authorizers/query_spec.rb +3 -3
  21. data/spec/drillbit/authorizers/scope_spec.rb +3 -3
  22. data/spec/drillbit/errors/invalid_api_request_spec.rb +3 -3
  23. data/spec/drillbit/errors/invalid_request_body_spec.rb +25 -0
  24. data/spec/drillbit/errors/invalid_subdomain_spec.rb +3 -3
  25. data/spec/drillbit/errors/invalid_token_spec.rb +3 -3
  26. data/spec/drillbit/invalid_subdomain_spec.rb +3 -3
  27. data/spec/drillbit/invalid_token_spec.rb +3 -3
  28. data/spec/drillbit/matchers/accept_header_spec.rb +3 -3
  29. data/spec/drillbit/matchers/subdomain_spec.rb +3 -3
  30. data/spec/drillbit/matchers/version_spec.rb +3 -3
  31. data/spec/drillbit/middleware/{api_request_spec.rb → api_request_validator_spec.rb} +11 -46
  32. data/spec/drillbit/middleware/parameter_parser_spec.rb +184 -0
  33. data/spec/drillbit/middleware/token_processor_spec.rb +27 -0
  34. data/spec/drillbit/requests/base_spec.rb +3 -3
  35. data/spec/drillbit/requests/rack_spec.rb +3 -3
  36. data/spec/drillbit/requests/rails_spec.rb +3 -3
  37. data/spec/drillbit/resource/model_spec.rb +3 -3
  38. data/spec/drillbit/resource/processors/filtering_spec.rb +4 -4
  39. data/spec/drillbit/resource/processors/indexing_spec.rb +4 -4
  40. data/spec/drillbit/resource/processors/paging_spec.rb +4 -4
  41. data/spec/drillbit/resource/processors/sorting_spec.rb +4 -4
  42. data/spec/drillbit/tokens/base64_spec.rb +3 -3
  43. data/spec/drillbit/tokens/json_web_token_spec.rb +11 -3
  44. data/spec/drillbit/tokens/json_web_tokens/password_reset_spec.rb +4 -4
  45. metadata +15 -8
  46. metadata.gz.sig +0 -0
  47. data/lib/drillbit/middleware/api_request.rb +0 -49
  48. data/lib/drillbit/parameters.rb +0 -22
  49. data/spec/drillbit/parameters_spec.rb +0 -49
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'drillbit/errors/invalid_request_body'
4
+
5
+ module Drillbit
6
+ module Errors
7
+ describe InvalidRequestBody do
8
+ let(:error) { InvalidRequestBody.new }
9
+
10
+ it 'has a status of 400' do
11
+ expect(error.http_status).to eql 400
12
+ end
13
+
14
+ it 'has a code' do
15
+ expect(error.code).to eql 'errors.invalid_request_body'
16
+ end
17
+
18
+ it 'can output the detail' do
19
+ expect(error.detail).to eql \
20
+ 'The information you attempted to send in the request cannot be parsed as ' \
21
+ 'a valid JSON document.'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -2,9 +2,9 @@
2
2
  require 'spec_helper'
3
3
  require 'drillbit/errors/invalid_subdomain'
4
4
 
5
- module Drillbit
6
- module Errors
7
- RSpec.describe InvalidSubdomain do
5
+ module Drillbit
6
+ module Errors
7
+ describe InvalidSubdomain do
8
8
  let(:error) { InvalidSubdomain.new }
9
9
 
10
10
  it 'has a status of 404' do
@@ -2,9 +2,9 @@
2
2
  require 'spec_helper'
3
3
  require 'drillbit/errors/invalid_token'
4
4
 
5
- module Drillbit
6
- module Errors
7
- RSpec.describe InvalidToken do
5
+ module Drillbit
6
+ module Errors
7
+ describe InvalidToken do
8
8
  let(:error) { InvalidToken.new }
9
9
 
10
10
  it 'has a status of 401' do
@@ -2,9 +2,9 @@
2
2
  require 'spec_helper'
3
3
  require 'drillbit/responses/invalid_subdomain'
4
4
 
5
- module Drillbit
6
- module Responses
7
- RSpec.describe InvalidSubdomain, singletons: Erratum::Configuration do
5
+ module Drillbit
6
+ module Responses
7
+ describe InvalidSubdomain, singletons: Erratum::Configuration do
8
8
  it 'returns the proper response' do
9
9
  Erratum.configuration.url_mappings = {
10
10
  'external_documentation_urls' => {
@@ -3,9 +3,9 @@ require 'spec_helper'
3
3
  require 'drillbit/responses/invalid_token'
4
4
 
5
5
  # rubocop:disable Metrics/LineLength
6
- module Drillbit
7
- module Responses
8
- RSpec.describe InvalidToken, singletons: Erratum::Configuration do
6
+ module Drillbit
7
+ module Responses
8
+ describe InvalidToken, singletons: Erratum::Configuration do
9
9
  it 'returns the proper response' do
10
10
  Erratum.configuration.url_mappings = {
11
11
  'external_documentation_urls' => {
@@ -4,9 +4,9 @@ require 'drillbit/requests/base'
4
4
  require 'drillbit/matchers/accept_header'
5
5
 
6
6
  # rubocop:disable Metrics/LineLength
7
- module Drillbit
8
- module Matchers
9
- RSpec.describe AcceptHeader do
7
+ module Drillbit
8
+ module Matchers
9
+ describe AcceptHeader do
10
10
  it 'matches if the subdomain is API and the accept header is valid' do
11
11
  env = {
12
12
  'HTTP_ACCEPT' => 'application/vnd.westeros+redkeep;version=1.0.0',
@@ -4,9 +4,9 @@ require 'drillbit/requests/base'
4
4
  require 'drillbit/matchers/subdomain'
5
5
  require 'drillbit/configuration'
6
6
 
7
- module Drillbit
8
- module Matchers
9
- RSpec.describe Subdomain do
7
+ module Drillbit
8
+ module Matchers
9
+ describe Subdomain do
10
10
  before(:each) do
11
11
  Drillbit.configuration.allowed_subdomains = %w{api}
12
12
  Drillbit.configuration.allowed_api_subdomains = %w{api}
@@ -3,9 +3,9 @@ require 'spec_helper'
3
3
  require 'drillbit/requests/base'
4
4
  require 'drillbit/matchers/version'
5
5
 
6
- module Drillbit
7
- module Matchers
8
- RSpec.describe Version do
6
+ module Drillbit
7
+ module Matchers
8
+ describe Version do
9
9
  context 'when the version is passed in the accept header' do
10
10
  it 'does not match if the subdomain is API but the requested version does not ' \
11
11
  'equal the version constraint' do
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
  require 'spec_helper'
3
- require 'drillbit/middleware/api_request'
3
+ require 'drillbit/middleware/api_request_validator'
4
4
 
5
5
  # rubocop:disable Metrics/LineLength
6
- module Drillbit
7
- module Middleware
8
- RSpec.describe ApiRequest, singletons: Erratum::Configuration do
6
+ module Drillbit
7
+ module Middleware
8
+ describe ApiRequestValidator, singletons: Erratum::Configuration do
9
9
  let(:app) { ->(_env) { [200, {}, 'response'] } }
10
10
 
11
11
  before(:each) do
@@ -26,7 +26,7 @@ RSpec.describe ApiRequest, singletons: Erratum::Configuration do
26
26
  end
27
27
 
28
28
  it 'allows requests for allowed subdomains without accept headers' do
29
- api_request_middleware = ApiRequest.new(app)
29
+ api_request_middleware = ApiRequestValidator.new(app)
30
30
 
31
31
  request = {
32
32
  'HTTP_HOST' => 'westeros.example.com',
@@ -42,7 +42,7 @@ RSpec.describe ApiRequest, singletons: Erratum::Configuration do
42
42
  end
43
43
 
44
44
  it 'does not allow requests if they are not for an allowed subdomain' do
45
- api_request_middleware = ApiRequest.new(app)
45
+ api_request_middleware = ApiRequestValidator.new(app)
46
46
 
47
47
  request = {
48
48
  'HTTP_HOST' => 'notvalid.example.com',
@@ -78,7 +78,7 @@ RSpec.describe ApiRequest, singletons: Erratum::Configuration do
78
78
  it 'does not allow requests if they are for an allowed subdomain but does ' \
79
79
  'not have a valid accept header' do
80
80
 
81
- api_request_middleware = ApiRequest.new(app)
81
+ api_request_middleware = ApiRequestValidator.new(app)
82
82
 
83
83
  request = {
84
84
  'HTTP_HOST' => 'api.example.com',
@@ -112,7 +112,7 @@ RSpec.describe ApiRequest, singletons: Erratum::Configuration do
112
112
  end
113
113
 
114
114
  it 'does allow requests if both the subdomain and the accept header are valid' do
115
- api_request_middleware = ApiRequest.new(app)
115
+ api_request_middleware = ApiRequestValidator.new(app)
116
116
 
117
117
  request = {
118
118
  'HTTP_HOST' => 'api.example.com',
@@ -129,7 +129,7 @@ RSpec.describe ApiRequest, singletons: Erratum::Configuration do
129
129
 
130
130
  it 'does allow requests if the subdomain, the accept header and the token are valid' do
131
131
  Drillbit.configuration.token_private_key = test_private_key
132
- api_request_middleware = ApiRequest.new(app)
132
+ api_request_middleware = ApiRequestValidator.new(app)
133
133
 
134
134
  request = {
135
135
  'HTTP_HOST' => 'api.example.com',
@@ -145,43 +145,8 @@ RSpec.describe ApiRequest, singletons: Erratum::Configuration do
145
145
  expect(response).to eql 'response'
146
146
  end
147
147
 
148
- it 'returns the proper response if the token is invalid' do
149
- Drillbit.configuration.token_private_key = test_private_key
150
- api_request_middleware = ApiRequest.new(app)
151
-
152
- request = {
153
- 'HTTP_HOST' => 'api.example.com',
154
- 'HTTP_ACCEPT' => 'application/vnd.westeros+redkeep;version=1.0.0',
155
- 'HTTP_AUTHORIZATION' => "Token #{invalid_jwe_token}",
156
- 'QUERY_STRING' => 'accept=application/vnd.westeros+redkeep;version=1.0.0',
157
- }
158
-
159
- _status, _headers, response = api_request_middleware.call(request)
160
-
161
- expect(response.first).to include 'errors.invalid_token'
162
- end
163
-
164
- it 'converts JSON API compliant dasherized query params to underscored' do
165
- app = ->(env) { [200, env, 'response'] }
166
- api_request_middleware = ApiRequest.new(app)
167
-
168
- request = {
169
- 'HTTP_HOST' => 'api.example.com',
170
- 'HTTP_ACCEPT' => 'application/vnd.westeros+redkeep;version=1.0.0',
171
- 'QUERY_STRING' => 'hello-there=bob-jones&' \
172
- 'nice-to-meet=you-bob&' \
173
- 'hows-the-weather=today-bob',
174
- }
175
-
176
- _status, headers, _response = api_request_middleware.call(request)
177
-
178
- expect(headers['QUERY_STRING']).to eql 'hello_there=bob-jones&' \
179
- 'nice_to_meet=you-bob&' \
180
- 'hows_the_weather=today-bob'
181
- end
182
-
183
148
  it 'properly converts the content type for Rails when it is the only one' do
184
- api_request_middleware = ApiRequest.new(app)
149
+ api_request_middleware = ApiRequestValidator.new(app)
185
150
 
186
151
  request = {
187
152
  'CONTENT_TYPE' => 'application/vnd.api+json',
@@ -199,7 +164,7 @@ RSpec.describe ApiRequest, singletons: Erratum::Configuration do
199
164
  end
200
165
 
201
166
  it 'properly converts the content type for Rails when it is not the only one' do
202
- api_request_middleware = ApiRequest.new(app)
167
+ api_request_middleware = ApiRequestValidator.new(app)
203
168
 
204
169
  request = {
205
170
  'CONTENT_TYPE' => 'application/vnd.api+json;other',
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'drillbit/middleware/parameter_parser'
4
+
5
+ module Drillbit
6
+ module Middleware
7
+ describe ParameterParser do
8
+ let(:app) { ->(_env) { [200, {}, 'response'] } }
9
+
10
+ it 'converts JSON API compliant dasherized query params to underscored' do
11
+ app = ->(env) { [200, env, 'response'] }
12
+ middleware = ParameterParser.new(app)
13
+
14
+ request = {
15
+ 'QUERY_STRING' => 'hello-there=bob-jones&' \
16
+ 'nice-to-meet=you-bob&' \
17
+ 'hows-the-weather=today-bob',
18
+ }
19
+
20
+ _status, headers, _response = middleware.call(request)
21
+
22
+ expect(headers['QUERY_STRING']).to eql 'hello_there=bob-jones&' \
23
+ 'nice_to_meet=you-bob&' \
24
+ 'hows_the_weather=today-bob'
25
+ end
26
+
27
+ it 'does not touch query params with no dashes' do
28
+ app = ->(env) { [200, env, 'response'] }
29
+ middleware = ParameterParser.new(app)
30
+
31
+ request = {
32
+ 'QUERY_STRING' => 'hello_there=bob-jones',
33
+ }
34
+
35
+ _status, headers, _response = middleware.call(request)
36
+
37
+ expect(headers['QUERY_STRING']).to eql 'hello_there=bob-jones'
38
+ end
39
+
40
+ it 'can handle weirdly formatted query string parameters' do
41
+ app = ->(env) { [200, env, 'response'] }
42
+ middleware = ParameterParser.new(app)
43
+
44
+ request = {
45
+ 'QUERY_STRING' => 'hello-there=bob-jones&' \
46
+ 'nice-to-meet=you-bob&=',
47
+ }
48
+
49
+ _status, headers, _response = middleware.call(request)
50
+
51
+ expect(headers['QUERY_STRING']).to eql 'hello_there=bob-jones&' \
52
+ 'nice_to_meet=you-bob&='
53
+ end
54
+
55
+ it 'can handle query string parameters with no values' do
56
+ app = ->(env) { [200, env, 'response'] }
57
+ middleware = ParameterParser.new(app)
58
+
59
+ request = {
60
+ 'QUERY_STRING' => 'hello-there&nice-to-meet=you-bob&=',
61
+ }
62
+
63
+ _status, headers, _response = middleware.call(request)
64
+
65
+ expect(headers['QUERY_STRING']).to eql 'hello_there&nice_to_meet=you-bob&='
66
+ end
67
+
68
+ it 'can handle query string parameters with parameter name' do
69
+ app = ->(env) { [200, env, 'response'] }
70
+ middleware = ParameterParser.new(app)
71
+
72
+ request = {
73
+ 'QUERY_STRING' => 'hello-there=bob-jones&=you-bob&nice-to-meet=you-bob&=',
74
+ }
75
+
76
+ _status, headers, _response = middleware.call(request)
77
+
78
+ expect(headers['QUERY_STRING']).to eql 'hello_there=bob-jones&' \
79
+ '=you-bob&' \
80
+ 'nice_to_meet=you-bob&='
81
+ end
82
+
83
+ it 'converts JSON API compliant dasherized request params to underscored' do
84
+ app = ->(env) { [200, env, 'response'] }
85
+ middleware = ParameterParser.new(app)
86
+
87
+ request = {
88
+ 'CONTENT_LENGTH' => '1',
89
+ 'CONTENT_TYPE' => 'application/json',
90
+ 'RACK_INPUT' => <<-HEREDOC
91
+ {
92
+ "single": "word",
93
+ "double-word": "double double",
94
+ "triple": {
95
+ "trippple-tripple": "mcdipple",
96
+ "quad": {
97
+ "another-level": "whoa inception"
98
+ }
99
+ }
100
+ }
101
+ HEREDOC
102
+ }
103
+
104
+ _status, headers, _response = middleware.call(request)
105
+
106
+ expect(JSON.load(headers['RACK_INPUT'])).to eql(
107
+ 'single' => 'word',
108
+ 'double_word' => 'double double',
109
+ 'triple' => {
110
+ 'trippple_tripple' => 'mcdipple',
111
+ 'quad' => {
112
+ 'another_level' => 'whoa inception',
113
+ },
114
+ },
115
+ )
116
+ end
117
+
118
+ it 'does not convert to JSON if the content type is not JSON' do
119
+ app = ->(env) { [200, env, 'response'] }
120
+ middleware = ParameterParser.new(app)
121
+
122
+ request = {
123
+ 'CONTENT_LENGTH' => '1',
124
+ 'CONTENT_TYPE' => 'not_j_s_o_n',
125
+ 'RACK_INPUT' => 'This is not JSON',
126
+ }
127
+
128
+ _status, headers, _response = middleware.call(request)
129
+
130
+ expect(headers['RACK_INPUT']).to eql 'This is not JSON'
131
+ end
132
+
133
+ it 'does not convert to JSON if there is no content' do
134
+ app = ->(env) { [200, env, 'response'] }
135
+ middleware = ParameterParser.new(app)
136
+
137
+ request = {
138
+ 'CONTENT_LENGTH' => '0',
139
+ 'CONTENT_TYPE' => 'application/json',
140
+ 'RACK_INPUT' => 'empty',
141
+ }
142
+
143
+ _status, headers, _response = middleware.call(request)
144
+
145
+ expect(headers['RACK_INPUT']).to eql 'empty'
146
+ end
147
+
148
+ it 'can handle incorrectly formatted JSON' do
149
+ app = ->(env) { [200, env, 'response'] }
150
+ middleware = ParameterParser.new(app)
151
+
152
+ request = {
153
+ 'CONTENT_LENGTH' => '1',
154
+ 'CONTENT_TYPE' => 'application/json',
155
+ 'RACK_INPUT' => 'something blah',
156
+ }
157
+
158
+ status, headers, response = middleware.call(request)
159
+
160
+ expect(status).to eql 400
161
+ expect(headers).to eql({})
162
+ expect(JSON.load(response[0])).to include(
163
+ 'errors' => [
164
+ {
165
+ 'id' => match(/[a-z0-9\-]+/),
166
+ 'links' => {
167
+ 'about' => nil,
168
+ 'documentation' => nil,
169
+ },
170
+ 'status' => 400,
171
+ 'code' => 'errors.invalid_request_body',
172
+ 'title' => 'Invalid Request Body',
173
+ 'detail' => 'The information you attempted to send in the ' \
174
+ 'request cannot be parsed as a valid JSON document.',
175
+ 'source' => {
176
+ 'request_body' => 'something blah',
177
+ },
178
+ },
179
+ ],
180
+ )
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'drillbit/middleware/token_processor'
4
+
5
+ module Drillbit
6
+ module Middleware
7
+ describe TokenProcessor do
8
+ let(:app) { ->(_env) { [200, {}, 'response'] } }
9
+
10
+ it 'returns the proper response if the token is invalid' do
11
+ Drillbit.configuration.token_private_key = test_private_key
12
+ middleware = TokenProcessor.new(app)
13
+
14
+ request = {
15
+ 'HTTP_HOST' => 'api.example.com',
16
+ 'HTTP_ACCEPT' => 'application/vnd.westeros+redkeep;version=1.0.0',
17
+ 'HTTP_AUTHORIZATION' => "Token #{invalid_jwe_token}",
18
+ 'QUERY_STRING' => 'accept=application/vnd.westeros+redkeep;version=1.0.0',
19
+ }
20
+
21
+ _status, _headers, response = middleware.call(request)
22
+
23
+ expect(response.first).to include 'errors.invalid_token'
24
+ end
25
+ end
26
+ end
27
+ end
@@ -3,9 +3,9 @@ require 'ostruct'
3
3
  require 'spec_helper'
4
4
  require 'drillbit/requests/base'
5
5
 
6
- module Drillbit
7
- module Requests
8
- RSpec.describe Base do
6
+ module Drillbit
7
+ module Requests
8
+ describe Base do
9
9
  it 'can resolve itself by returning itself' do
10
10
  raw_request = Base.new(token_private_key: '', request: {})
11
11
  resolved_request = Base.resolve(raw_request)