insights-api-common 3.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.
- checksums.yaml +7 -0
- data/LICENSE.txt +202 -0
- data/README.md +102 -0
- data/Rakefile +18 -0
- data/app/models/authentication.rb +19 -0
- data/app/models/concerns/encryption_concern.rb +52 -0
- data/app/models/encryption.rb +13 -0
- data/lib/generators/shared_utilities/migration_generator.rb +79 -0
- data/lib/generators/shared_utilities/orm_helper.rb +25 -0
- data/lib/generators/shared_utilities/templates/migration.rb +27 -0
- data/lib/generators/shared_utilities/templates/migration_existing.rb +28 -0
- data/lib/insights.rb +1 -0
- data/lib/insights/api/common.rb +12 -0
- data/lib/insights/api/common/application_controller_mixins/api_doc.rb +39 -0
- data/lib/insights/api/common/application_controller_mixins/common.rb +27 -0
- data/lib/insights/api/common/application_controller_mixins/exception_handling.rb +41 -0
- data/lib/insights/api/common/application_controller_mixins/openapi_enabled.rb +13 -0
- data/lib/insights/api/common/application_controller_mixins/parameters.rb +134 -0
- data/lib/insights/api/common/application_controller_mixins/request_body_validation.rb +48 -0
- data/lib/insights/api/common/application_controller_mixins/request_parameter_validation.rb +29 -0
- data/lib/insights/api/common/application_controller_mixins/request_path.rb +70 -0
- data/lib/insights/api/common/engine.rb +20 -0
- data/lib/insights/api/common/entitlement.rb +37 -0
- data/lib/insights/api/common/error_document.rb +29 -0
- data/lib/insights/api/common/filter.rb +175 -0
- data/lib/insights/api/common/graphql.rb +127 -0
- data/lib/insights/api/common/graphql/associated_records.rb +44 -0
- data/lib/insights/api/common/graphql/association_loader.rb +35 -0
- data/lib/insights/api/common/graphql/generator.rb +148 -0
- data/lib/insights/api/common/graphql/templates/model_type.erb +35 -0
- data/lib/insights/api/common/graphql/templates/query_type.erb +49 -0
- data/lib/insights/api/common/graphql/templates/schema.erb +6 -0
- data/lib/insights/api/common/graphql/types/big_int.rb +23 -0
- data/lib/insights/api/common/graphql/types/date_time.rb +16 -0
- data/lib/insights/api/common/graphql/types/query_filter.rb +16 -0
- data/lib/insights/api/common/graphql/types/query_sort_by.rb +16 -0
- data/lib/insights/api/common/inflections.rb +28 -0
- data/lib/insights/api/common/logging.rb +17 -0
- data/lib/insights/api/common/metrics.rb +39 -0
- data/lib/insights/api/common/middleware/web_server_metrics.rb +62 -0
- data/lib/insights/api/common/open_api.rb +2 -0
- data/lib/insights/api/common/open_api/docs.rb +54 -0
- data/lib/insights/api/common/open_api/docs/component_collection.rb +67 -0
- data/lib/insights/api/common/open_api/docs/doc_v3.rb +102 -0
- data/lib/insights/api/common/open_api/docs/object_definition.rb +39 -0
- data/lib/insights/api/common/open_api/generator.rb +520 -0
- data/lib/insights/api/common/open_api/serializer.rb +31 -0
- data/lib/insights/api/common/option_redirect_enhancements.rb +23 -0
- data/lib/insights/api/common/paginated_response.rb +108 -0
- data/lib/insights/api/common/rbac/access.rb +66 -0
- data/lib/insights/api/common/rbac/acl.rb +74 -0
- data/lib/insights/api/common/rbac/policies.rb +33 -0
- data/lib/insights/api/common/rbac/query_shared_resource.rb +45 -0
- data/lib/insights/api/common/rbac/roles.rb +77 -0
- data/lib/insights/api/common/rbac/seed.rb +140 -0
- data/lib/insights/api/common/rbac/service.rb +67 -0
- data/lib/insights/api/common/rbac/share_resource.rb +60 -0
- data/lib/insights/api/common/rbac/unshare_resource.rb +32 -0
- data/lib/insights/api/common/rbac/utilities.rb +30 -0
- data/lib/insights/api/common/request.rb +111 -0
- data/lib/insights/api/common/routing.rb +26 -0
- data/lib/insights/api/common/user.rb +48 -0
- data/lib/insights/api/common/version.rb +7 -0
- data/lib/tasks/insights/api/common_tasks.rake +4 -0
- data/spec/support/default_as_json.rb +17 -0
- data/spec/support/rbac_shared_contexts.rb +44 -0
- data/spec/support/requests_spec_helper.rb +7 -0
- data/spec/support/service_spec_helper.rb +26 -0
- data/spec/support/user_header_spec_helper.rb +68 -0
- metadata +403 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module OpenApi
|
5
|
+
module Serializer
|
6
|
+
def as_json(arg = {})
|
7
|
+
previous = super
|
8
|
+
encrypted_columns_set = (self.class.try(:encrypted_columns) || []).to_set
|
9
|
+
encryption_filtered = previous.except(*encrypted_columns_set)
|
10
|
+
return encryption_filtered unless arg.key?(:prefixes)
|
11
|
+
version = api_version_from_prefix(arg[:prefixes].first)
|
12
|
+
schema = ::Insights::API::Common::OpenApi::Docs.instance[version].definitions[self.class.name]
|
13
|
+
attrs = encryption_filtered.slice(*schema["properties"].keys)
|
14
|
+
schema["properties"].keys.each do |name|
|
15
|
+
next if attrs[name].nil?
|
16
|
+
attrs[name] = attrs[name].iso8601 if attrs[name].kind_of?(Time)
|
17
|
+
attrs[name] = attrs[name].to_s if name.ends_with?("_id") || name == "id"
|
18
|
+
attrs[name] = self.public_send(name) if !attrs.key?(name) && !encrypted_columns_set.include?(name)
|
19
|
+
end
|
20
|
+
attrs.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
def api_version_from_prefix(prefix)
|
24
|
+
/\/?\w+\/v(?<major>\d+)[x\.]?(?<minor>\d+)?\// =~ prefix
|
25
|
+
[major, minor].compact.join(".")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module OptionRedirectEnhancements
|
5
|
+
def serve(req)
|
6
|
+
uri = URI.parse(path(req.path_parameters, req))
|
7
|
+
|
8
|
+
req.commit_flash
|
9
|
+
|
10
|
+
body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri.to_s)}">redirected</a>.</body></html>)
|
11
|
+
|
12
|
+
headers = {
|
13
|
+
"Location" => uri.to_s,
|
14
|
+
"Content-Type" => "text/html",
|
15
|
+
"Content-Length" => body.length.to_s
|
16
|
+
}
|
17
|
+
|
18
|
+
[ status, headers, [body] ]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class PaginatedResponse
|
5
|
+
attr_reader :limit, :offset, :sort_by
|
6
|
+
|
7
|
+
def initialize(base_query:, request:, limit: nil, offset: nil, sort_by: nil)
|
8
|
+
@base_query = base_query
|
9
|
+
@request = request
|
10
|
+
@limit = (limit || 100).to_i.clamp(1, 1000)
|
11
|
+
@offset = (offset || 0).to_i.clamp(0, Float::INFINITY)
|
12
|
+
@sort_by = sort_by
|
13
|
+
end
|
14
|
+
|
15
|
+
def records
|
16
|
+
@records ||= begin
|
17
|
+
res = @base_query.order(:id).limit(limit).offset(offset)
|
18
|
+
order_options = sort_by_options(res.klass)
|
19
|
+
res = res.reorder(order_options) if order_options.present?
|
20
|
+
res
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def response
|
25
|
+
{
|
26
|
+
"meta" => metadata_hash,
|
27
|
+
"links" => links_hash,
|
28
|
+
"data" => records
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def metadata_hash
|
35
|
+
@metadata_hash ||= {"count" => count, "limit" => limit, "offset" => offset}
|
36
|
+
end
|
37
|
+
|
38
|
+
def links_hash
|
39
|
+
@links_hash ||= {
|
40
|
+
"first" => link_to_first,
|
41
|
+
"last" => link_to_last,
|
42
|
+
"prev" => link_to_prev,
|
43
|
+
"next" => link_to_next,
|
44
|
+
}.compact
|
45
|
+
end
|
46
|
+
|
47
|
+
def link_to_first
|
48
|
+
link_with_new_offset(0)
|
49
|
+
end
|
50
|
+
|
51
|
+
def link_to_last
|
52
|
+
link_with_new_offset(max_limit_multiplier.clamp(0, Float::INFINITY) * limit)
|
53
|
+
end
|
54
|
+
|
55
|
+
def link_to_prev
|
56
|
+
return if offset == 0
|
57
|
+
prev_offset = offset - limit
|
58
|
+
|
59
|
+
link_with_new_offset(prev_offset.clamp(0, Float::INFINITY))
|
60
|
+
end
|
61
|
+
|
62
|
+
def link_to_next
|
63
|
+
next_offset = limit + offset
|
64
|
+
return if next_offset >= count
|
65
|
+
|
66
|
+
link_with_new_offset(next_offset)
|
67
|
+
end
|
68
|
+
|
69
|
+
def link_with_new_offset(offset)
|
70
|
+
URI::Generic.build(:path => request_uri.path, :query => query_hash_merge("offset" => offset.to_s)).to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def query_hash_merge(new_hash)
|
74
|
+
parsed_query.merge(new_hash).to_query
|
75
|
+
end
|
76
|
+
|
77
|
+
def parsed_query
|
78
|
+
@parsed_query ||= Rack::Utils.parse_query(request_uri.query)
|
79
|
+
end
|
80
|
+
|
81
|
+
def request_uri
|
82
|
+
@request_uri ||= URI.parse(@request.original_url)
|
83
|
+
end
|
84
|
+
|
85
|
+
def max_limit_multiplier
|
86
|
+
@max_limit_multiplier ||= ((count - 1) / limit)
|
87
|
+
end
|
88
|
+
|
89
|
+
def count
|
90
|
+
@count ||= @base_query.count
|
91
|
+
end
|
92
|
+
|
93
|
+
def sort_by_options(model)
|
94
|
+
@sort_by_options ||= begin
|
95
|
+
Array(sort_by).collect do |selection|
|
96
|
+
sort_attr, sort_order = selection.split(':')
|
97
|
+
sort_order ||= "asc"
|
98
|
+
arel = model.arel_attribute(sort_attr)
|
99
|
+
arel = arel.asc if sort_order == "asc"
|
100
|
+
arel = arel.desc if sort_order == "desc"
|
101
|
+
arel
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
class Access
|
6
|
+
attr_reader :acl
|
7
|
+
DEFAULT_LIMIT = 500
|
8
|
+
def initialize(resource, verb)
|
9
|
+
@resource = resource
|
10
|
+
@verb = verb
|
11
|
+
@regexp = Regexp.new(":(#{Regexp.escape(@resource)}|\\*):(#{Regexp.escape(@verb)}|\\*)")
|
12
|
+
@app_name = ENV["APP_NAME"]
|
13
|
+
end
|
14
|
+
|
15
|
+
def process
|
16
|
+
Service.call(RBACApiClient::AccessApi) do |api|
|
17
|
+
@acl ||= Service.paginate(api, :get_principal_access, {:limit => DEFAULT_LIMIT}, @app_name).select do |item|
|
18
|
+
@regexp.match?(item.permission)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def accessible?
|
25
|
+
@acl.any?
|
26
|
+
end
|
27
|
+
|
28
|
+
def id_list
|
29
|
+
ids.include?('*') ? [] : ids
|
30
|
+
end
|
31
|
+
|
32
|
+
def owner_scoped?
|
33
|
+
ids.include?('*') ? false : owner_scope_filter?
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.enabled?
|
37
|
+
ENV['BYPASS_RBAC'].blank?
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def ids
|
43
|
+
@ids ||= @acl.each_with_object([]) do |item, ids|
|
44
|
+
item.resource_definitions.each do |rd|
|
45
|
+
next unless rd.attribute_filter.key == 'id'
|
46
|
+
next unless rd.attribute_filter.operation == 'equal'
|
47
|
+
|
48
|
+
ids << rd.attribute_filter.value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def owner_scope_filter?
|
54
|
+
@acl.any? do |item|
|
55
|
+
item.resource_definitions.any? do |rd|
|
56
|
+
rd.attribute_filter.key == 'owner' &&
|
57
|
+
rd.attribute_filter.operation == 'equal' &&
|
58
|
+
rd.attribute_filter.value == '{{username}}'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
class ACL
|
6
|
+
def create(resource_id, permissions)
|
7
|
+
permissions.collect do |permission|
|
8
|
+
create_acl(permission, resource_id)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def remove(acls, resource_id, permissions)
|
13
|
+
permissions.each_with_object(acls) do |permission, as|
|
14
|
+
delete_matching(as, resource_id, permission)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def add(acls, resource_id, permissions)
|
19
|
+
new_acls = permissions.each_with_object([]) do |permission, as|
|
20
|
+
next if find_matching(acls, resource_id, permission)
|
21
|
+
|
22
|
+
as << create_acl(permission, resource_id)
|
23
|
+
end
|
24
|
+
new_acls + acls
|
25
|
+
end
|
26
|
+
|
27
|
+
def resource_defintions_empty?(acls, permission)
|
28
|
+
acls.each do |acl|
|
29
|
+
if acl.permission == permission
|
30
|
+
return acl.resource_definitions.empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def create_acl(permission, resource_id = nil)
|
39
|
+
resource_def = resource_definition(resource_id) if resource_id
|
40
|
+
RBACApiClient::Access.new.tap do |access|
|
41
|
+
access.permission = permission
|
42
|
+
access.resource_definitions = resource_def ? [resource_def] : []
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def resource_definition(resource_id)
|
47
|
+
rdf = RBACApiClient::ResourceDefinitionFilter.new.tap do |obj|
|
48
|
+
obj.key = 'id'
|
49
|
+
obj.operation = 'equal'
|
50
|
+
obj.value = resource_id.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
RBACApiClient::ResourceDefinition.new.tap do |rd|
|
54
|
+
rd.attribute_filter = rdf
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def matches?(access, resource_id, permission)
|
59
|
+
access.permission == permission &&
|
60
|
+
access.resource_definitions.any? { |rdf| rdf.attribute_filter.key == 'id' && rdf.attribute_filter.operation == 'equal' && rdf.attribute_filter.value == resource_id.to_s }
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_matching(acls, resource_id, permission)
|
64
|
+
acls.detect { |access| matches?(access, resource_id, permission) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete_matching(acls, resource_id, permission)
|
68
|
+
acls.delete_if { |access| matches?(access, resource_id, permission) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
class Policies
|
6
|
+
def initialize(prefix)
|
7
|
+
@prefix = prefix
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_policy(policy_name, description, group_name, role_uuid)
|
11
|
+
Service.call(RBACApiClient::PolicyApi) do |api_instance|
|
12
|
+
policy_in = RBACApiClient::PolicyIn.new
|
13
|
+
policy_in.name = policy_name
|
14
|
+
policy_in.description = description
|
15
|
+
policy_in.group = group_name
|
16
|
+
policy_in.roles = [role_uuid]
|
17
|
+
api_instance.create_policies(policy_in)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# delete all policies that contains the role.
|
22
|
+
def delete_policy(role)
|
23
|
+
Service.call(RBACApiClient::PolicyApi) do |api_instance|
|
24
|
+
Service.paginate(api_instance, :list_policies, :name => @prefix).each do |policy|
|
25
|
+
api_instance.delete_policy(policy.uuid) if policy.roles.map(&:uuid).include?(role.uuid)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
class QuerySharedResource
|
6
|
+
require 'rbac-api-client'
|
7
|
+
|
8
|
+
include Utilities
|
9
|
+
attr_accessor :share_info
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
@app_name = options[:app_name]
|
13
|
+
@resource_id = options[:resource_id]
|
14
|
+
@resource_name = options[:resource_name]
|
15
|
+
@share_info = []
|
16
|
+
@roles = RBAC::Roles.new("#{@app_name}-#{@resource_name}-#{@resource_id}", 'account')
|
17
|
+
end
|
18
|
+
|
19
|
+
def process
|
20
|
+
build_share_info
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_share_info
|
27
|
+
@roles.with_each_role do |role|
|
28
|
+
_id, group_uuid = parse_ids_from_name(role.name)
|
29
|
+
group = get_group(group_uuid)
|
30
|
+
@share_info << { 'group_name' => group.name,
|
31
|
+
'group_uuid' => group.uuid,
|
32
|
+
'permissions' => role.access.collect(&:permission)}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_group(uuid)
|
37
|
+
Service.call(RBACApiClient::GroupApi) do |api_instance|
|
38
|
+
api_instance.get_group(uuid)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
class Roles
|
6
|
+
attr_reader :roles
|
7
|
+
|
8
|
+
def initialize(prefix = nil, scope = 'principal')
|
9
|
+
@roles = {}
|
10
|
+
load(prefix, scope)
|
11
|
+
end
|
12
|
+
|
13
|
+
def find(name)
|
14
|
+
uuid = @roles[name]
|
15
|
+
get(uuid) if uuid
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_each_role
|
19
|
+
@roles.each_value do |uuid|
|
20
|
+
yield get(uuid)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(name, acls)
|
25
|
+
Service.call(RBACApiClient::RoleApi) do |api_instance|
|
26
|
+
role_in = RBACApiClient::RoleIn.new
|
27
|
+
role_in.name = name
|
28
|
+
role_in.access = acls
|
29
|
+
api_instance.create_roles(role_in).tap do |role|
|
30
|
+
@roles[name] = role.uuid
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def update(role)
|
36
|
+
Service.call(RBACApiClient::RoleApi) do |api_instance|
|
37
|
+
api_instance.update_role(role.uuid, role)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(role)
|
42
|
+
@roles.delete(role.name)
|
43
|
+
Service.call(RBACApiClient::RoleApi) do |api_instance|
|
44
|
+
api_instance.delete_role(role.uuid)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.assigned_role?(role_name)
|
49
|
+
opts = { :name => role_name,
|
50
|
+
:scope => 'principal' }
|
51
|
+
|
52
|
+
Service.call(RBACApiClient::RoleApi) do |api_instance|
|
53
|
+
Service.paginate(api_instance, :list_roles, opts).count.positive?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def load(prefix, scope)
|
60
|
+
opts = { :scope => scope, :name => prefix, :limit => 500 }
|
61
|
+
Service.call(RBACApiClient::RoleApi) do |api_instance|
|
62
|
+
Service.paginate(api_instance, :list_roles, opts).each do |role|
|
63
|
+
@roles[role.name] = role.uuid
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def get(uuid)
|
69
|
+
Service.call(RBACApiClient::RoleApi) do |api_instance|
|
70
|
+
api_instance.get_role(uuid)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|