drillbit 1.1.0 → 2.0.0

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