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.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/LICENSE.txt +19 -0
- data/README.md +2 -0
- data/Rakefile +2 -0
- data/lib/drillbit.rb +19 -0
- data/lib/drillbit/accept_header.rb +50 -0
- data/lib/drillbit/authorizable_resource.rb +160 -0
- data/lib/drillbit/authorizers/parameters.rb +24 -0
- data/lib/drillbit/authorizers/parameters/filtering.rb +50 -0
- data/lib/drillbit/authorizers/parameters/resource.rb +11 -0
- data/lib/drillbit/authorizers/query.rb +40 -0
- data/lib/drillbit/authorizers/scope.rb +30 -0
- data/lib/drillbit/configuration.rb +36 -0
- data/lib/drillbit/errors/invalid_api_request.rb +29 -0
- data/lib/drillbit/errors/invalid_subdomain.rb +29 -0
- data/lib/drillbit/errors/invalid_token.rb +22 -0
- data/lib/drillbit/matchers/accept_header.rb +16 -0
- data/lib/drillbit/matchers/generic.rb +30 -0
- data/lib/drillbit/matchers/subdomain.rb +31 -0
- data/lib/drillbit/matchers/version.rb +30 -0
- data/lib/drillbit/middleware/api_request.rb +49 -0
- data/lib/drillbit/parameters.rb +22 -0
- data/lib/drillbit/parameters/filter.rb +57 -0
- data/lib/drillbit/parameters/index.rb +31 -0
- data/lib/drillbit/parameters/page.rb +28 -0
- data/lib/drillbit/parameters/sort.rb +32 -0
- data/lib/drillbit/requests/base.rb +114 -0
- data/lib/drillbit/requests/rack.rb +50 -0
- data/lib/drillbit/requests/rails.rb +44 -0
- data/lib/drillbit/resource.rb +14 -0
- data/lib/drillbit/resource/model.rb +41 -0
- data/lib/drillbit/resource/naming.rb +33 -0
- data/lib/drillbit/resource/processors/filtering.rb +66 -0
- data/lib/drillbit/resource/processors/indexing.rb +40 -0
- data/lib/drillbit/resource/processors/paging.rb +46 -0
- data/lib/drillbit/resource/processors/sorting.rb +42 -0
- data/lib/drillbit/responses/invalid_api_request.rb +18 -0
- data/lib/drillbit/responses/invalid_subdomain.rb +18 -0
- data/lib/drillbit/responses/invalid_token.rb +20 -0
- data/lib/drillbit/serializers/json_api.rb +10 -0
- data/lib/drillbit/tokens/base64.rb +45 -0
- data/lib/drillbit/tokens/base64s/invalid.rb +14 -0
- data/lib/drillbit/tokens/base64s/null.rb +14 -0
- data/lib/drillbit/tokens/invalid.rb +26 -0
- data/lib/drillbit/tokens/json_web_token.rb +112 -0
- data/lib/drillbit/tokens/json_web_tokens/invalid.rb +14 -0
- data/lib/drillbit/tokens/json_web_tokens/null.rb +14 -0
- data/lib/drillbit/tokens/null.rb +26 -0
- data/lib/drillbit/version.rb +4 -0
- data/spec/drillbit/accept_header_spec.rb +112 -0
- data/spec/drillbit/authorizers/parameters/filtering_spec.rb +71 -0
- data/spec/drillbit/authorizers/parameters/resource_spec.rb +12 -0
- data/spec/drillbit/authorizers/parameters_spec.rb +17 -0
- data/spec/drillbit/authorizers/query_spec.rb +21 -0
- data/spec/drillbit/authorizers/scope_spec.rb +20 -0
- data/spec/drillbit/errors/invalid_api_request_spec.rb +31 -0
- data/spec/drillbit/errors/invalid_subdomain_spec.rb +31 -0
- data/spec/drillbit/errors/invalid_token_spec.rb +24 -0
- data/spec/drillbit/invalid_subdomain_spec.rb +46 -0
- data/spec/drillbit/invalid_token_spec.rb +44 -0
- data/spec/drillbit/matchers/accept_header_spec.rb +114 -0
- data/spec/drillbit/matchers/subdomain_spec.rb +78 -0
- data/spec/drillbit/matchers/version_spec.rb +86 -0
- data/spec/drillbit/middleware/api_request_spec.rb +220 -0
- data/spec/drillbit/parameters_spec.rb +49 -0
- data/spec/drillbit/requests/base_spec.rb +37 -0
- data/spec/drillbit/requests/rack_spec.rb +253 -0
- data/spec/drillbit/requests/rails_spec.rb +264 -0
- data/spec/drillbit/resource/model_spec.rb +64 -0
- data/spec/drillbit/resource/processors/filtering_spec.rb +106 -0
- data/spec/drillbit/resource/processors/indexing_spec.rb +46 -0
- data/spec/drillbit/resource/processors/paging_spec.rb +74 -0
- data/spec/drillbit/resource/processors/sorting_spec.rb +66 -0
- data/spec/drillbit/tokens/base64_spec.rb +44 -0
- data/spec/drillbit/tokens/json_web_token_spec.rb +135 -0
- data/spec/fixtures/test_rsa_key +27 -0
- data/spec/fixtures/test_rsa_key.pub +9 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/private_keys.rb +42 -0
- metadata +244 -0
- 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
|