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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +202 -0
  3. data/README.md +102 -0
  4. data/Rakefile +18 -0
  5. data/app/models/authentication.rb +19 -0
  6. data/app/models/concerns/encryption_concern.rb +52 -0
  7. data/app/models/encryption.rb +13 -0
  8. data/lib/generators/shared_utilities/migration_generator.rb +79 -0
  9. data/lib/generators/shared_utilities/orm_helper.rb +25 -0
  10. data/lib/generators/shared_utilities/templates/migration.rb +27 -0
  11. data/lib/generators/shared_utilities/templates/migration_existing.rb +28 -0
  12. data/lib/insights.rb +1 -0
  13. data/lib/insights/api/common.rb +12 -0
  14. data/lib/insights/api/common/application_controller_mixins/api_doc.rb +39 -0
  15. data/lib/insights/api/common/application_controller_mixins/common.rb +27 -0
  16. data/lib/insights/api/common/application_controller_mixins/exception_handling.rb +41 -0
  17. data/lib/insights/api/common/application_controller_mixins/openapi_enabled.rb +13 -0
  18. data/lib/insights/api/common/application_controller_mixins/parameters.rb +134 -0
  19. data/lib/insights/api/common/application_controller_mixins/request_body_validation.rb +48 -0
  20. data/lib/insights/api/common/application_controller_mixins/request_parameter_validation.rb +29 -0
  21. data/lib/insights/api/common/application_controller_mixins/request_path.rb +70 -0
  22. data/lib/insights/api/common/engine.rb +20 -0
  23. data/lib/insights/api/common/entitlement.rb +37 -0
  24. data/lib/insights/api/common/error_document.rb +29 -0
  25. data/lib/insights/api/common/filter.rb +175 -0
  26. data/lib/insights/api/common/graphql.rb +127 -0
  27. data/lib/insights/api/common/graphql/associated_records.rb +44 -0
  28. data/lib/insights/api/common/graphql/association_loader.rb +35 -0
  29. data/lib/insights/api/common/graphql/generator.rb +148 -0
  30. data/lib/insights/api/common/graphql/templates/model_type.erb +35 -0
  31. data/lib/insights/api/common/graphql/templates/query_type.erb +49 -0
  32. data/lib/insights/api/common/graphql/templates/schema.erb +6 -0
  33. data/lib/insights/api/common/graphql/types/big_int.rb +23 -0
  34. data/lib/insights/api/common/graphql/types/date_time.rb +16 -0
  35. data/lib/insights/api/common/graphql/types/query_filter.rb +16 -0
  36. data/lib/insights/api/common/graphql/types/query_sort_by.rb +16 -0
  37. data/lib/insights/api/common/inflections.rb +28 -0
  38. data/lib/insights/api/common/logging.rb +17 -0
  39. data/lib/insights/api/common/metrics.rb +39 -0
  40. data/lib/insights/api/common/middleware/web_server_metrics.rb +62 -0
  41. data/lib/insights/api/common/open_api.rb +2 -0
  42. data/lib/insights/api/common/open_api/docs.rb +54 -0
  43. data/lib/insights/api/common/open_api/docs/component_collection.rb +67 -0
  44. data/lib/insights/api/common/open_api/docs/doc_v3.rb +102 -0
  45. data/lib/insights/api/common/open_api/docs/object_definition.rb +39 -0
  46. data/lib/insights/api/common/open_api/generator.rb +520 -0
  47. data/lib/insights/api/common/open_api/serializer.rb +31 -0
  48. data/lib/insights/api/common/option_redirect_enhancements.rb +23 -0
  49. data/lib/insights/api/common/paginated_response.rb +108 -0
  50. data/lib/insights/api/common/rbac/access.rb +66 -0
  51. data/lib/insights/api/common/rbac/acl.rb +74 -0
  52. data/lib/insights/api/common/rbac/policies.rb +33 -0
  53. data/lib/insights/api/common/rbac/query_shared_resource.rb +45 -0
  54. data/lib/insights/api/common/rbac/roles.rb +77 -0
  55. data/lib/insights/api/common/rbac/seed.rb +140 -0
  56. data/lib/insights/api/common/rbac/service.rb +67 -0
  57. data/lib/insights/api/common/rbac/share_resource.rb +60 -0
  58. data/lib/insights/api/common/rbac/unshare_resource.rb +32 -0
  59. data/lib/insights/api/common/rbac/utilities.rb +30 -0
  60. data/lib/insights/api/common/request.rb +111 -0
  61. data/lib/insights/api/common/routing.rb +26 -0
  62. data/lib/insights/api/common/user.rb +48 -0
  63. data/lib/insights/api/common/version.rb +7 -0
  64. data/lib/tasks/insights/api/common_tasks.rake +4 -0
  65. data/spec/support/default_as_json.rb +17 -0
  66. data/spec/support/rbac_shared_contexts.rb +44 -0
  67. data/spec/support/requests_spec_helper.rb +7 -0
  68. data/spec/support/service_spec_helper.rb +26 -0
  69. data/spec/support/user_header_spec_helper.rb +68 -0
  70. 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