Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions app/controllers/api/v2/compliance/hosts_bulk_actions_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module Api::V2
module Compliance
class HostsBulkActionsController < ::Api::V2::BaseController
include Api::V2::BulkHostsExtension

rescue_from ActionController::ParameterMissing do |exception|
render_error(:custom_error, :status => :unprocessable_entity, :locals => { :message => exception.message })
end

before_action :find_editable_hosts, only: [:change_openscap_proxy]
before_action :find_openscap_proxy, only: [:change_openscap_proxy]
before_action :validate_openscap_proxy_feature, only: [:change_openscap_proxy]

def_param_group :bulk_host_ids do
param :included, Hash, :desc => N_("Hosts to include in the action"), :required => true, :action_aware => true do
param :search, String, :required => false, :desc => N_("Search string describing which hosts to perform the action on")
param :ids, Array, :required => false, :desc => N_("List of host ids to perform the action on")
end
param :excluded, Hash, :desc => N_("Hosts to explicitly exclude in the action."\
" All other hosts will be included in the action,"\
" unless an included parameter is passed as well."), :required => true, :action_aware => true do
param :ids, Array, :required => false, :desc => N_("List of host ids to exclude and not perform the action on")
end
end

api :PUT, "/hosts/bulk/change_openscap_proxy", N_("Assign OpenSCAP Proxy to multiple hosts")
param_group :bulk_host_ids
param :openscap_proxy_id, :number, :required => true, :desc => N_("ID of the OpenSCAP Proxy to assign to the hosts")
def change_openscap_proxy
failed_host_ids = []
host_count = @hosts.count

@hosts.find_each do |host|
host.openscap_proxy = @smart_proxy
failed_host_ids << host.id unless host.save
end

if failed_host_ids.empty?
message = _("OpenSCAP Proxy is set to %s") % @smart_proxy.name
process_response(true, {
:message => n_("Updated host: #{message}", "Updated hosts: #{message}", host_count),
})
else
failed_count = failed_host_ids.size
success_count = host_count - failed_count

parts = [
n_("Failed to assign OpenSCAP Proxy to %{failed} of %{total} host.",
"Failed to assign OpenSCAP Proxy to %{failed} of %{total} hosts.",
host_count) % { failed: failed_count, total: host_count },
]
if success_count > 0
parts << n_("Successfully updated %{success} host.",
"Successfully updated %{success} hosts.",
success_count) % { success: success_count }
end

render_error(:bulk_hosts_error, :status => :unprocessable_entity,
:locals => {
:message => parts.join(' '),
:failed_host_ids => failed_host_ids,
})
end
end

private

def find_editable_hosts
find_bulk_hosts(:edit_hosts, params)
end

def find_openscap_proxy
@smart_proxy = ::SmartProxy.authorized(:view_smart_proxies)
.find_by(:id => params.require(:openscap_proxy_id))
return if @smart_proxy

render_error(:custom_error, :status => :unprocessable_entity,
:locals => { :message => _("OpenSCAP Proxy with id %s not found") % params[:openscap_proxy_id] })
end

def validate_openscap_proxy_feature
return if @smart_proxy.has_feature?('Openscap')

render_error(:custom_error, :status => :unprocessable_entity,
:locals => { :message => _("The selected OpenSCAP Proxy does not have the OpenSCAP feature enabled.") })
end
end
end
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
end
end
end

match 'hosts/bulk/change_openscap_proxy', :to => 'compliance/hosts_bulk_actions#change_openscap_proxy', :via => [:put]
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/foreman_openscap/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ class Engine < ::Rails::Engine
:resource_type => 'ForemanOpenscap::ScapContent'
permission :edit_hosts, { :hosts => %i[openscap_proxy_changed
select_multiple_openscap_proxy
update_multiple_openscap_proxy] },
update_multiple_openscap_proxy],
'api/v2/compliance/hosts_bulk_actions' => [:change_openscap_proxy] },
:resource_type => "Host"
permission :view_hosts, { 'api/v2/hosts' => [:policies_enc] }, :resource_type => 'Host'
permission :edit_hostgroups, { :hostgroups => [:openscap_proxy_changed] }, :resource_type => "Hostgroup"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
require 'test_plugin_helper'

class Api::V2::Compliance::HostsBulkActionsControllerTest < ActionController::TestCase
tests Api::V2::Compliance::HostsBulkActionsController

def setup
as_admin do
@organization = FactoryBot.create(:organization)
@location = FactoryBot.create(:location)
@proxy = FactoryBot.create(:openscap_proxy,
:organizations => [@organization],
:locations => [@location])
@host1 = FactoryBot.create(:host, :managed,
:organization => @organization,
:location => @location)
@host2 = FactoryBot.create(:host, :managed,
:organization => @organization,
:location => @location)
@host_ids = [@host1.id, @host2.id]
end
end

def valid_bulk_params(host_ids = @host_ids)
{
:organization_id => @organization.id,
:location_id => @location.id,
:included => {
:ids => host_ids,
},
:excluded => {
:ids => [],
},
}
end

test "should assign openscap proxy to selected hosts" do
put :change_openscap_proxy,
params: valid_bulk_params.merge(:openscap_proxy_id => @proxy.id),
session: set_session_user

assert_response :success
response = ActiveSupport::JSON.decode(@response.body)
assert_match(/Updated hosts: OpenSCAP Proxy is set to/, response['message'])
assert_includes response['message'], @proxy.name

[@host1, @host2].each do |host|
host.reload
assert_equal @proxy.id, host.openscap_proxy_id
end
end

test "should require openscap_proxy_id" do
put :change_openscap_proxy,
params: valid_bulk_params,
session: set_session_user

assert_response :unprocessable_entity
response = ActiveSupport::JSON.decode(@response.body)
assert_match(/openscap_proxy_id/, response['error']['message'])
end

test "should return error when proxy is not found" do
put :change_openscap_proxy,
params: valid_bulk_params.merge(:openscap_proxy_id => 0),
session: set_session_user

assert_response :unprocessable_entity
response = ActiveSupport::JSON.decode(@response.body)
assert_match(/OpenSCAP Proxy with id .* not found/, response['error']['message'])
end

test "should return error when proxy lacks Openscap feature" do
other_proxy = FactoryBot.create(:smart_proxy,
:organizations => [@organization],
:locations => [@location])
openscap_feature = Feature.find_by(:name => 'Openscap')
other_proxy.features.delete(openscap_feature) if openscap_feature
refute other_proxy.reload.has_feature?('Openscap')

put :change_openscap_proxy,
params: valid_bulk_params.merge(:openscap_proxy_id => other_proxy.id),
session: set_session_user

assert_response :unprocessable_entity
response = ActiveSupport::JSON.decode(@response.body)
assert_match(/OpenSCAP Proxy does not have the OpenSCAP feature/, response['error']['message'])
end

test "should assign openscap proxy for a single host" do
put :change_openscap_proxy,
params: valid_bulk_params([@host1.id]).merge(:openscap_proxy_id => @proxy.id),
session: set_session_user

assert_response :success
response = ActiveSupport::JSON.decode(@response.body)
assert_match(/Updated host: OpenSCAP Proxy is set to/, response['message'])

@host1.reload
assert_equal @proxy.id, @host1.openscap_proxy_id
@host2.reload
assert_nil @host2.openscap_proxy_id
end

test "should report failed and successful counts on partial failure" do
Host.any_instance.stubs(:save).returns(false).then.returns(true)

put :change_openscap_proxy,
params: valid_bulk_params.merge(:openscap_proxy_id => @proxy.id),
session: set_session_user

assert_response :unprocessable_entity
response = ActiveSupport::JSON.decode(@response.body)
assert_match(/Failed to assign OpenSCAP Proxy to 1 of 2 hosts/, response['error']['message'])
assert_match(/Successfully updated 1 host/, response['error']['message'])
assert_equal 1, response['error']['failed_host_ids'].size
assert_includes @host_ids, response['error']['failed_host_ids'].first
end

test "should report only failures when all hosts fail" do
Host.any_instance.stubs(:save).returns(false)

put :change_openscap_proxy,
params: valid_bulk_params.merge(:openscap_proxy_id => @proxy.id),
session: set_session_user

assert_response :unprocessable_entity
response = ActiveSupport::JSON.decode(@response.body)
assert_match(/Failed to assign OpenSCAP Proxy to 2 of 2 hosts/, response['error']['message'])
refute_match(/Successfully updated/, response['error']['message'])
assert_equal @host_ids.sort, response['error']['failed_host_ids'].sort
end
end
Loading
Loading