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