Skip to content

Commit

Permalink
feat(APIv2): RHINENG-2076 implemented the policies endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
skateman committed Nov 15, 2023
1 parent bcdbac7 commit aae9a11
Show file tree
Hide file tree
Showing 14 changed files with 490 additions and 1 deletion.
72 changes: 72 additions & 0 deletions app/controllers/v2/policies_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# frozen_string_literal: true

module V2
# Controller for Policies
class PoliciesController < ApplicationController
CREATE_ATTRIBUTES = %i[title description compliance_threshold business_objective profile_id].freeze
UPDATE_ATTRIBUTES = %i[description compliance_threshold business_objective].freeze

def index
render_json compliance_policies
end
permission_for_action :index, Rbac::POLICY_READ

def show
render_json compliance_policy
end
permission_for_action :show, Rbac::POLICY_READ

def create
new_policy = Policy.new(resource_params[:attributes].to_h.slice(*CREATE_ATTRIBUTES))

Check warning on line 20 in app/controllers/v2/policies_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/v2/policies_controller.rb#L20

Added line #L20 was not covered by tests

if new_policy.save
render_json new_policy, status: :created
audit_success("Created policy #{new_policy.id} with initial profile #{new_profile.id}")

Check warning on line 24 in app/controllers/v2/policies_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/v2/policies_controller.rb#L22-L24

Added lines #L22 - L24 were not covered by tests
else
render_model_errors new_policy

Check warning on line 26 in app/controllers/v2/policies_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/v2/policies_controller.rb#L26

Added line #L26 was not covered by tests
end
end

def update
if compliance_policy.update(resource_params[:attributes].to_h.slice(*UPDATE_ATTRIBUTES))
render_json compliance_policy
audit_success("Updated policy #{compliance_policy.id}")

Check warning on line 33 in app/controllers/v2/policies_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/v2/policies_controller.rb#L31-L33

Added lines #L31 - L33 were not covered by tests
else
render_model_errors compliance_policy

Check warning on line 35 in app/controllers/v2/policies_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/v2/policies_controller.rb#L35

Added line #L35 was not covered by tests
end
end

def destroy
compliance_policy.destroy
audit_success("Removed policy #{compliance_policy.id}")
render_json compliance_policy, status: :accepted

Check warning on line 42 in app/controllers/v2/policies_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/v2/policies_controller.rb#L40-L42

Added lines #L40 - L42 were not covered by tests
end
permission_for_action :create, Rbac::POLICY_DELETE

private

def compliance_policies
@compliance_policies ||= authorize(resolve_collection)
end

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

def resource_params
permitted_params.require(:data).permit(:attributes)

Check warning on line 57 in app/controllers/v2/policies_controller.rb

View check run for this annotation

Codecov / codecov/patch

app/controllers/v2/policies_controller.rb#L57

Added line #L57 was not covered by tests
end

def resource
V2::Policy
end

def serializer
V2::PolicySerializer
end

def extra_fields
[:account_id]
end
end
end
8 changes: 8 additions & 0 deletions app/models/v2/account.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

module V2
# Model for user accounts
class Account < ApplicationRecord
has_many :policies, class_name: 'V2::Policy', dependent: :destroy
end
end
28 changes: 28 additions & 0 deletions app/models/v2/policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,33 @@ class Policy < ApplicationRecord
belongs_to :profile, class_name: 'V2::Profile'
has_one :security_guide, through: :profile, class_name: 'V2::SecurityGuide'
has_many :tailorings, class_name: 'V2::Tailoring', dependent: :destroy
belongs_to :account

validates :account, presence: true
validates :profile, presence: true
validates :title, presence: true
validates :compliance_threshold, numericality: {
greater_than_or_equal_to: 0, less_than_or_equal_to: 100
}

sortable_by :title
# sortable_by :os_major_version # TODO: this needs to be made compatible with `expand_resource`
# sortable_by :host_count # TODO: this can be turned on after we have ways to assign hosts
sortable_by :business_objective
sortable_by :compliance_threshold

scoped_search on: :title, only_explicit: true, operators: %i[like unlike eq ne in notin]

def os_major_version
attributes['security_guide__ref_id'].try(:[], SecurityGuide::OS_MAJOR_RE)&.to_i || security_guide.os_major_version
end

def profile_title
attributes['profile__title'] || profile.title
end

def ref_id
attributes['profile__ref_id'] || profile.ref_id
end
end
end
4 changes: 3 additions & 1 deletion app/models/v2/security_guide.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class SecurityGuide < ApplicationRecord
# FIXME: clean up after the remodel
self.primary_key = :id

OS_MAJOR_RE = /(?<=RHEL-)\d+/

SORT_BY_VERSION = Arel::Nodes::NamedFunction.new(
'CAST',
[
Expand Down Expand Up @@ -48,7 +50,7 @@ class SecurityGuide < ApplicationRecord
sortable_by :os_major_version, SORT_BY_OS_MAJOR_VERSION

def os_major_version
ref_id[/(?<=RHEL-)\d+/].to_i
ref_id[OS_MAJOR_RE].to_i
end

def self.os_major_version_search(_filter, operator, value)
Expand Down
5 changes: 5 additions & 0 deletions app/models/v2/tailoring.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@ class Tailoring < ApplicationRecord
belongs_to :policy, class_name: 'V2::Policy'
belongs_to :profile, class_name: 'V2::Profile'
has_one :security_guide, through: :profile, class_name: 'V2::SecurityGuide'
has_one :account, through: :policy, class_name: 'V2::Account'

validates :policy, presence: true
validates :profile, presence: true
validates :os_minor_version, numericality: { greater_than_or_equal_to: 0 }
end
end
31 changes: 31 additions & 0 deletions app/policies/v2/policy_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

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

def show?
match_account?
end

def update?
match_account?
end

def destroy?
match_account?
end

# Only show hosts 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
12 changes: 12 additions & 0 deletions app/serializers/v2/policy_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

module V2
# JSON serialization for Policies
class PolicySerializer < V2::ApplicationSerializer
attributes :title, :description, :business_objective, :compliance_threshold, :host_count

derived_attribute :os_major_version, security_guide: [:ref_id]
derived_attribute :profile_title, profile: [:title]
derived_attribute :ref_id, profile: [:ref_id]
end
end
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def draw_routes(prefix)
resources :rules, only: [:index, :show], parents: [:security_guide, :profiles]
end
end

resources :policies, except: [:new, :edit]
end
end

Expand Down
54 changes: 54 additions & 0 deletions spec/controllers/v2/policies_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require 'rails_helper'

describe V2::PoliciesController do
let(:attributes) do
{
title: :title,
description: :description,
business_objective: :business_objective,
compliance_threshold: :compliance_threshold,
host_count: :host_count,
ref_id: :ref_id,
profile_title: :profile_title,
os_major_version: :os_major_version
}
end

let(:current_user) { FactoryBot.create(:v2_user) }
let(:rbac_allowed?) { true }

before do
request.headers['X-RH-IDENTITY'] = current_user.account.identity_header.raw
allow(StrongerParameters::InvalidValue).to receive(:new) { |value, _| value.to_sym }
allow(controller).to receive(:rbac_allowed?).and_return(rbac_allowed?)
end

describe 'GET index' do
let(:extra_params) { { account: current_user.account } }
let(:parents) { nil }
let(:item_count) { 2 }

let(:items) do
FactoryBot.create_list(
:v2_policy,
item_count,
account: current_user.account
).sort_by(&:id)
end

it_behaves_like 'collection'
include_examples 'with metadata'
it_behaves_like 'paginable'
it_behaves_like 'sortable'
it_behaves_like 'searchable'
end

describe 'GET show' do
let(:item) { FactoryBot.create(:v2_policy, account: current_user.account) }
let(:extra_params) { { id: item.id } }

it_behaves_like 'individual'
end
end
15 changes: 15 additions & 0 deletions spec/factories/policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

FactoryBot.define do
factory :v2_policy, class: 'V2::Policy' do
title { Faker::Lorem.sentence }
description { Faker::Lorem.paragraph }
profile { association :v2_profile, os_major_version: os_major_version }
compliance_threshold { SecureRandom.random_number(100) }
host_count { 0 }

transient do
os_major_version { 7 }
end
end
end
68 changes: 68 additions & 0 deletions spec/fixtures/files/searchable/policies_controller.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---

- :name: "equality search by title"
:entities:
:found:
- :factory: :v2_policy
:title: searched title
:account: ${account}
:not_found:
- :factory: :v2_policy
:title: not this title
:account: ${account}
:query: (title = "searched title")
- :name: "non-equality search by title"
:entities:
:found:
- :factory: :v2_policy
:title: not this title
:account: ${account}
:not_found:
- :factory: :v2_policy
:title: searched title
:account: ${account}
:query: (title != "searched title")
- :name: "in search by title"
:entities:
:found:
- :factory: :v2_policy
:title: searched title
:account: ${account}
:not_found:
- :factory: :v2_policy
:title: not this title
:account: ${account}
:query: (title ^ "searched title")
- :name: "not-in search by title"
:entities:
:found:
- :factory: :v2_policy
:title: not this title
:account: ${account}
:not_found:
- :factory: :v2_policy
:title: searched title
:account: ${account}
:query: (title !^ "searched title")
- :name: "like search by title"
:entities:
:found:
- :factory: :v2_policy
:title: searched title
:account: ${account}
:not_found:
- :factory: :v2_policy
:title: not this title
:account: ${account}
:query: (title ~ "searched title")
- :name: "unlike search by title"
:entities:
:found:
- :factory: :v2_policy
:title: not this title
:account: ${account}
:not_found:
- :factory: :v2_policy
:title: searched title
:account: ${account}
:query: (title !~ "searched title")
Loading

0 comments on commit aae9a11

Please sign in to comment.