drillbit 0.0.1

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