drillbit 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 (83) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE.txt +19 -0
  5. data/README.md +2 -0
  6. data/Rakefile +2 -0
  7. data/lib/drillbit.rb +19 -0
  8. data/lib/drillbit/accept_header.rb +50 -0
  9. data/lib/drillbit/authorizable_resource.rb +160 -0
  10. data/lib/drillbit/authorizers/parameters.rb +24 -0
  11. data/lib/drillbit/authorizers/parameters/filtering.rb +50 -0
  12. data/lib/drillbit/authorizers/parameters/resource.rb +11 -0
  13. data/lib/drillbit/authorizers/query.rb +40 -0
  14. data/lib/drillbit/authorizers/scope.rb +30 -0
  15. data/lib/drillbit/configuration.rb +36 -0
  16. data/lib/drillbit/errors/invalid_api_request.rb +29 -0
  17. data/lib/drillbit/errors/invalid_subdomain.rb +29 -0
  18. data/lib/drillbit/errors/invalid_token.rb +22 -0
  19. data/lib/drillbit/matchers/accept_header.rb +16 -0
  20. data/lib/drillbit/matchers/generic.rb +30 -0
  21. data/lib/drillbit/matchers/subdomain.rb +31 -0
  22. data/lib/drillbit/matchers/version.rb +30 -0
  23. data/lib/drillbit/middleware/api_request.rb +49 -0
  24. data/lib/drillbit/parameters.rb +22 -0
  25. data/lib/drillbit/parameters/filter.rb +57 -0
  26. data/lib/drillbit/parameters/index.rb +31 -0
  27. data/lib/drillbit/parameters/page.rb +28 -0
  28. data/lib/drillbit/parameters/sort.rb +32 -0
  29. data/lib/drillbit/requests/base.rb +114 -0
  30. data/lib/drillbit/requests/rack.rb +50 -0
  31. data/lib/drillbit/requests/rails.rb +44 -0
  32. data/lib/drillbit/resource.rb +14 -0
  33. data/lib/drillbit/resource/model.rb +41 -0
  34. data/lib/drillbit/resource/naming.rb +33 -0
  35. data/lib/drillbit/resource/processors/filtering.rb +66 -0
  36. data/lib/drillbit/resource/processors/indexing.rb +40 -0
  37. data/lib/drillbit/resource/processors/paging.rb +46 -0
  38. data/lib/drillbit/resource/processors/sorting.rb +42 -0
  39. data/lib/drillbit/responses/invalid_api_request.rb +18 -0
  40. data/lib/drillbit/responses/invalid_subdomain.rb +18 -0
  41. data/lib/drillbit/responses/invalid_token.rb +20 -0
  42. data/lib/drillbit/serializers/json_api.rb +10 -0
  43. data/lib/drillbit/tokens/base64.rb +45 -0
  44. data/lib/drillbit/tokens/base64s/invalid.rb +14 -0
  45. data/lib/drillbit/tokens/base64s/null.rb +14 -0
  46. data/lib/drillbit/tokens/invalid.rb +26 -0
  47. data/lib/drillbit/tokens/json_web_token.rb +112 -0
  48. data/lib/drillbit/tokens/json_web_tokens/invalid.rb +14 -0
  49. data/lib/drillbit/tokens/json_web_tokens/null.rb +14 -0
  50. data/lib/drillbit/tokens/null.rb +26 -0
  51. data/lib/drillbit/version.rb +4 -0
  52. data/spec/drillbit/accept_header_spec.rb +112 -0
  53. data/spec/drillbit/authorizers/parameters/filtering_spec.rb +71 -0
  54. data/spec/drillbit/authorizers/parameters/resource_spec.rb +12 -0
  55. data/spec/drillbit/authorizers/parameters_spec.rb +17 -0
  56. data/spec/drillbit/authorizers/query_spec.rb +21 -0
  57. data/spec/drillbit/authorizers/scope_spec.rb +20 -0
  58. data/spec/drillbit/errors/invalid_api_request_spec.rb +31 -0
  59. data/spec/drillbit/errors/invalid_subdomain_spec.rb +31 -0
  60. data/spec/drillbit/errors/invalid_token_spec.rb +24 -0
  61. data/spec/drillbit/invalid_subdomain_spec.rb +46 -0
  62. data/spec/drillbit/invalid_token_spec.rb +44 -0
  63. data/spec/drillbit/matchers/accept_header_spec.rb +114 -0
  64. data/spec/drillbit/matchers/subdomain_spec.rb +78 -0
  65. data/spec/drillbit/matchers/version_spec.rb +86 -0
  66. data/spec/drillbit/middleware/api_request_spec.rb +220 -0
  67. data/spec/drillbit/parameters_spec.rb +49 -0
  68. data/spec/drillbit/requests/base_spec.rb +37 -0
  69. data/spec/drillbit/requests/rack_spec.rb +253 -0
  70. data/spec/drillbit/requests/rails_spec.rb +264 -0
  71. data/spec/drillbit/resource/model_spec.rb +64 -0
  72. data/spec/drillbit/resource/processors/filtering_spec.rb +106 -0
  73. data/spec/drillbit/resource/processors/indexing_spec.rb +46 -0
  74. data/spec/drillbit/resource/processors/paging_spec.rb +74 -0
  75. data/spec/drillbit/resource/processors/sorting_spec.rb +66 -0
  76. data/spec/drillbit/tokens/base64_spec.rb +44 -0
  77. data/spec/drillbit/tokens/json_web_token_spec.rb +135 -0
  78. data/spec/fixtures/test_rsa_key +27 -0
  79. data/spec/fixtures/test_rsa_key.pub +9 -0
  80. data/spec/spec_helper.rb +4 -0
  81. data/spec/support/private_keys.rb +42 -0
  82. metadata +244 -0
  83. metadata.gz.sig +0 -0
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+ require 'ostruct'
3
+ require 'spec_helper'
4
+ require 'drillbit/requests/rails'
5
+
6
+ # rubocop:disable Metrics/LineLength
7
+ module Drillbit
8
+ module Requests
9
+ RSpec.describe Rails do
10
+ it 'finds the accept header from the headers if it is valid' do
11
+ raw_request = OpenStruct.new(
12
+ headers: {
13
+ 'X-Application-Name' => 'westeros',
14
+ 'Accept' => 'application/vnd.westeros+redkeep;version=10.0',
15
+ },
16
+ params: {},
17
+ )
18
+ request = Rails.new(request: raw_request)
19
+
20
+ expect(request.accept_header.to_s).to eql 'application/vnd.westeros+redkeep;version=10.0'
21
+ end
22
+
23
+ it 'finds the accept header from the headers if it is invalid but there is no ' \
24
+ 'accept header in the params' do
25
+
26
+ raw_request = OpenStruct.new(
27
+ headers: {
28
+ 'X-Application-Name' => 'westeros',
29
+ 'Accept' => 'invalid/vnd.westeros+redkeep;version=10.0',
30
+ },
31
+ params: {},
32
+ )
33
+ request = Rails.new(request: raw_request)
34
+
35
+ expect(request.accept_header.to_s).to eql 'invalid/vnd.westeros+redkeep;version=10.0'
36
+ end
37
+
38
+ it 'finds the accept header from the params if it is valid' do
39
+ raw_request = OpenStruct.new(
40
+ headers: {
41
+ 'X-Application-Name' => 'westeros',
42
+ },
43
+ params: { 'accept' => 'application/vnd.westeros+redkeep;version=10.0' },
44
+ )
45
+ request = Rails.new(request: raw_request)
46
+
47
+ expect(request.accept_header.to_s).to eql 'application/vnd.westeros+redkeep;version=10.0'
48
+ end
49
+
50
+ it 'finds the authorization token from the header' do
51
+ raw_request = OpenStruct.new(
52
+ headers: {
53
+ 'HTTP_AUTHORIZATION' => "Token #{valid_jwe_token}",
54
+ },
55
+ params: {},
56
+ )
57
+ request = Rails.new(token_private_key: test_private_key,
58
+ request: raw_request)
59
+
60
+ expect(request.authorization_token).to be_valid
61
+ expect(request.authorization_token.to_h).to eql(
62
+ [
63
+ { 'bar' => 'baz' },
64
+ { 'typ' => 'JWT', 'alg' => 'RS256' },
65
+ ],
66
+ )
67
+ end
68
+
69
+ it 'finds the Base64 token from the header' do
70
+ raw_request = OpenStruct.new(
71
+ headers: {
72
+ 'HTTP_AUTHORIZATION' => "Basic #{valid_b64_token}",
73
+ },
74
+ params: {},
75
+ )
76
+ request = Rails.new(token_private_key: test_private_key,
77
+ request: raw_request)
78
+
79
+ expect(request.authorization_token).to be_valid
80
+ expect(request.authorization_token.to_h).to eql(
81
+ [
82
+ { 'token' => valid_b64_token },
83
+ { 'typ' => 'base64' },
84
+ ],
85
+ )
86
+ end
87
+
88
+ it 'finds a null token from the header if there is no header' do
89
+ raw_request = OpenStruct.new(
90
+ headers: {},
91
+ params: {},
92
+ )
93
+ request = Rails.new(token_private_key: test_private_key,
94
+ request: raw_request)
95
+
96
+ expect(request.authorization_token).to be_valid
97
+ expect(request.authorization_token).to be_blank
98
+ end
99
+
100
+ it 'ignores incorrectly passed in tokens since we do not know what to do' do
101
+ raw_request = OpenStruct.new(
102
+ headers: {
103
+ 'HTTP_AUTHORIZATION' => valid_jwe_token.to_s,
104
+ },
105
+ params: {},
106
+ )
107
+ request = Rails.new(token_private_key: test_private_key,
108
+ request: raw_request)
109
+
110
+ expect(request.authorization_token).to be_valid
111
+ expect(request.authorization_token).to be_blank
112
+ end
113
+
114
+ it 'finds the authorization token from the params if the authorization token from ' \
115
+ 'the header is invalid and the authorization token from the params is valid' do
116
+
117
+ raw_request = OpenStruct.new(
118
+ headers: {
119
+ 'HTTP_AUTHORIZATION' => "Token #{invalid_jwe_token}",
120
+ },
121
+ params: { 'token_jwt' => valid_jwe_token },
122
+ )
123
+ request = Rails.new(token_private_key: test_private_key,
124
+ request: raw_request)
125
+
126
+ expect(request.authorization_token).to be_valid
127
+ expect(request.authorization_token.to_h).to eql(
128
+ [
129
+ { 'bar' => 'baz' },
130
+ { 'typ' => 'JWT', 'alg' => 'RS256' },
131
+ ],
132
+ )
133
+ end
134
+
135
+ it 'finds the authorization token from the params if the authorization token from ' \
136
+ 'the header is not present and the authorization token from the params is valid' do
137
+
138
+ raw_request = OpenStruct.new(
139
+ headers: {},
140
+ params: { 'token_jwt' => valid_jwe_token },
141
+ )
142
+ request = Rails.new(token_private_key: test_private_key,
143
+ request: raw_request)
144
+
145
+ expect(request.authorization_token).to be_valid
146
+ expect(request.authorization_token.to_h).to eql(
147
+ [
148
+ { 'bar' => 'baz' },
149
+ { 'typ' => 'JWT', 'alg' => 'RS256' },
150
+ ],
151
+ )
152
+ end
153
+
154
+ it 'is a null authorization token if neither authorization token is present' do
155
+ raw_request = OpenStruct.new(
156
+ headers: {},
157
+ params: {},
158
+ )
159
+ request = Rails.new(token_private_key: test_private_key,
160
+ request: raw_request)
161
+
162
+ expect(request.authorization_token).to be_valid
163
+ expect(request.authorization_token.to_h).to eql([{}, {}])
164
+ end
165
+
166
+ it 'finds the JSON web token from the params' do
167
+ raw_request = OpenStruct.new(
168
+ headers: {},
169
+ params: { 'token_jwt' => valid_jwe_token },
170
+ )
171
+ request = Rails.new(token_private_key: test_private_key,
172
+ request: raw_request)
173
+
174
+ expect(request.authorization_token).to be_valid
175
+ expect(request.authorization_token.to_h).to eql(
176
+ [
177
+ { 'bar' => 'baz' },
178
+ { 'typ' => 'JWT', 'alg' => 'RS256' },
179
+ ],
180
+ )
181
+ end
182
+
183
+ it 'finds the generic Base64 web token from the params' do
184
+ raw_request = OpenStruct.new(
185
+ headers: {},
186
+ params: { 'token_b64' => valid_b64_token },
187
+ )
188
+ request = Rails.new(request: raw_request)
189
+
190
+ expect(request.authorization_token).to be_valid
191
+ expect(request.authorization_token.to_h).to eql(
192
+ [
193
+ { 'token' => valid_b64_token },
194
+ { 'typ' => 'base64' },
195
+ ],
196
+ )
197
+ end
198
+
199
+ it 'finds invalid tokens from the params' do
200
+ raw_request = OpenStruct.new(
201
+ headers: {},
202
+ params: { 'token_b64' => 'bla.h' },
203
+ )
204
+ request = Rails.new(request: raw_request)
205
+
206
+ expect(request.authorization_token_from_params).not_to be_valid
207
+ expect(request.authorization_token_from_params).not_to be_blank
208
+
209
+ raw_request = OpenStruct.new(
210
+ headers: {},
211
+ params: { 'token_jwt' => invalid_jwe_token },
212
+ )
213
+ request = Rails.new(token_private_key: test_private_key,
214
+ request: raw_request)
215
+
216
+ expect(request.authorization_token_from_params).not_to be_valid
217
+ expect(request.authorization_token_from_params).not_to be_blank
218
+ end
219
+
220
+ it 'finds the null token from the params if nothing is passed in' do
221
+ raw_request = OpenStruct.new(
222
+ headers: {},
223
+ params: { 'token_b64' => '' },
224
+ )
225
+ request = Rails.new(request: raw_request)
226
+
227
+ expect(request.authorization_token_from_params).to be_valid
228
+ expect(request.authorization_token_from_params).to be_blank
229
+
230
+ raw_request = OpenStruct.new(
231
+ headers: {},
232
+ params: { 'token_jwt' => '' },
233
+ )
234
+ request = Rails.new(request: raw_request)
235
+
236
+ expect(request.authorization_token_from_params).to be_valid
237
+ expect(request.authorization_token_from_params).to be_blank
238
+
239
+ raw_request = OpenStruct.new(
240
+ headers: {},
241
+ params: {},
242
+ )
243
+ request = Rails.new(request: raw_request)
244
+
245
+ expect(request.authorization_token_from_params).to be_valid
246
+ expect(request.authorization_token_from_params).to be_blank
247
+ end
248
+
249
+ it 'defaults to the application name in the configuration if none is found in ' \
250
+ 'the header' do
251
+
252
+ Drillbit.configuration.application_name = 'redkeep'
253
+
254
+ raw_request = OpenStruct.new(
255
+ headers: {},
256
+ params: { 'accept' => 'application/vnd.redkeep+zion;version=10.0' },
257
+ )
258
+ request = Rails.new(request: raw_request)
259
+
260
+ expect(request.accept_header.to_s).to eql 'application/vnd.redkeep+zion;version=10.0'
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'drillbit/resource/model'
4
+
5
+ module Drillbit
6
+ module Resource
7
+ RSpec.describe Model do
8
+ it 'can chain multiple processors together' do
9
+ resource = double
10
+ processed_resource = double
11
+
12
+ model = Model.new(resource: resource,
13
+ parameters: {
14
+ 'filter' => {
15
+ 'query' => 'my_query',
16
+ 'single_arity' => true,
17
+ 'multiple_arity' => 'multi',
18
+ },
19
+ 'sort' => 'my_attribute',
20
+ 'page' => {
21
+ 'number' => 10,
22
+ 'size' => 100,
23
+ },
24
+ })
25
+
26
+ allow(resource).to receive(:single_arity).
27
+ and_return(resource)
28
+ allow(resource).to receive(:multiple_arity).
29
+ with('multi').
30
+ and_return(resource)
31
+ allow(resource).to receive(:order).
32
+ with('my_attribute' => 'asc').
33
+ and_return(resource)
34
+ allow(resource).to receive(:page).
35
+ with(10).
36
+ and_return(resource)
37
+ allow(resource).to receive(:per).
38
+ with(100).
39
+ and_return(resource)
40
+ allow(resource).to receive(:for_query).
41
+ with('my_query').
42
+ and_return(processed_resource)
43
+
44
+ allow(processed_resource).to receive(:total_pages).
45
+ and_return(10)
46
+ allow(processed_resource).to receive(:current_page).
47
+ and_return(5)
48
+ allow(processed_resource).to receive(:prev_page).
49
+ and_return(4)
50
+ allow(processed_resource).to receive(:next_page).
51
+ and_return(6)
52
+
53
+ expect(model.processed).to eql processed_resource
54
+ expect(model.meta).to eql(
55
+ 'current-page' => 5,
56
+ 'total-pages' => 10,
57
+ 'previous-page' => 4,
58
+ 'next-page' => 6,
59
+ 'sort' => { 'my_attribute' => 'asc' },
60
+ )
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'drillbit/resource/processors/filtering'
4
+
5
+ module Drillbit
6
+ module Resource
7
+ module Processors
8
+ RSpec.describe Filtering do
9
+ let(:filtering_resource) { double }
10
+
11
+ it 'can return the resource if not filtered parameters are passed in' do
12
+ filtering = Filtering.new(filtering_resource)
13
+
14
+ expect(filtering.processed).to eql filtering_resource
15
+ end
16
+
17
+ it 'can return a queried resource' do
18
+ filtering = Filtering.new(filtering_resource,
19
+ 'filter' => {
20
+ 'stuff' => 'blah',
21
+ })
22
+
23
+ allow(filtering_resource).to receive(:for_stuff).
24
+ with('blah').
25
+ and_return 'stuffed'
26
+
27
+ expect(filtering.processed).to eql 'stuffed'
28
+ end
29
+
30
+ it 'does not try to query if the resource cannot query for that thing' do
31
+ filtering = Filtering.new(filtering_resource,
32
+ 'filter' => {
33
+ 'whatever' => 'blah',
34
+ })
35
+
36
+ expect(filtering.processed).to eql filtering_resource
37
+ end
38
+
39
+ it 'can query for something that does not take arguments' do
40
+ filtering = Filtering.new(filtering_resource,
41
+ 'filter' => {
42
+ 'stuff' => 'blah',
43
+ })
44
+
45
+ allow(filtering_resource).to receive(:stuff).
46
+ and_return 'stuffed'
47
+
48
+ expect(filtering.processed).to eql 'stuffed'
49
+ end
50
+
51
+ it 'can query for something that does not take arguments' do
52
+ filtering = Filtering.new(filtering_resource,
53
+ 'filter' => {
54
+ 'stuff' => 'blah',
55
+ 'other_stuff' => 'other_blah',
56
+ })
57
+
58
+ allow(filtering_resource).to receive(:for_stuff).
59
+ with('blah').
60
+ and_return filtering_resource
61
+ allow(filtering_resource).to receive(:other_stuff).
62
+ and_return 'other_stuffed'
63
+
64
+ expect(filtering.processed).to eql 'other_stuffed'
65
+ end
66
+
67
+ it 'can properly format numerical ranges' do
68
+ filtering = Filtering.new(filtering_resource,
69
+ 'filter' => {
70
+ 'stuff' => '100...200',
71
+ 'infinity' => '9...Infinity',
72
+ 'other_stuff' => '3_333.33..8_8__8.0',
73
+ })
74
+
75
+ allow(filtering_resource).to receive(:for_stuff).
76
+ with(100.0...200.0).
77
+ and_return filtering_resource
78
+ allow(filtering_resource).to receive(:infinity).
79
+ with(9.0...9_999_999).
80
+ and_return filtering_resource
81
+ allow(filtering_resource).to receive(:other_stuff).
82
+ with(3333.33..888.0).
83
+ and_return 'other_stuffed'
84
+
85
+ expect(filtering.processed).to eql 'other_stuffed'
86
+ end
87
+
88
+ it 'can handle objects (eg ActiveRelation) that store their proxy class in klass' do
89
+ resource_class = double
90
+ filtering = Filtering.new(filtering_resource,
91
+ 'filter' => {
92
+ 'stuff' => 'blah',
93
+ })
94
+
95
+ allow(filtering_resource).to receive(:klass).
96
+ and_return(resource_class)
97
+ allow(resource_class).to receive(:stuff)
98
+ allow(filtering_resource).to receive(:stuff).
99
+ and_return 'stuffed'
100
+
101
+ expect(filtering.processed).to eql 'stuffed'
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'drillbit/resource/processors/indexing'
4
+
5
+ module Drillbit
6
+ class TestIndexClass
7
+ def for_query(_param)
8
+ end
9
+ end
10
+ end
11
+
12
+ module Drillbit
13
+ module Resource
14
+ module Processors
15
+ RSpec.describe Indexing do
16
+ let(:indexing_resource) { double }
17
+
18
+ it 'does not do anything if indexing params are not passed in' do
19
+ indexing = Indexing.new(indexing_resource)
20
+
21
+ expect(indexing.processed).to eql indexing_resource
22
+ end
23
+
24
+ it 'does not do anything if indexing params are passed in but they are blank' do
25
+ indexing = Indexing.new(indexing_resource,
26
+ 'filter' => {
27
+ 'query' => '',
28
+ })
29
+
30
+ expect(indexing.processed).to eql indexing_resource
31
+ end
32
+
33
+ it 'forces a query even if no parameters were passed in' do
34
+ indexing_resource = TestIndexClass.new
35
+ indexing = Indexing.new(indexing_resource)
36
+
37
+ allow(indexing_resource).to receive(:for_query).
38
+ with('*').
39
+ and_return('blah')
40
+
41
+ expect(indexing.processed).to eql 'blah'
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end