manageiq-api-common 1.1.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f53d5e98416af5bcf21e208dd323bf3995fa35cd8b3acb1ecabe6ceb19d44de8
4
- data.tar.gz: 0270d9ce153351bdc7d904cd40d226c259ef0ea55ae908bfdfb811c028c59445
3
+ metadata.gz: bf558ff2ddd7a2cbed5ffca066b2f769523d4fdd3b835d9497c280d97838f81b
4
+ data.tar.gz: 160883b30284ec6b53f0c14c43b66413ac9b9ab75a87766ebc91bdfb15b4c658
5
5
  SHA512:
6
- metadata.gz: fbbf638e19fdf20d3575fed8db27e20e009d444f35ca86a2e6d03613ab41a8723d19ab59c4c77e9e9f4d978b3a07e5b8ef0aae6afc4b147d256f9041d4933fb7
7
- data.tar.gz: 486713b3545ef4d3eeb5666c0c74c9669d2dc9cbe9bd81757872d8a4c4ff9b805813d068d11ecd0b6037947346b5fffb750bcca53cd806e47263088b914352fd
6
+ metadata.gz: 108cba357408d2e2889321672976053c8a2d45a0b2f189ad5c6534b6d1d9d0f30e9637681c6e45751b157acc38061f93cf805c08c5ecba2b367284e639061f44
7
+ data.tar.gz: 30f9ab415d4b6d949b1ae62d40ee22a53f0f0ec5c5a938da3cec4a98b926b472e95e33531cf7cb7f3f914d9ba3ca204916175ee1ca7a1dc4c8ac18f0dfa826b5
@@ -1,4 +1,3 @@
1
- require "manageiq/api/common/api_error"
2
1
  require "manageiq/api/common/engine"
3
2
  require "manageiq/api/common/entitlement"
4
3
  require "manageiq/api/common/error_document"
@@ -0,0 +1,41 @@
1
+ module ManageIQ
2
+ module API
3
+ module Common
4
+ module ApplicationControllerMixins
5
+ module ExceptionHandling
6
+ DEFAULT_ERROR_CODE = 400
7
+
8
+ def self.included(other)
9
+ other.rescue_from(StandardError, RuntimeError) do |exception|
10
+ errors = ManageIQ::API::Common::ErrorDocument.new.tap do |error_document|
11
+ exception_list_from(exception).each do |exc|
12
+ code = exc.respond_to?(:code) ? exc.code : error_code_from_class(exc)
13
+ error_document.add(code, "#{exc.class}: #{exc.message}")
14
+ end
15
+ end
16
+
17
+ render :json => errors.to_h, :status => error_code_from_class(exception)
18
+ end
19
+ end
20
+
21
+ def exception_list_from(exception)
22
+ [].tap do |arr|
23
+ until exception.nil?
24
+ arr << exception
25
+ exception = exception.cause
26
+ end
27
+ end
28
+ end
29
+
30
+ def error_code_from_class(exception)
31
+ if ActionDispatch::ExceptionWrapper.rescue_responses.key?(exception.class.to_s)
32
+ ActionDispatch::ExceptionWrapper.rescue_responses[exception.class.to_s]
33
+ else
34
+ DEFAULT_ERROR_CODE
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -12,16 +12,6 @@ module ManageIQ
12
12
  other.include(OpenapiEnabled)
13
13
 
14
14
  other.before_action(:validate_request)
15
-
16
- other.rescue_from(ActionController::UnpermittedParameters) do |exception|
17
- error_document = ManageIQ::API::Common::ErrorDocument.new.add(400, exception.message)
18
- render :json => error_document.to_h, :status => error_document.status
19
- end
20
-
21
- other.rescue_from(ManageIQ::API::Common::ApplicationControllerMixins::RequestBodyValidation::BodyParseError) do |_exception|
22
- error_document = ManageIQ::API::Common::ErrorDocument.new.add(400, "Failed to parse request body, expected JSON")
23
- render :json => error_document.to_h, :status => error_document.status
24
- end
25
15
  end
26
16
 
27
17
  private
@@ -32,7 +22,7 @@ module ManageIQ
32
22
  parsed_body = raw_body.blank? ? {} : JSON.parse(raw_body)
33
23
  ActionController::Parameters.new(parsed_body).permit!
34
24
  rescue JSON::ParserError
35
- raise ManageIQ::API::Common::ApplicationControllerMixins::RequestBodyValidation::BodyParseError
25
+ raise ManageIQ::API::Common::ApplicationControllerMixins::RequestBodyValidation::BodyParseError, "Failed to parse request body, expected JSON"
36
26
  end
37
27
  end
38
28
 
@@ -50,9 +40,6 @@ module ManageIQ
50
40
  api_version,
51
41
  body_params.as_json
52
42
  )
53
- rescue OpenAPIParser::OpenAPIError => exception
54
- error_document = ManageIQ::API::Common::ErrorDocument.new.add(400, exception.message)
55
- render :json => error_document.to_h, :status => :bad_request
56
43
  end
57
44
  end
58
45
  end
@@ -10,11 +10,6 @@ module ManageIQ
10
10
  other.extend(self::ClassMethods)
11
11
 
12
12
  other.before_action(:validate_primary_collection_id)
13
-
14
- other.rescue_from(ManageIQ::API::Common::ApplicationControllerMixins::RequestPath::RequestPathError) do |exception|
15
- error_document = ManageIQ::API::Common::ErrorDocument.new.add(400, exception.message)
16
- render :json => error_document.to_h, :status => error_document.status
17
- end
18
13
  end
19
14
 
20
15
  def request_path
@@ -105,6 +105,34 @@ module ManageIQ
105
105
  "##{SCHEMAS_PATH}/#{klass_name}"
106
106
  end
107
107
 
108
+ def build_schema_error_not_found
109
+ klass_name = "ErrorNotFound"
110
+
111
+ schemas[klass_name] = {
112
+ "type" => "object",
113
+ "properties" => {
114
+ "errors" => {
115
+ "type" => "array",
116
+ "items" => {
117
+ "type" => "object",
118
+ "properties" => {
119
+ "status" => {
120
+ "type" => "integer",
121
+ "example" => 404
122
+ },
123
+ "detail" => {
124
+ "type" => "string",
125
+ "example" => "Record not found"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ "##{SCHEMAS_PATH}/#{klass_name}"
134
+ end
135
+
108
136
  def parameters
109
137
  @parameters ||= {
110
138
  "QueryFilter" => {
@@ -158,10 +186,10 @@ module ManageIQ
158
186
  end
159
187
 
160
188
  def openapi_list_description(klass_name, primary_collection)
161
- primary_collection = nil if primary_collection == klass_name
189
+ sub_collection = (primary_collection != klass_name)
162
190
  {
163
- "summary" => "List #{klass_name.pluralize}#{" for #{primary_collection}" if primary_collection}",
164
- "operationId" => "list#{primary_collection}#{klass_name.pluralize}",
191
+ "summary" => "List #{klass_name.pluralize}#{" for #{primary_collection}" if sub_collection}",
192
+ "operationId" => "list#{primary_collection if sub_collection}#{klass_name.pluralize}",
165
193
  "description" => "Returns an array of #{klass_name} objects",
166
194
  "parameters" => [
167
195
  { "$ref" => "##{PARAMETERS_PATH}/QueryLimit" },
@@ -179,7 +207,18 @@ module ManageIQ
179
207
  }
180
208
  }
181
209
  }.tap do |h|
182
- h["parameters"] << { "$ref" => build_parameter("ID") } if primary_collection
210
+ h["parameters"] << { "$ref" => build_parameter("ID") } if sub_collection
211
+
212
+ next unless sub_collection
213
+
214
+ h["responses"]["404"] = {
215
+ "description" => "Not found",
216
+ "content" => {
217
+ "application/json" => {
218
+ "schema" => { "$ref" => build_schema_error_not_found }
219
+ }
220
+ }
221
+ }
183
222
  end
184
223
  end
185
224
 
@@ -215,7 +254,14 @@ module ManageIQ
215
254
  }
216
255
  }
217
256
  },
218
- "404" => {"description" => "Not found"}
257
+ "404" => {
258
+ "description" => "Not found",
259
+ "content" => {
260
+ "application/json" => {
261
+ "schema" => { "$ref" => build_schema_error_not_found }
262
+ }
263
+ }
264
+ }
219
265
  }
220
266
  }
221
267
  end
@@ -228,7 +274,14 @@ module ManageIQ
228
274
  "parameters" => [{ "$ref" => build_parameter("ID") }],
229
275
  "responses" => {
230
276
  "204" => { "description" => "#{klass_name} deleted" },
231
- "404" => { "description" => "Not found" }
277
+ "404" => {
278
+ "description" => "Not found",
279
+ "content" => {
280
+ "application/json" => {
281
+ "schema" => { "$ref" => build_schema_error_not_found }
282
+ }
283
+ }
284
+ }
232
285
  }
233
286
  }
234
287
  end
@@ -281,7 +334,14 @@ module ManageIQ
281
334
  "responses" => {
282
335
  "204" => { "description" => "Updated, no content" },
283
336
  "400" => { "description" => "Bad request" },
284
- "404" => { "description" => "Not found" }
337
+ "404" => {
338
+ "description" => "Not found",
339
+ "content" => {
340
+ "application/json" => {
341
+ "schema" => { "$ref" => build_schema_error_not_found }
342
+ }
343
+ }
344
+ }
285
345
  }
286
346
  }
287
347
  end
@@ -0,0 +1,66 @@
1
+ module ManageIQ
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 ManageIQ
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 ManageIQ
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 ManageIQ
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}")
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 ManageIQ
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
@@ -0,0 +1,140 @@
1
+ module ManageIQ
2
+ module API
3
+ module Common
4
+ module RBAC
5
+ require 'rbac-api-client'
6
+
7
+ class Seed
8
+ def initialize(seed_file, user_file)
9
+ @acl_data = YAML.load_file(seed_file)
10
+ @request = create_request(user_file)
11
+ end
12
+
13
+ def process
14
+ ManageIQ::API::Common::Request.with_request(@request) do
15
+ create_groups
16
+ create_roles
17
+ create_policies
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def create_groups
24
+ current = current_groups
25
+ names = current.collect(&:name)
26
+ group = RBACApiClient::Group.new
27
+ begin
28
+ Service.call(RBACApiClient::GroupApi) do |api_instance|
29
+ @acl_data['groups'].each do |grp|
30
+ next if names.include?(grp['name'])
31
+
32
+ Rails.logger.info("Creating #{grp['name']}")
33
+ group.name = grp['name']
34
+ group.description = grp['description']
35
+ api_instance.create_group(group)
36
+ end
37
+ end
38
+ rescue RBACApiClient::ApiError => e
39
+ Rails.logger.error("Exception when calling GroupApi->create_group: #{e}")
40
+ raise
41
+ end
42
+ end
43
+
44
+ def current_groups
45
+ Service.call(RBACApiClient::GroupApi) do |api|
46
+ Service.paginate(api, :list_groups, {}).to_a
47
+ end
48
+ end
49
+
50
+ def create_roles
51
+ current = current_roles
52
+ names = current.collect(&:name)
53
+ role_in = RBACApiClient::RoleIn.new
54
+ begin
55
+ Service.call(RBACApiClient::RoleApi) do |api_instance|
56
+ @acl_data['roles'].each do |role|
57
+ next if names.include?(role['name'])
58
+
59
+ role_in.name = role['name']
60
+ role_in.access = []
61
+ role['access'].each do |obj|
62
+ access = RBACApiClient::Access.new
63
+ access.permission = obj['permission']
64
+ access.resource_definitions = create_rds(obj)
65
+ role_in.access << access
66
+ end
67
+ api_instance.create_roles(role_in)
68
+ end
69
+ end
70
+ rescue RBACApiClient::ApiError => e
71
+ Rails.logger.error("Exception when calling RoleApi->create_roles: #{e}")
72
+ raise
73
+ end
74
+ end
75
+
76
+ def create_rds(obj)
77
+ obj.fetch('resource_definitions', []).collect do |item|
78
+ RBACApiClient::ResourceDefinition.new.tap do |rd|
79
+ rd.attribute_filter = RBACApiClient::ResourceDefinitionFilter.new.tap do |rdf|
80
+ rdf.key = item['attribute_filter']['key']
81
+ rdf.value = item['attribute_filter']['value']
82
+ rdf.operation = item['attribute_filter']['operation']
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def current_roles
89
+ Service.call(RBACApiClient::RoleApi) do |api|
90
+ Service.paginate(api, :list_roles, {}).to_a
91
+ end
92
+ end
93
+
94
+ def create_policies
95
+ names = current_policies.collect(&:name)
96
+ groups = current_groups
97
+ roles = current_roles
98
+ policy_in = RBACApiClient::PolicyIn.new
99
+ begin
100
+ Service.call(RBACApiClient::PolicyApi) do |api_instance|
101
+ @acl_data['policies'].each do |policy|
102
+ next if names.include?(policy['name'])
103
+
104
+ policy_in.name = policy['name']
105
+ policy_in.description = policy['description']
106
+ policy_in.group = find_uuid('Group', groups, policy['group']['name'])
107
+ policy_in.roles = [find_uuid('Role', roles, policy['role']['name'])]
108
+ api_instance.create_policies(policy_in)
109
+ end
110
+ end
111
+ rescue RBACApiClient::ApiError => e
112
+ Rails.logger.error("Exception when calling PolicyApi->create_policies: #{e}")
113
+ raise
114
+ end
115
+ end
116
+
117
+ def current_policies
118
+ Service.call(RBACApiClient::PolicyApi) do |api|
119
+ Service.paginate(api, :list_policies, {}).to_a
120
+ end
121
+ end
122
+
123
+ def find_uuid(type, data, name)
124
+ result = data.detect { |item| item.name == name }
125
+ raise "#{type} #{name} not found in RBAC service" unless result
126
+
127
+ result.uuid
128
+ end
129
+
130
+ def create_request(user_file)
131
+ raise "File #{user_file} not found" unless File.exist?(user_file)
132
+
133
+ user = YAML.load_file(user_file)
134
+ {:headers => {'x-rh-identity' => Base64.strict_encode64(user.to_json)}, :original_url => '/'}
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,67 @@
1
+ module ManageIQ
2
+ module API
3
+ module Common
4
+ module RBAC
5
+ require 'rbac-api-client'
6
+
7
+ class Service
8
+ def self.call(klass)
9
+ setup
10
+ yield init(klass)
11
+ rescue RBACApiClient::ApiError => err
12
+ Rails.logger.error("RBACApiClient::ApiError #{err.message} ")
13
+ raise
14
+ end
15
+
16
+ def self.paginate(obj, method, pagination_options, *method_args)
17
+ Enumerator.new do |enum|
18
+ opts = { :limit => 10, :offset => 0 }.merge(pagination_options)
19
+ count = nil
20
+ fetched = 0
21
+ begin
22
+ loop do
23
+ args = [method_args, opts].flatten.compact
24
+ result = obj.send(method, *args)
25
+ count ||= result.meta.count
26
+ opts[:offset] = opts[:offset] + result.data.count
27
+ result.data.each do |element|
28
+ enum.yield element
29
+ end
30
+ fetched += result.data.count
31
+ break if count == fetched || result.data.empty?
32
+ end
33
+ rescue StandardError => e
34
+ Rails.logger.error("Exception when calling pagination on #{method} #{e}")
35
+ raise
36
+ end
37
+ end
38
+ end
39
+
40
+ private_class_method def self.setup
41
+ RBACApiClient.configure do |config|
42
+ config.host = ENV['RBAC_URL'] || 'localhost'
43
+ config.scheme = URI.parse(ENV['RBAC_URL']).try(:scheme) || 'http'
44
+ dev_credentials(config)
45
+ end
46
+ end
47
+
48
+ private_class_method def self.init(klass)
49
+ headers = ManageIQ::API::Common::Request.current_forwardable
50
+ Rails.logger.info("Sending Headers to RBAC #{headers}")
51
+ klass.new.tap do |api|
52
+ api.api_client.default_headers = api.api_client.default_headers.merge(headers)
53
+ end
54
+ end
55
+
56
+ private_class_method def self.dev_credentials(config)
57
+ # Set up user/pass for basic auth if we're in dev and they exist.
58
+ if Rails.env.development?
59
+ config.username = ENV.fetch('DEV_USERNAME')
60
+ config.password = ENV.fetch('DEV_PASSWORD')
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,60 @@
1
+ module ManageIQ
2
+ module API
3
+ module Common
4
+ module RBAC
5
+ class ShareResource
6
+ require 'rbac-api-client'
7
+ include Utilities
8
+
9
+ def initialize(options)
10
+ @app_name = options[:app_name]
11
+ @resource_name = options[:resource_name]
12
+ @permissions = options[:permissions]
13
+ @resource_ids = options[:resource_ids]
14
+ @group_uuids = SortedSet.new(options[:group_uuids])
15
+ @acls = RBAC::ACL.new
16
+ end
17
+
18
+ def process
19
+ validate_groups
20
+ @roles = RBAC::Roles.new("#{@app_name}-#{@resource_name}-")
21
+ @group_uuids.each { |uuid| manage_roles_for_group(uuid) }
22
+ self
23
+ end
24
+
25
+ private
26
+
27
+ def manage_roles_for_group(group_uuid)
28
+ @resource_ids.each do |resource_id|
29
+ name = unique_name(resource_id, group_uuid)
30
+ role = @roles.find(name)
31
+ role ? update_existing_role(role, resource_id) : add_new_role(name, group_uuid, resource_id)
32
+ end
33
+ end
34
+
35
+ def update_existing_role(role, resource_id)
36
+ role.access = @acls.add(role.access, resource_id, @permissions)
37
+ @roles.update(role) if role.access.present?
38
+ end
39
+
40
+ def add_new_role(name, group_uuid, resource_id)
41
+ acls = @acls.create(resource_id, @permissions)
42
+ role = @roles.add(name, acls)
43
+ add_policy(name, group_uuid, role.uuid)
44
+ end
45
+
46
+ def add_policy(name, group_uuid, role_uuid)
47
+ Service.call(RBACApiClient::PolicyApi) do |api_instance|
48
+ policy_in = RBACApiClient::PolicyIn.new
49
+ policy_in.name = name
50
+ policy_in.description = 'Shared Policy'
51
+ policy_in.group = group_uuid
52
+ policy_in.roles = [role_uuid]
53
+ api_instance.create_policies(policy_in)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,32 @@
1
+ module ManageIQ
2
+ module API
3
+ module Common
4
+ module RBAC
5
+ require 'rbac-api-client'
6
+
7
+ class UnshareResource < ShareResource
8
+ attr_accessor :count
9
+
10
+ def initialize(options)
11
+ @count = 0
12
+ super
13
+ end
14
+
15
+ private
16
+
17
+ def manage_roles_for_group(group_uuid)
18
+ @resource_ids.each do |resource_id|
19
+ name = unique_name(resource_id, group_uuid)
20
+ role = @roles.find(name)
21
+ next unless role
22
+
23
+ role.access = @acls.remove(role.access, resource_id, @permissions)
24
+ role.access.present? ? @roles.update(role) : @roles.delete(role)
25
+ @count += 1
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,30 @@
1
+ module ManageIQ
2
+ module API
3
+ module Common
4
+ module RBAC
5
+ module Utilities
6
+ def validate_groups
7
+ Service.call(RBACApiClient::GroupApi) do |api|
8
+ uuids = SortedSet.new
9
+ Service.paginate(api, :list_groups, {}).each { |group| uuids << group.uuid }
10
+ missing = @group_uuids - uuids
11
+ raise ManageIQ::API::Common::InvalidParameter, "The following group uuids are missing #{missing.to_a.join(",")}" unless missing.empty?
12
+ end
13
+ end
14
+
15
+ def unique_name(resource_id, group_id)
16
+ "#{@app_name}-#{@resource_name}-#{resource_id}-group-#{group_id}"
17
+ end
18
+
19
+ def parse_ids_from_name(name)
20
+ @regexp ||= Regexp.new("#{@app_name}-#{@resource_name}-(?<resource_id>.*)-group-(?<group_uuid>.*)")
21
+ result = @regexp.match(name)
22
+ if result
23
+ [result[:resource_id], result[:group_uuid]]
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -7,6 +7,9 @@ module ManageIQ
7
7
  end
8
8
  end
9
9
 
10
+ class InvalidParameter < StandardError
11
+ end
12
+
10
13
  class Request
11
14
  REQUEST_ID_KEY = "x-rh-insights-request-id".freeze
12
15
  IDENTITY_KEY = 'x-rh-identity'.freeze
@@ -1,7 +1,7 @@
1
1
  module ManageIQ
2
2
  module API
3
3
  module Common
4
- VERSION = "1.1.0".freeze
4
+ VERSION = "2.0.0".freeze
5
5
  end
6
6
  end
7
7
  end
@@ -0,0 +1,44 @@
1
+ RSpec.shared_context "rbac_objects" do
2
+ let(:app_name) { 'catalog' }
3
+ let(:resource) { "portfolios" }
4
+ let(:permissions) { ["#{app_name}:#{resource}:read"] }
5
+ let(:resource_id1) { "1" }
6
+ let(:resource_id2) { "2" }
7
+ let(:resource_id3) { "3" }
8
+ let(:group1) { instance_double(RBACApiClient::GroupOut, :name => 'group1', :uuid => "123") }
9
+ let(:group2) { instance_double(RBACApiClient::GroupOut, :name => 'group2', :uuid => "12345") }
10
+ let(:group3) { instance_double(RBACApiClient::GroupOut, :name => 'group3', :uuid => "45665") }
11
+ let(:role1) { instance_double(RBACApiClient::RoleOut, :name => "#{app_name}-#{resource}-#{resource_id1}-group-#{group1.uuid}", :uuid => "67899") }
12
+ let(:role2) { instance_double(RBACApiClient::RoleOut, :name => "#{app_name}-#{resource}-#{resource_id2}-group-#{group1.uuid}", :uuid => "55555") }
13
+ let(:role1_detail) { instance_double(RBACApiClient::RoleWithAccess, :name => role1.name, :uuid => role1.uuid, :access => [access1]) }
14
+ let(:role2_detail) { instance_double(RBACApiClient::RoleWithAccess, :name => role2.name, :uuid => role2.uuid, :access => []) }
15
+ let(:role1_detail_updated) { instance_double(RBACApiClient::RoleWithAccess, :name => role1.name, :uuid => role1.uuid, :access => []) }
16
+ let(:groups) { [group1, group2, group3] }
17
+ let(:roles) { [role1] }
18
+ let(:policies) { [instance_double(RBACApiClient::PolicyIn, :group => group1, :roles => roles)] }
19
+ let(:filter1) { instance_double(RBACApiClient::ResourceDefinitionFilter, :key => 'id', :operation => 'equal', :value => resource_id1) }
20
+ let(:resource_def1) { instance_double(RBACApiClient::ResourceDefinition, :attribute_filter => filter1) }
21
+ let(:filter2) { instance_double(RBACApiClient::ResourceDefinitionFilter, :key => 'id', :operation => 'equal', :value => resource_id2) }
22
+ let(:resource_def2) { instance_double(RBACApiClient::ResourceDefinition, :attribute_filter => filter2) }
23
+ let(:filter3) { instance_double(RBACApiClient::ResourceDefinitionFilter, :key => 'id', :operation => 'equal', :value => resource_id3) }
24
+ let(:resource_def3) { instance_double(RBACApiClient::ResourceDefinition, :attribute_filter => filter3) }
25
+ let(:filter4) { instance_double(RBACApiClient::ResourceDefinitionFilter, :key => 'id', :operation => 'equal', :value => '*') }
26
+ let(:resource_def4) { instance_double(RBACApiClient::ResourceDefinition, :attribute_filter => filter4) }
27
+ let(:access1) { instance_double(RBACApiClient::Access, :permission => "#{app_name}:#{resource}:read", :resource_definitions => [resource_def1]) }
28
+ let(:access2) { instance_double(RBACApiClient::Access, :permission => "#{app_name}:#{resource}:write", :resource_definitions => [resource_def2]) }
29
+ let(:access3) { instance_double(RBACApiClient::Access, :permission => "#{app_name}:#{resource}:order", :resource_definitions => []) }
30
+ let(:admin_access) { instance_double(RBACApiClient::Access, :permission => "#{app_name}:#{resource}:read", :resource_definitions => [resource_def4]) }
31
+ let(:group_uuids) { [group1.uuid, group2.uuid, group3.uuid] }
32
+ let(:api_instance) { double }
33
+ let(:rs_class) { class_double("ManageIQ::API::Common::RBAC::Service").as_stubbed_const(:transfer_nested_constants => true) }
34
+ let(:current_user) { '{{username}}' }
35
+ let(:id_value) { '*' }
36
+ let(:owner_filter) { instance_double(RBACApiClient::ResourceDefinitionFilter, :key => 'owner', :operation => 'equal', :value => current_user) }
37
+ let(:owner_resource_def) { instance_double(RBACApiClient::ResourceDefinition, :attribute_filter => owner_filter) }
38
+ let(:id_filter) { instance_double(RBACApiClient::ResourceDefinitionFilter, :key => 'id', :operation => 'equal', :value => id_value) }
39
+ let(:id_resource_def) { instance_double(RBACApiClient::ResourceDefinition, :attribute_filter => id_filter) }
40
+ let(:owner_resource) { 'orders' }
41
+ let(:owner_permission) { "#{app_name}:#{owner_resource}:read" }
42
+ let(:owner_access) { instance_double(RBACApiClient::Access, :permission => owner_permission, :resource_definitions => [owner_resource_def]) }
43
+ let(:all_access) { instance_double(RBACApiClient::Access, :permission => owner_permission, :resource_definitions => [id_resource_def]) }
44
+ end
@@ -0,0 +1,26 @@
1
+ module ServiceSpecHelper
2
+ RSpec.configure do |config|
3
+ config.around(:example, :type => :service) do |example|
4
+ default_tenant = Tenant.first_or_create!(:external_tenant => default_account_number)
5
+
6
+ ActsAsTenant.with_tenant(default_tenant) do
7
+ example.call
8
+ end
9
+
10
+ Tenant.delete_all
11
+ end
12
+ end
13
+
14
+ def default_headers
15
+ { 'x-rh-identity' => encoded_user_hash,
16
+ 'x-rh-insights-request-id' => 'gobbledygook' }
17
+ end
18
+
19
+ def original_url
20
+ "http://example.com"
21
+ end
22
+
23
+ def default_request
24
+ { :headers => default_headers, :original_url => original_url }
25
+ end
26
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: manageiq-api-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ManageIQ Authors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-15 00:00:00.000000000 Z
11
+ date: 2019-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: acts_as_tenant
@@ -134,14 +134,14 @@ dependencies:
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: 0.5.0
137
+ version: 0.6.1
138
138
  type: :runtime
139
139
  prerelease: false
140
140
  version_requirements: !ruby/object:Gem::Requirement
141
141
  requirements:
142
142
  - - "~>"
143
143
  - !ruby/object:Gem::Version
144
- version: 0.5.0
144
+ version: 0.6.1
145
145
  - !ruby/object:Gem::Dependency
146
146
  name: graphql
147
147
  requirement: !ruby/object:Gem::Requirement
@@ -288,6 +288,20 @@ dependencies:
288
288
  - - ">="
289
289
  - !ruby/object:Gem::Version
290
290
  version: '0'
291
+ - !ruby/object:Gem::Dependency
292
+ name: webmock
293
+ requirement: !ruby/object:Gem::Requirement
294
+ requirements:
295
+ - - ">="
296
+ - !ruby/object:Gem::Version
297
+ version: '0'
298
+ type: :development
299
+ prerelease: false
300
+ version_requirements: !ruby/object:Gem::Requirement
301
+ requirements:
302
+ - - ">="
303
+ - !ruby/object:Gem::Version
304
+ version: '0'
291
305
  description: Header, Encryption, RBAC, Serialization, Pagination and other common
292
306
  behavior for microservices
293
307
  email:
@@ -307,9 +321,9 @@ files:
307
321
  - lib/generators/shared_utilities/templates/migration_existing.rb
308
322
  - lib/manageiq-api-common.rb
309
323
  - lib/manageiq/api/common.rb
310
- - lib/manageiq/api/common/api_error.rb
311
324
  - lib/manageiq/api/common/application_controller_mixins/api_doc.rb
312
325
  - lib/manageiq/api/common/application_controller_mixins/common.rb
326
+ - lib/manageiq/api/common/application_controller_mixins/exception_handling.rb
313
327
  - lib/manageiq/api/common/application_controller_mixins/openapi_enabled.rb
314
328
  - lib/manageiq/api/common/application_controller_mixins/parameters.rb
315
329
  - lib/manageiq/api/common/application_controller_mixins/request_body_validation.rb
@@ -341,13 +355,25 @@ files:
341
355
  - lib/manageiq/api/common/open_api/serializer.rb
342
356
  - lib/manageiq/api/common/option_redirect_enhancements.rb
343
357
  - lib/manageiq/api/common/paginated_response.rb
358
+ - lib/manageiq/api/common/rbac/access.rb
359
+ - lib/manageiq/api/common/rbac/acl.rb
360
+ - lib/manageiq/api/common/rbac/policies.rb
361
+ - lib/manageiq/api/common/rbac/query_shared_resource.rb
362
+ - lib/manageiq/api/common/rbac/roles.rb
363
+ - lib/manageiq/api/common/rbac/seed.rb
364
+ - lib/manageiq/api/common/rbac/service.rb
365
+ - lib/manageiq/api/common/rbac/share_resource.rb
366
+ - lib/manageiq/api/common/rbac/unshare_resource.rb
367
+ - lib/manageiq/api/common/rbac/utilities.rb
344
368
  - lib/manageiq/api/common/request.rb
345
369
  - lib/manageiq/api/common/routing.rb
346
370
  - lib/manageiq/api/common/user.rb
347
371
  - lib/manageiq/api/common/version.rb
348
372
  - lib/tasks/manageiq/api/common_tasks.rake
349
373
  - spec/support/default_as_json.rb
374
+ - spec/support/rbac_shared_contexts.rb
350
375
  - spec/support/requests_spec_helper.rb
376
+ - spec/support/service_spec_helper.rb
351
377
  - spec/support/user_header_spec_helper.rb
352
378
  homepage: https://github.com/ManageIQ/manageiq-api-common.git
353
379
  licenses:
@@ -1,21 +0,0 @@
1
- module ManageIQ
2
- module API
3
- module Common
4
- class ApiError < StandardError
5
- attr_reader :errors
6
-
7
- def initialize(status, detail)
8
- @errors = ErrorDocument.new.add(status, detail)
9
- end
10
-
11
- def status
12
- @errors.status
13
- end
14
-
15
- def add(status, detail)
16
- @errors.add(status, detail)
17
- end
18
- end
19
- end
20
- end
21
- end