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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 477a99cfe9c988e804d76c42b67dd503d71716ca
4
- data.tar.gz: 2cbc77f50321b0d52b0209df5228c564a68f7993
3
+ metadata.gz: 362e3bda774e3c8a662b8dc640281e0ae389c953
4
+ data.tar.gz: ffa6d8b3eb8c83f52496a103378b994302d01fc2
5
5
  SHA512:
6
- metadata.gz: 0a2cf2c6301d8d29a8936e3c73334f074ab0c27087a326ee8228c627dad73c55d8eb39b84bf741ddb3b458d595ff287cc8fe4fd95d6d570decf357c0fe04a140
7
- data.tar.gz: cd9c47f410eeda20b0ff09b0ede87c8febb5cc9a971853cb59b71e2f567fc370887022eda8519a87688ed40a5f78b2055e061f6f4700deacac6f976421ce8ea7
6
+ metadata.gz: b6d144f2c1b07894b0ed5ebf08e308720b882ed3892dea1e55f4a4a77a3dd348d933b56be2f9357b19bc651759380d9b52834274745e3651b76d3f32648ad2da
7
+ data.tar.gz: e0be2925d007e7771501b62fc28d2561445edd900ea584ccc6685c90de6b19cca7df02340e5342fbd7efd41bc0791d5bcc87c7d939e11b827ec0f339b6ff0405
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -13,7 +13,9 @@ require 'drillbit/matchers/version'
13
13
  require 'drillbit/resource'
14
14
  require 'drillbit/serializers/json_api'
15
15
 
16
- require 'drillbit/middleware/api_request'
16
+ require 'drillbit/middleware/api_request_validator'
17
+ require 'drillbit/middleware/token_processor'
18
+ require 'drillbit/middleware/parameter_parser'
17
19
 
18
20
  require 'drillbit/responses/invalid_api_request'
19
21
  require 'drillbit/responses/invalid_subdomain'
@@ -99,6 +99,12 @@ module AuthorizableResource
99
99
  call
100
100
  end
101
101
 
102
+ def authorized_attributes
103
+ @authorized_attributes ||= authorized_params.
104
+ fetch(:data, {}).
105
+ fetch(:attributes, {})
106
+ end
107
+
102
108
  def authorized_resource
103
109
  return nil if RESOURCE_COLLECTION_ACTIONS.include?(action_name)
104
110
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require 'drillbit/authorizers/parameters'
3
3
 
4
+ # :reek:UnusedPrivateMethod
4
5
  module Drillbit
5
6
  module Authorizers
6
7
  class Parameters
@@ -1,10 +1,69 @@
1
1
  # frozen_string_literal: true
2
2
  require 'drillbit/authorizers/parameters'
3
3
 
4
+ # :reek:UnusedPrivateMethod
4
5
  module Drillbit
5
6
  module Authorizers
6
7
  class Parameters
7
8
  class Resource < Authorizers::Parameters
9
+ def call
10
+ params.permit(*authorized_params)
11
+ end
12
+
13
+ private
14
+
15
+ def authorized_params
16
+ @authorized_params ||= [
17
+ data: [
18
+ :type,
19
+ :id,
20
+ {
21
+ attributes: [
22
+ {},
23
+ ],
24
+ relationships: [
25
+ {},
26
+ ],
27
+ },
28
+ ],
29
+ ]
30
+ end
31
+
32
+ def add_authorized_attribute(name)
33
+ param = params.fetch(:data, {}).
34
+ fetch(:attributes, {}).
35
+ fetch(name, nil)
36
+
37
+ if param.class == Array
38
+ authorized_params[0][:data][:attributes][0][name] = []
39
+ else
40
+ authorized_params[0][:data][2][:attributes] << name
41
+ end
42
+ end
43
+
44
+ def add_authorized_attributes(*names)
45
+ names.each do |name|
46
+ add_authorized_attribute(name)
47
+ end
48
+ end
49
+
50
+ def add_authorized_relationship(name)
51
+ param = params.fetch(:data, {}).
52
+ fetch(:relationships, {}).
53
+ fetch(name, nil)
54
+
55
+ if param.class == Array
56
+ authorized_params[0][:data][:relationships][0][name] = []
57
+ else
58
+ authorized_params[0][:data][2][:relationships] << name
59
+ end
60
+ end
61
+
62
+ def add_authorized_relationships(*names)
63
+ names.each do |name|
64
+ add_authorized_relationship(name)
65
+ end
66
+ end
8
67
  end
9
68
  end
10
69
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ require 'erratum'
3
+
4
+ module Drillbit
5
+ module Errors
6
+ class InvalidRequestBody < RuntimeError
7
+ include Erratum::Error
8
+
9
+ attr_accessor :request_body
10
+
11
+ def http_status
12
+ 400
13
+ end
14
+
15
+ def title
16
+ 'Invalid Request Body'
17
+ end
18
+
19
+ def detail
20
+ 'The information you attempted to send in the request cannot be parsed as ' \
21
+ 'a valid JSON document.'
22
+ end
23
+
24
+ def source
25
+ { request_body: request_body }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/configuration'
3
+ require 'drillbit/matchers/subdomain'
4
+ require 'drillbit/matchers/accept_header'
5
+ require 'drillbit/requests/base'
6
+ require 'drillbit/responses/invalid_api_request'
7
+ require 'drillbit/responses/invalid_subdomain'
8
+
9
+ module Drillbit
10
+ module Middleware
11
+ class ApiRequestValidator
12
+ JSON_API_MIME_TYPE_PATTERN = %r{application/vnd\.api\+json(?=\z|;)}
13
+
14
+ def initialize(app)
15
+ @app = app
16
+ end
17
+
18
+ # rubocop:disable Metrics/LineLength
19
+ # :reek:FeatureEnvy
20
+ def call(env)
21
+ env['HTTP_X_APPLICATION_NAME'] = Drillbit.configuration.application_name
22
+
23
+ request = Requests::Base.resolve(env)
24
+ subdomain_matcher = Matchers::Subdomain.new
25
+ accept_header_matcher = Matchers::AcceptHeader.new
26
+
27
+ return Responses::InvalidSubdomain.call(env) unless subdomain_matcher.matches?(request)
28
+ return Responses::InvalidApiRequest.call(env) if subdomain_matcher.matches_api_subdomain?(request) &&
29
+ !accept_header_matcher.matches?(request)
30
+
31
+ env['CONTENT_TYPE'] = env['CONTENT_TYPE'].
32
+ to_s.
33
+ gsub(JSON_API_MIME_TYPE_PATTERN, 'application/json')
34
+
35
+ @app.call(env)
36
+ end
37
+ # rubocop:enable Metrics/LineLength
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+ require 'stringio'
4
+ require 'drillbit/responses/invalid_request_body'
5
+
6
+ module Drillbit
7
+ module Middleware
8
+ class ParameterParser
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ # :reek:FeatureEnvy
14
+ def call(env)
15
+ env['QUERY_STRING'] = underscore_query_string(env['QUERY_STRING'])
16
+
17
+ if env['CONTENT_LENGTH'].to_i > 0 && env['CONTENT_TYPE'] =~ /json/
18
+ if env['rack.input']
19
+ env['rack.input'] = StringIO.new(underscore_request_parameters(env['rack.input']))
20
+ elsif env['RACK_INPUT']
21
+ env['RACK_INPUT'] = underscore_request_parameters(env['RACK_INPUT'])
22
+ end
23
+ end
24
+
25
+ @app.call(env)
26
+ rescue JSON::ParserError => _error
27
+ return Responses::InvalidRequestBody.call(env)
28
+ end
29
+
30
+ private
31
+
32
+ def underscore_request_parameters(request_parameters)
33
+ data = JSON.load(request_parameters)
34
+ data = underscore_parameters(data)
35
+
36
+ JSON.dump(data)
37
+ end
38
+
39
+ # :reek:UtilityFunction
40
+ def underscore_query_string(query_string)
41
+ return query_string unless query_string.respond_to? :gsub
42
+
43
+ query_string.gsub(/(?<=\A|&|\?)[^=&]+/) do |match|
44
+ match.tr('-', '_')
45
+ end
46
+ end
47
+
48
+ # :reek:UtilityFunction
49
+ # :reek:FeatureEnvy
50
+ def underscore_parameters(parameters)
51
+ return parameters unless parameters.is_a? Hash
52
+
53
+ parameters.each_with_object({}) do |(key, value), hash|
54
+ value = underscore_parameters(value) if value.is_a? Hash
55
+
56
+ hash[key.tr('-', '_')] = value
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/requests/base'
3
+ require 'drillbit/responses/invalid_token'
4
+
5
+ module Drillbit
6
+ module Middleware
7
+ class TokenProcessor
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ # :reek:FeatureEnvy
13
+ def call(env)
14
+ request = Requests::Base.resolve(env)
15
+ token = request.authorization_token
16
+
17
+ return Responses::InvalidToken.call(env, application_name: request.application_name) \
18
+ unless token.valid?
19
+
20
+ env['X_JSON_WEB_TOKEN'] = token.to_h
21
+
22
+ @app.call(env)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -32,21 +32,24 @@ class Base
32
32
  end
33
33
  end
34
34
 
35
+ # rubocop:disable Style/ClosingParenthesisIndentation
35
36
  def authorization_token
36
37
  if (
37
38
  !authorization_token_from_header.blank? &&
38
39
  authorization_token_from_header.valid?
39
- ) ||
40
- (
41
- authorization_token_from_params.blank? ||
42
- !authorization_token_from_params.valid?
43
- )
40
+ ) \
41
+ ||
42
+ (
43
+ authorization_token_from_params.blank? ||
44
+ !authorization_token_from_params.valid?
45
+ )
44
46
 
45
47
  authorization_token_from_header
46
48
  else
47
49
  authorization_token_from_params
48
50
  end
49
51
  end
52
+ # rubocop:enable Style/ClosingParenthesisIndentation
50
53
 
51
54
  def application_name
52
55
  raw_request_application_name || Drillbit.configuration.application_name
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/errors/invalid_request_body'
3
+
4
+ module Drillbit
5
+ module Responses
6
+ class InvalidRequestBody
7
+ def self.call(env)
8
+ error = Drillbit::Errors::InvalidRequestBody.new(request_body: env['RACK_INPUT'])
9
+
10
+ [
11
+ error.http_status, # HTTP Status Code
12
+ {}, # Response Headers
13
+ [error.to_json], # Message
14
+ ]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -88,7 +88,15 @@ class JsonWebToken
88
88
  end
89
89
 
90
90
  def blank?
91
- false
91
+ data.empty?
92
+ end
93
+
94
+ def present?
95
+ data.any?
96
+ end
97
+
98
+ def empty?
99
+ data.empty?
92
100
  end
93
101
 
94
102
  def to_h
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Drillbit
3
- VERSION = '1.1.0'
3
+ VERSION = '2.0.0'
4
4
  end
@@ -3,8 +3,8 @@ require 'spec_helper'
3
3
  require 'drillbit/accept_header'
4
4
 
5
5
  # rubocop:disable Metrics/LineLength
6
- module Drillbit
7
- RSpec.describe AcceptHeader do
6
+ module Drillbit
7
+ describe AcceptHeader do
8
8
  it 'can validate an accept header with all the pieces of information' do
9
9
  header = AcceptHeader.new(application: 'westeros',
10
10
  header: 'application/vnd.westeros+redkeep;version=1.0.0')
@@ -2,10 +2,10 @@
2
2
  require 'rspeckled'
3
3
  require 'drillbit/authorizers/parameters/filtering'
4
4
 
5
- module Drillbit
6
- module Authorizers
7
- class Parameters
8
- RSpec.describe Filtering do
5
+ module Drillbit
6
+ module Authorizers
7
+ class Parameters
8
+ describe Filtering do
9
9
  let(:params) { { filter: { name: 'Bill', age: 26 } } }
10
10
 
11
11
  it 'can authorize new filter parameters', verify: false do
@@ -2,10 +2,10 @@
2
2
  require 'rspeckled'
3
3
  require 'drillbit/authorizers/parameters/resource'
4
4
 
5
- module Drillbit
6
- module Authorizers
7
- class Parameters
8
- RSpec.describe Resource do
5
+ module Drillbit
6
+ module Authorizers
7
+ class Parameters
8
+ describe Resource do
9
9
  end
10
10
  end
11
11
  end
@@ -2,9 +2,9 @@
2
2
  require 'rspeckled'
3
3
  require 'drillbit/authorizers/parameters'
4
4
 
5
- module Drillbit
6
- module Authorizers
7
- RSpec.describe Parameters do
5
+ module Drillbit
6
+ module Authorizers
7
+ describe Parameters do
8
8
  it 'defaults to nothing' do
9
9
  parameters = Parameters.new(token: '123',
10
10
  user: 'my_user',
@@ -2,9 +2,9 @@
2
2
  require 'rspeckled'
3
3
  require 'drillbit/authorizers/query'
4
4
 
5
- module Drillbit
6
- module Authorizers
7
- RSpec.describe Query do
5
+ module Drillbit
6
+ module Authorizers
7
+ describe Query do
8
8
  it 'does not authorize the resource by default' do
9
9
  authorizer = Query.new(token: '123',
10
10
  user: 'my_user',
@@ -3,9 +3,9 @@ require 'rspeckled'
3
3
  require 'ostruct'
4
4
  require 'drillbit/authorizers/scope'
5
5
 
6
- module Drillbit
7
- module Authorizers
8
- RSpec.describe Scope do
6
+ module Drillbit
7
+ module Authorizers
8
+ describe Scope do
9
9
  it 'defaults to nothing' do
10
10
  scope = Scope.new(token: '123',
11
11
  user: 'my_user',
@@ -2,9 +2,9 @@
2
2
  require 'spec_helper'
3
3
  require 'drillbit/errors/invalid_api_request'
4
4
 
5
- module Drillbit
6
- module Errors
7
- RSpec.describe InvalidApiRequest do
5
+ module Drillbit
6
+ module Errors
7
+ describe InvalidApiRequest do
8
8
  let(:error) { InvalidApiRequest.new }
9
9
 
10
10
  it 'has a status of 400' do