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,50 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/configuration'
3
+ require 'drillbit/requests/base'
4
+ require 'drillbit/accept_header'
5
+ require 'drillbit/tokens/json_web_token'
6
+ require 'drillbit/tokens/base64'
7
+
8
+ module Drillbit
9
+ module Requests
10
+ class Rack < Base
11
+ ACCEPT_PARAM_PATTERN = /(?:\A|&)accept=(.+?)(?=\z|&)/
12
+ BASE64_TOKEN_PARAM_PATTERN = /(?:\A|&)#{BASE64_TOKEN_PARAM_NAME}=(.*)(?=\z|&)/
13
+ JSON_WEB_TOKEN_PARAM_PATTERN = /(?:\A|&)#{JSON_WEB_TOKEN_PARAM_NAME}=(.*)(?=\z|&)/
14
+
15
+ def authorization_token_from_params
16
+ case request['QUERY_STRING']
17
+ when JSON_WEB_TOKEN_PARAM_PATTERN
18
+ Tokens::JsonWebToken.from_jwe(
19
+ request['QUERY_STRING'][JSON_WEB_TOKEN_PARAM_PATTERN, 1] || '',
20
+ private_key: token_private_key,
21
+ )
22
+ when BASE64_TOKEN_PARAM_PATTERN
23
+ base64_token = request['QUERY_STRING'][BASE64_TOKEN_PARAM_PATTERN, 1]
24
+
25
+ Tokens::Base64.convert(raw_token: base64_token)
26
+ else
27
+ Tokens::Null.instance
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def raw_accept_header_from_header
34
+ request['HTTP_ACCEPT']
35
+ end
36
+
37
+ def raw_accept_header_from_params
38
+ URI.unescape(request['QUERY_STRING'][ACCEPT_PARAM_PATTERN, 1] || '')
39
+ end
40
+
41
+ def raw_authorization_header
42
+ request['HTTP_AUTHORIZATION'] || ''
43
+ end
44
+
45
+ def raw_request_application_name
46
+ request['HTTP_X_APPLICATION_NAME']
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/configuration'
3
+ require 'drillbit/requests/base'
4
+ require 'drillbit/accept_header'
5
+ require 'drillbit/tokens/json_web_token'
6
+ require 'drillbit/tokens/base64'
7
+
8
+ module Drillbit
9
+ module Requests
10
+ class Rails < Base
11
+ def authorization_token_from_params
12
+ case
13
+ when request.params.key?(JSON_WEB_TOKEN_PARAM_NAME)
14
+ Tokens::JsonWebToken.from_jwe(
15
+ request.params[JSON_WEB_TOKEN_PARAM_NAME] || '',
16
+ private_key: token_private_key,
17
+ )
18
+ when request.params.key?(BASE64_TOKEN_PARAM_NAME)
19
+ Tokens::Base64.convert(raw_token: request.params[BASE64_TOKEN_PARAM_NAME] || '')
20
+ else
21
+ Tokens::Null.instance
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def raw_accept_header_from_header
28
+ request.headers['Accept']
29
+ end
30
+
31
+ def raw_accept_header_from_params
32
+ request.params['accept']
33
+ end
34
+
35
+ def raw_authorization_header
36
+ request.headers['HTTP_AUTHORIZATION'] || ''
37
+ end
38
+
39
+ def raw_request_application_name
40
+ request.headers['X-Application-Name']
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/authorizable_resource'
3
+ require 'erratum/rescuable_resource'
4
+ require 'erratum/verifiable_resource'
5
+
6
+ module Drillbit
7
+ module Resource
8
+ def self.included(base)
9
+ base.include Erratum::RescuableResource
10
+ base.include Erratum::VerifiableResource
11
+ base.include Drillbit::AuthorizableResource
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/resource/processors/filtering'
3
+ require 'drillbit/resource/processors/sorting'
4
+ require 'drillbit/resource/processors/paging'
5
+ require 'drillbit/resource/processors/indexing'
6
+
7
+ module Drillbit
8
+ module Resource
9
+ class Model
10
+ DEFAULT_PROCESSORS = %w{filtering sorting paging indexing}.freeze
11
+
12
+ attr_accessor :resource,
13
+ :parameters,
14
+ :processors
15
+
16
+ def initialize(resource:, parameters:, **options)
17
+ self.resource = resource
18
+ self.parameters = parameters.dup
19
+ self.processors = options.fetch(:processors, DEFAULT_PROCESSORS)
20
+ end
21
+
22
+ def processed
23
+ @processed ||= processors.inject(resource) do |processed_resource, processor|
24
+ processor.processed(processed_resource, parameters)
25
+ end
26
+ end
27
+
28
+ def meta
29
+ @meta ||= processors.inject({}) do |metadata, processor|
30
+ metadata.merge processor.meta(processed, parameters)
31
+ end
32
+ end
33
+
34
+ def processors=(other)
35
+ @processors = other.map do |processor|
36
+ Object.const_get "::Drillbit::Resource::Processors::#{processor.capitalize}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ module Drillbit
3
+ module Resource
4
+ module Naming
5
+ CONTROLLER_RESOURCE_NAME_PATTERN = /\A((.*?::)?.*?)(\w+)Controller\z/
6
+
7
+ module ClassMethods
8
+ def plural_resource_name
9
+ @plural_resource_name ||= name[CONTROLLER_RESOURCE_NAME_PATTERN, 3].
10
+ underscore.
11
+ pluralize.
12
+ downcase
13
+ end
14
+
15
+ def singular_resource_name
16
+ @singular_resource_name ||= name[CONTROLLER_RESOURCE_NAME_PATTERN, 3].
17
+ underscore.
18
+ singularize.
19
+ downcase
20
+ end
21
+
22
+ def resource_class_name
23
+ @resource_class_name ||= singular_resource_name.
24
+ camelize
25
+ end
26
+ end
27
+
28
+ def self.included(base)
29
+ base.extend ClassMethods
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/parameters/filter'
3
+
4
+ module Drillbit
5
+ module Resource
6
+ module Processors
7
+ class Filtering
8
+ attr_accessor :resource,
9
+ :parameters
10
+
11
+ # rubocop:disable Style/OptionHash
12
+ def initialize(resource, parameters = {})
13
+ self.resource = resource
14
+ self.parameters = Parameters::Filter.new(parameters['filter'] || {})
15
+ end
16
+ # rubocop:enable Style/OptionHash
17
+
18
+ def self.processed(*attrs)
19
+ new(*attrs).processed
20
+ end
21
+
22
+ def self.meta(*_attrs)
23
+ {}
24
+ end
25
+
26
+ def processed
27
+ parameters.each_with_object(resource) do |name, value, filtered_resource|
28
+ filter_method = filter_method_for(name)
29
+
30
+ if !filter_method
31
+ filtered_resource
32
+ elsif filter_method.arity == 0
33
+ filtered_resource.public_send(filter_method.name)
34
+ else
35
+ filtered_resource.public_send(filter_method.name, value)
36
+ end
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def filter_method_for(filter_item)
43
+ filter_method_name = filter_method_name_for(filter_item)
44
+
45
+ resource_class.method(filter_method_name) if filter_method_name
46
+ end
47
+
48
+ def filter_method_name_for(filter_item)
49
+ if resource_class.respond_to? "for_#{filter_item}"
50
+ "for_#{filter_item}"
51
+ elsif resource_class.respond_to? filter_item
52
+ filter_item
53
+ end
54
+ end
55
+
56
+ def resource_class
57
+ @resource_class ||= if resource.respond_to? :klass
58
+ resource.klass
59
+ else
60
+ resource
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/parameters/index'
3
+
4
+ module Drillbit
5
+ module Resource
6
+ module Processors
7
+ class Indexing
8
+ attr_accessor :resource,
9
+ :parameters
10
+
11
+ # rubocop:disable Style/OptionHash
12
+ def initialize(resource, parameters = {})
13
+ self.resource = resource
14
+ self.parameters = Parameters::Index.new(parameters['filter'] || {})
15
+ end
16
+ # rubocop:enable Style/OptionHash
17
+
18
+ def self.processed(*attrs)
19
+ new(*attrs).processed
20
+ end
21
+
22
+ def self.meta(*_attrs)
23
+ {}
24
+ end
25
+
26
+ def processed
27
+ return resource unless parameters.present? || force_query
28
+
29
+ resource.for_query(parameters.query || Parameters::Index::DEFAULT_QUERY)
30
+ end
31
+
32
+ private
33
+
34
+ def force_query
35
+ resource.class.name.include? 'Index'
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/parameters/page'
3
+
4
+ module Drillbit
5
+ module Resource
6
+ module Processors
7
+ class Paging
8
+ attr_accessor :resource,
9
+ :parameters
10
+
11
+ # rubocop:disable Style/OptionHash
12
+ def initialize(resource, parameters = {})
13
+ self.resource = resource
14
+ self.parameters = Parameters::Page.new(parameters['page'] || {})
15
+ end
16
+ # rubocop:enable Style/OptionHash
17
+
18
+ def self.processed(*attrs)
19
+ new(*attrs).processed
20
+ end
21
+
22
+ def self.meta(*attrs)
23
+ new(*attrs).meta
24
+ end
25
+
26
+ def processed
27
+ return resource unless parameters.present?
28
+
29
+ resource.page(parameters.page_number).
30
+ per(parameters.per_page)
31
+ end
32
+
33
+ def meta
34
+ return {} unless parameters.present?
35
+
36
+ {
37
+ 'total-pages' => resource.total_pages,
38
+ 'current-page' => resource.current_page,
39
+ 'previous-page' => resource.prev_page,
40
+ 'next-page' => resource.next_page,
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/parameters/sort'
3
+
4
+ module Drillbit
5
+ module Resource
6
+ module Processors
7
+ class Sorting
8
+ attr_accessor :resource,
9
+ :parameters
10
+
11
+ # rubocop:disable Style/OptionHash
12
+ def initialize(resource, parameters = {})
13
+ self.resource = resource
14
+ self.parameters = Parameters::Sort.new(parameters['sort'])
15
+ end
16
+ # rubocop:enable Style/OptionHash
17
+
18
+ def self.processed(*attrs)
19
+ new(*attrs).processed
20
+ end
21
+
22
+ def self.meta(*attrs)
23
+ new(*attrs).meta
24
+ end
25
+
26
+ def processed
27
+ return resource unless parameters.present?
28
+
29
+ resource.order(parameters.to_h)
30
+ end
31
+
32
+ def meta
33
+ return {} unless parameters.present?
34
+
35
+ {
36
+ 'sort' => parameters.to_h,
37
+ }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/errors/invalid_api_request'
3
+
4
+ module Drillbit
5
+ module Responses
6
+ class InvalidApiRequest
7
+ def self.call(env)
8
+ error = Drillbit::Errors::InvalidApiRequest.new(accept_header: env['HTTP_ACCEPT'])
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
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/errors/invalid_subdomain'
3
+
4
+ module Drillbit
5
+ module Responses
6
+ class InvalidSubdomain
7
+ def self.call(env)
8
+ error = Drillbit::Errors::InvalidSubdomain.new(http_host: env['HTTP_HOST'])
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
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ require 'drillbit/errors/invalid_token'
3
+
4
+ module Drillbit
5
+ module Responses
6
+ class InvalidToken
7
+ def self.call(_env, application_name:)
8
+ error = Drillbit::Errors::InvalidToken.new
9
+
10
+ [
11
+ error.http_status, # HTTP Status Code
12
+ { # Response Headers
13
+ 'WWW-Authenticate' => %Q{Token realm="#{application_name}"},
14
+ },
15
+ [error.to_json], # Message
16
+ ]
17
+ end
18
+ end
19
+ end
20
+ end