Skip to content

Commit

Permalink
feat(APIv2): RHINENG-13519 expose the RuleTree on profiles/tailorings
Browse files Browse the repository at this point in the history
  • Loading branch information
skateman committed Oct 11, 2024
1 parent 505c9ee commit 7e99cff
Show file tree
Hide file tree
Showing 16 changed files with 5,165 additions and 4,650 deletions.
10 changes: 10 additions & 0 deletions app/controllers/v2/profiles_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ def show
end
permission_for_action :show, Rbac::COMPLIANCE_VIEWER

def rule_tree
render json: profile.rule_tree
end
permission_for_action :rule_tree, Rbac::COMPLIANCE_VIEWER
permitted_params_for_action :rule_tree, id: ID_TYPE.required

private

def profiles
Expand All @@ -30,5 +36,9 @@ def resource
def serializer
V2::ProfileSerializer
end

def extra_fields
%i[security_guide_id]
end
end
end
6 changes: 6 additions & 0 deletions app/controllers/v2/tailorings_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ def show
end
permission_for_action :show, Rbac::POLICY_READ

def rule_tree
render json: tailoring.rule_tree
end
permission_for_action :rule_tree, Rbac::COMPLIANCE_VIEWER
permitted_params_for_action :rule_tree, id: ID_TYPE.required

def create
# Look up the latest Profile supporting the given OS minor version
new_tailoring = V2::Tailoring.for_policy(policy, permitted_params[:os_minor_version])
Expand Down
25 changes: 25 additions & 0 deletions app/models/concerns/v2/rule_tree.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module V2
# Methods that are related to getting hierarchical structure of rules and rule groups
module RuleTree
extend ActiveSupport::Concern

included do
# Builds the hierarchical structure of groups and rules
def rule_tree
cached_rules = rules.order(:precedence).select(:id, :rule_group_id).group_by(&:rule_group_id)

rule_groups.order(:precedence).select(:id, :ancestry).arrange_serializable do |group, children|
{
id: group.id,
type: :rule_group,
children: children + (cached_rules[group.id]&.map do |rule|
{ id: rule.id, type: :rule }
end || [])
}
end
end
end
end
end
3 changes: 3 additions & 0 deletions app/models/v2/policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module V2
# Model for SCAP policies
class Policy < ApplicationRecord
include V2::RuleTree

# FIXME: clean up after the remodel
self.table_name = :v2_policies
self.primary_key = :id
Expand All @@ -18,6 +20,7 @@ class Policy < ApplicationRecord
has_many :rules, through: :tailoring_rules, class_name: 'V2::Rule'
has_many :policy_systems, class_name: 'V2::PolicySystem', dependent: :destroy
has_many :systems, through: :policy_systems, class_name: 'V2::System'
has_many :rule_groups, through: :security_guide, class_name: 'V2::RuleGroup'

validates :account, presence: true
validates :profile, presence: true
Expand Down
3 changes: 3 additions & 0 deletions app/models/v2/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module V2
# Model for Canonical Profile
class Profile < ApplicationRecord
include V2::RuleTree

# FIXME: clean up after the remodel
self.table_name = :canonical_profiles
self.primary_key = :id
Expand All @@ -18,6 +20,7 @@ class Profile < ApplicationRecord
has_many :profile_rules, class_name: 'V2::ProfileRule', dependent: :destroy
has_many :rules, through: :profile_rules, class_name: 'V2::Rule'
has_many :os_minor_versions, class_name: 'V2::ProfileOsMinorVersion', dependent: :destroy
has_many :rule_groups, through: :security_guide, class_name: 'V2::RuleGroup'

def variant_for_minor(version)
self.class.joins(:security_guide, :os_minor_versions)
Expand Down
17 changes: 2 additions & 15 deletions app/models/v2/security_guide.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module V2
# Model for Security Guides
class SecurityGuide < ApplicationRecord
include V2::RuleTree

# FIXME: clean up after the remodel
self.primary_key = :id

Expand Down Expand Up @@ -37,20 +39,5 @@ class SecurityGuide < ApplicationRecord
def self.os_versions
reselect(:os_major_version).distinct.reorder(:os_major_version).map(&:os_major_version)
end

# Builds the hierarchical structure of groups and rules
def rule_tree
cached_rules = rules.order(:precedence).select(:id, :rule_group_id).group_by(&:rule_group_id)

rule_groups.order(:precedence).select(:id, :ancestry).arrange_serializable do |group, children|
{
id: group.id,
type: :rule_group,
children: children + (cached_rules[group.id]&.map do |rule|
{ id: rule.id, type: :rule }
end || [])
}
end
end
end
end
3 changes: 3 additions & 0 deletions app/models/v2/tailoring.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module V2
# Model for profile tailoring
class Tailoring < ApplicationRecord
include V2::RuleTree

GROUP_ANCESTRY_IDS = Arel::Nodes::NamedFunction.new(
'CAST',
[
Expand Down Expand Up @@ -40,6 +42,7 @@ class Tailoring < ApplicationRecord
inverse_of: :tailoring
has_many :rules, class_name: 'V2::Rule', through: :tailoring_rules
has_many :test_results, class_name: 'V2::TestResult', dependent: :destroy
has_many :rule_groups, through: :security_guide, class_name: 'V2::RuleGroup'

searchable_by :os_minor_version, %i[eq ne]

Expand Down
4 changes: 4 additions & 0 deletions app/policies/v2/profile_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ def show?
true
end

def rule_tree?
true
end

# All users should see all Profiles currently
class Scope < V2::ApplicationPolicy::Scope
end
Expand Down
4 changes: 4 additions & 0 deletions app/policies/v2/tailoring_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ def show?
match_account?
end

def rule_tree?
true
end

def update?
match_account?
end
Expand Down
6 changes: 5 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ def draw_routes(prefix)
resources :rule_groups, only: %i[index show], parents: %i[security_guide]

resources :profiles, only: %i[index show], parents: %i[security_guide] do
get :rule_tree, on: :member, parents: %i[security_guide]

resources :rules, only: %i[index show], parents: %i[security_guide profiles]
end
end

resources :policies, except: %i[new edit] do
resources :tailorings, only: %i[index show create update], parents: %i[policy] do
resources :rules, only: %i[index create update destroy], parents: %i[policies tailorings]
get :tailoring_file, on: :member, defaults: { format: 'xml' }, constraints: { format: /json|xml/ }
get :rule_tree, on: :member, parents: %i[policy]

resources :rules, only: %i[index create update destroy], parents: %i[policies tailorings]
end

resources :systems, only: %i[index create update destroy], parents: %i[policies] do
Expand Down
62 changes: 38 additions & 24 deletions spec/controllers/v2/profiles_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,47 @@
allow(controller).to receive(:rbac_allowed?).and_return(rbac_allowed?)
end

describe 'GET index' do
let(:parent) { FactoryBot.create(:v2_security_guide) }
let(:extra_params) { { security_guide_id: parent.id } }
let(:item_count) { 2 }
let(:items) do
FactoryBot.create_list(
:v2_profile,
item_count,
value_count: 10,
security_guide: parent
).sort_by(&:id)
context '/security_guides/:id/profiles' do
describe 'GET index' do
let(:parent) { FactoryBot.create(:v2_security_guide) }
let(:extra_params) { { security_guide_id: parent.id } }
let(:item_count) { 2 }
let(:items) do
FactoryBot.create_list(
:v2_profile,
item_count,
value_count: 10,
security_guide: parent
).sort_by(&:id)
end

it_behaves_like 'collection', :security_guide
include_examples 'with metadata', :security_guide
it_behaves_like 'paginable', :security_guide
it_behaves_like 'sortable', :security_guide
it_behaves_like 'searchable', :security_guide
end

it_behaves_like 'collection', :security_guide
include_examples 'with metadata', :security_guide
it_behaves_like 'paginable', :security_guide
it_behaves_like 'sortable', :security_guide
it_behaves_like 'searchable', :security_guide
end
describe 'GET show' do
let(:item) { FactoryBot.create(:v2_profile) }
let(:parent) { item.security_guide }
let(:extra_params) { { security_guide_id: parent.id, id: item.id } }
let(:notfound_params) { extra_params.merge(security_guide_id: FactoryBot.create(:v2_security_guide).id) }

it_behaves_like 'individual', :security_guide
it_behaves_like 'indexable', :ref_id, :security_guide
end

describe 'GET rule_tree' do
let(:item) { FactoryBot.create(:v2_profile, rule_count: 5) }
let(:parent) { item.security_guide }

describe 'GET show' do
let(:item) { FactoryBot.create(:v2_profile) }
let(:parent) { item.security_guide }
let(:extra_params) { { security_guide_id: parent.id, id: item.id } }
let(:notfound_params) { extra_params.merge(security_guide_id: FactoryBot.create(:v2_security_guide).id) }
it 'calls the rule tree on the model' do
get :rule_tree, params: { id: item.id, security_guide_id: parent.id, parents: %i[security_guide] }

it_behaves_like 'individual', :security_guide
it_behaves_like 'indexable', :ref_id, :security_guide
expect(response).to have_http_status :ok
expect(response.parsed_body).not_to be_empty
end
end
end
end
20 changes: 20 additions & 0 deletions spec/controllers/v2/tailorings_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@
it_behaves_like 'indexable', :os_minor_version, :policy
end

describe 'GET rule_tree' do
let(:os_minor_version) { SecureRandom.random_number(10) }
let(:parent) { FactoryBot.create(:v2_policy, account: current_user.account, profile: canonical_profile) }

let(:item) do
FactoryBot.create(:v2_tailoring, :with_mixed_rules, policy: parent, os_minor_version: os_minor_version)
end

let(:canonical_profile) do
FactoryBot.create(:v2_profile, rule_count: 5, ref_id_suffix: 'foo', supports_minors: [os_minor_version])
end

it 'calls the rule tree on the model' do
get :rule_tree, params: { id: item.id, policy_id: parent.id, parents: %i[policy] }

expect(response).to have_http_status :ok
expect(response.parsed_body).not_to be_empty
end
end

describe 'POST create' do
let(:os_minor_version) { SecureRandom.random_number(10) }
let(:parent) { FactoryBot.create(:v2_policy, account: current_user.account, profile: canonical_profile) }
Expand Down
35 changes: 35 additions & 0 deletions spec/integration/v2/profiles_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,39 @@
end
end
end

path '/security_guides/{security_guide_id}/profiles/{profile_id}/rule_tree' do
let(:item) { FactoryBot.create(:v2_profile, rule_count: 10) }

get 'Request the Rule Tree of a Profile' do
v2_auth_header
tags 'Content'
description 'Returns the Rule Tree of a Profile'
operationId 'ProfileTree'
content_types

parameter name: :security_guide_id, in: :path, type: :string, required: true
parameter name: :profile_id, in: :path, type: :string, required: true

response '200', 'Returns the Rule Tree of a Profile' do
let(:profile_id) { item.id }
let(:security_guide_id) { item.security_guide.id }
schema ref_schema('rule_tree')

after { |e| autogenerate_examples(e, 'Returns the Rule Tree of a Profile') }

run_test!
end

response '404', 'Returns with Not Found' do
let(:profile_id) { Faker::Internet.uuid }
let(:security_guide_id) { Faker::Internet.uuid }
schema ref_schema('errors')

after { |e| autogenerate_examples(e, 'Description of an error when requesting a non-existing Profile') }

run_test!
end
end
end
end
2 changes: 1 addition & 1 deletion spec/integration/v2/security_guides_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@

parameter name: :security_guide_id, in: :path, type: :string, required: true

response '200', 'Returns a the Rule Tree of Security Guide' do
response '200', 'Returns the Rule Tree of a Security Guide' do
let(:security_guide_id) { item.id }
schema ref_schema('rule_tree')

Expand Down
43 changes: 43 additions & 0 deletions spec/integration/v2/tailorings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,49 @@
end
end

path '/policies/{policy_id}/tailorings/{tailoring_id}/rule_tree' do
let(:policy_id) do
FactoryBot.create(
:v2_policy,
account: user.account,
profile: FactoryBot.create(:v2_profile, rule_count: 10, ref_id_suffix: 'foo', supports_minors: [1])
).id
end

let(:item) do
FactoryBot.create(:v2_tailoring, :with_mixed_rules, policy: V2::Policy.find(policy_id), os_minor_version: 1)
end

get 'Request the Rule Tree of a Tailoring' do
v2_auth_header
tags 'Policies'
description 'Returns the Rule Tree of a Tailoring'
operationId 'TailoringRuleTree'
content_types

parameter name: :policy_id, in: :path, type: :string, required: true
parameter name: :tailoring_id, in: :path, type: :string, required: true

response '200', 'Returns the Rule Tree of a Tailoring' do
let(:tailoring_id) { item.id }
schema ref_schema('rule_tree')

after { |e| autogenerate_examples(e, 'Returns the Rule Tree of a Tailoring') }

run_test!
end

response '404', 'Returns with Not Found' do
let(:tailoring_id) { Faker::Internet.uuid }
schema ref_schema('errors')

after { |e| autogenerate_examples(e, 'Description of an error when requesting a non-existing Tailoring') }

run_test!
end
end
end

path '/policies/{policy_id}/tailorings/{tailoring_id}/tailoring_file.json' do
let(:policy_id) do
FactoryBot.create(:v2_policy, :for_tailoring, account: user.account, supports_minors: [1]).id
Expand Down
Loading

0 comments on commit 7e99cff

Please sign in to comment.