manageiq-api-common 1.1.0 → 2.0.0

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