Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(APIv2): RHINENG-2149 implement reports endpoint #2046

Merged
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
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
67 changes: 65 additions & 2 deletions app/models/v2/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,72 @@ 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
skateman marked this conversation as resolved.
Show resolved Hide resolved
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

romanblanco marked this conversation as resolved.
Show resolved Hide resolved
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
skateman marked this conversation as resolved.
Show resolved Hide resolved

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'
skateman marked this conversation as resolved.
Show resolved Hide resolved

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
romanblanco marked this conversation as resolved.
Show resolved Hide resolved
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
romanblanco marked this conversation as resolved.
Show resolved Hide resolved
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],
romanblanco marked this conversation as resolved.
Show resolved Hide resolved
description: 'The number of unsupported Systems assigned to this Report',
readOnly: true
}
}
}.freeze
end
end
end
end
Loading
Loading