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,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