Skip to content

Commit

Permalink
feat(APIv2): RHINENG-2149 implement reports endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
romanblanco committed May 15, 2024
1 parent ee8b215 commit e6e7316
Show file tree
Hide file tree
Showing 19 changed files with 4,482 additions and 3,091 deletions.
38 changes: 38 additions & 0 deletions app/controllers/v2/reports_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

module V2
# API for Reports (rule results)
class ReportsController < ApplicationController
def index
render_json reports
end
permission_for_action :index, Rbac::REPORT_READ

def show
render_json report
end
permission_for_action :show, Rbac::REPORT_READ

private

def reports
@reports ||= authorize(fetch_collection)
end

def report
@report ||= authorize(expand_resource.find(permitted_params[:id]))
end

def resource
V2::Report
end

def serializer
V2::ReportSerializer
end

def extra_fields
%i[account_id]
end
end
end
71 changes: 69 additions & 2 deletions app/models/v2/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,76 @@ class Report < ApplicationRecord
self.table_name = :reports
self.primary_key = :id

SYSTEM_COUNT = lambda do
AN::NamedFunction.new(
'COUNT', [V2::System.arel_table[:id]]
).filter(Pundit.policy_scope(User.current, V2::System).where_clause.ast)
end

COMPLIANT_SYSTEM_COUNT = lambda do
AN::NamedFunction.new(
'COUNT', [V2::System.arel_table[:id]]
).filter(
Pundit.policy_scope(User.current, V2::System).where_clause.ast.and(
V2::TestResult.arel_table[:score].gteq(V2::Report.arel_table[:compliance_threshold])
)
)
end

UNSUPPORTED_SYSTEM_COUNT = lambda do
AN::NamedFunction.new(
'COUNT', [V2::System.arel_table[:id]]
).filter(
Pundit.policy_scope(User.current, V2::System).where_clause.ast.and(
V2::TestResult.arel_table[:supported].not_eq('t')
)
)
end

# To prevent an autojoin with itself, there should not be an inverse relationship specified
belongs_to :policy, class_name: 'V2::Policy', foreign_key: :id # rubocop:disable Rails/InverseOf
has_many :tailorings, class_name: 'V2::Tailoring', through: :policy
has_many :systems, class_name: 'V2::System', through: :tailorings
belongs_to :account

belongs_to :profile, class_name: 'V2::Profile'
has_one :security_guide, through: :profile, class_name: 'V2::SecurityGuide'
has_many :tailorings, class_name: 'V2::Tailoring', foreign_key: :policy_id, dependent: nil # rubocop:disable Rails/InverseOf
has_many :test_results, class_name: 'V2::TestResult', dependent: nil, through: :tailorings
has_many :policy_systems, class_name: 'V2::PolicySystem', foreign_key: :policy_id, dependent: nil # rubocop:disable Rails/InverseOf
has_many :systems, class_name: 'V2::System', through: :test_results, dependent: nil
has_many :assigned_systems, class_name: 'V2::System', through: :policy_systems, source: :system

sortable_by :title
sortable_by :os_major_version
sortable_by :total_host_count
sortable_by :business_objective
sortable_by :compliance_threshold

searchable_by :title, %i[like unlike eq ne in notin]
searchable_by :os_major_version, %i[eq ne in notin] do |_key, op, val|
bind = ['IN', 'NOT IN'].include?(op) ? '(?)' : '?'

{
conditions: "security_guide.os_major_version #{op} #{bind}",
parameter: [val.split.map(&:to_i)]
}
end

validates :account, presence: true

def os_major_version
attributes['security_guide__os_major_version'] || try(:security_guide)&.os_major_version
end

def ref_id
attributes['profile__ref_id'] || try(:profile)&.ref_id
end

def profile_title
attributes['profile__title'] || try(:profile)&.title
end

def all_systems_exposed
total_system_count == try(:aggregate_assigned_system_count)
end
end
end
3 changes: 2 additions & 1 deletion app/models/v2/system.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ class System < ApplicationRecord
# rubocop:enable Rails/InverseOf
has_many :policy_systems, class_name: 'V2::PolicySystem', dependent: nil
has_many :policies, through: :policy_systems
has_many :reports, class_name: 'V2::Report', dependent: nil
has_many :reports, class_name: 'V2::Report', through: :policies
has_many :test_results, class_name: 'V2::TestResult', dependent: :destroy

OS_VERSION = AN::InfixOperation.new('->', Host.arel_table[:system_profile], AN::Quoted.new('operating_system'))
OS_MINOR_VERSION = AN::InfixOperation.new('->', OS_VERSION, AN::Quoted.new('minor')).as('os_minor_version')
Expand Down
3 changes: 2 additions & 1 deletion app/models/v2/tailoring.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ class Tailoring < ApplicationRecord
belongs_to :profile, class_name: 'V2::Profile'
has_one :security_guide, through: :profile, class_name: 'V2::SecurityGuide'
has_one :account, through: :policy, class_name: 'Account'
has_one :report, class_name: 'V2::Report', through: :policy, dependent: nil
has_many :tailoring_rules,
class_name: 'V2::TailoringRule',
dependent: :destroy,
inverse_of: :tailoring
has_many :rules, class_name: 'V2::Rule', through: :tailoring_rules
has_many :reports, class_name: 'V2::Report', dependent: nil
has_many :test_results, class_name: 'V2::TestResult', dependent: :destroy

searchable_by :os_minor_version, %i[eq ne]

Expand Down
9 changes: 5 additions & 4 deletions app/models/v2/test_result.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ class TestResult < ApplicationRecord
self.table_name = :v2_test_results
self.primary_key = :id

belongs_to :system, optional: true
belongs_to :tailoring
has_one :policy, through: :tailoring
has_one :security_guide, through: :tailoring
belongs_to :system, class_name: 'V2::System', optional: true
belongs_to :tailoring, class_name: 'V2::Tailoring'

has_one :policy, class_name: 'V2::Policy', through: :tailoring
has_one :security_guide, class_name: 'V2::SecurityGuide', through: :tailoring
end
end
23 changes: 23 additions & 0 deletions app/policies/v2/report_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# frozen_string_literal: true

module V2
# Policies for accessing Reports
class ReportPolicy < V2::ApplicationPolicy
def index?
true # FIXME: this is handled in scoping
end

def show?
match_account?
end

# Only show Reports in our user account
class Scope < V2::ApplicationPolicy::Scope
def resolve
return scope.where('1=0') if user&.account_id.blank?

scope.where(account_id: user.account_id)
end
end
end
end
18 changes: 18 additions & 0 deletions app/serializers/v2/report_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module V2
# JSON serialization for Reports
class ReportSerializer < V2::ApplicationSerializer
attributes :title, :description, :business_objective, :compliance_threshold

derived_attribute :os_major_version, security_guide: [:os_major_version]
derived_attribute :profile_title, profile: [:title]
derived_attribute :ref_id, profile: [:ref_id]
derived_attribute :all_systems_exposed, :total_system_count

aggregated_attribute :assigned_system_count, :assigned_systems, V2::Report::SYSTEM_COUNT
aggregated_attribute :compliant_system_count, :systems, V2::Report::COMPLIANT_SYSTEM_COUNT
aggregated_attribute :unsupported_system_count, :systems, V2::Report::UNSUPPORTED_SYSTEM_COUNT
aggregated_attribute :result_system_count, :systems, V2::Report::SYSTEM_COUNT
end
end
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def draw_routes(prefix)
resources :systems, only: [:index, :show] do
resources :policies, only: [:index], parents: [:systems]
end
resources :reports, only: [:index, :show]
end
end

Expand Down
2 changes: 2 additions & 0 deletions spec/api/v2/schemas.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Schemas
include Metadata
include Policy
include Profile
include Report
include Rule
include RuleGroup
include RuleTree
Expand All @@ -29,6 +30,7 @@ module Schemas
policy: POLICY,
policy_update: POLICY_UPDATE,
profile: PROFILE,
report: REPORT,
rule: RULE,
rule_group: RULE_GROUP,
rule_tree: RULE_TREE,
Expand Down
92 changes: 92 additions & 0 deletions spec/api/v2/schemas/report.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

require './spec/api/v2/schemas/util'

module Api
module V2
module Schemas
# :nodoc:
module Report
extend Api::V2::Schemas::Util

REPORT = {
type: :object,
properties: {
id: ref_schema('id'),
type: {
type: :string,
enum: ['report'],
readOnly: true
},
title: {
type: :string,
examples: ['CIS Red Hat Enterprise Linux 7 Benchmark'],
description: 'Short title of the Report',
readOnly: true
},
business_objective: {
type: :string,
examples: ['Guide to the Secure Configuration of Red Hat Enterprise Linux 7'],
description: 'The Business Objective associated to the Policy',
readOnly: true
},
compliance_threshold: {
type: :number,
examples: [90],
maximum: 100,
minimum: 0,
description: 'The percentage above which the Policy meets compliance requirements',
readOnly: true
},
os_major_version: {
type: :number,
minimum: 6,
examples: [7],
description: 'Major version of the Operating System that the Report covers',
readOnly: true
},
ref_id: {
type: :string,
examples: ['xccdf_org.ssgproject.content_profile_pci-dss'],
description: 'Identificator of the Profile',
readOnly: true
},
profile_title: {
type: :string,
examples: ['CIS Red Hat Enterprise Linux 7 Benchmark'],
description: 'Title of the associated Profile',
readOnly: true
},
assigned_system_count: {
type: :number,
minium: 1,
examples: [42],
description: 'The number of Systems assigned to this Report',
readOnly: true
},
compliant_system_count: {
type: :number,
minium: 0,
examples: [21],
description: 'The number of compliant Systems assigned to this Report',
readOnly: true
},
all_systems_exposed: {
type: :boolean,
description: 'Informs if the user has access to all the account\'s systems',
examples: [false],
readOnly: true
},
unsupported_system_count: {
type: :number,
minium: 0,
examples: [3],
description: 'The number of unsupported Systems assigned to this Report',
readOnly: true
}
}
}.freeze
end
end
end
end
Loading

0 comments on commit e6e7316

Please sign in to comment.