Project

General

Profile

Download (8.17 KB) Statistics
| Branch: | Tag: | Revision:
# The audit class is part of audited plugin
module AuditExtensions
extend ActiveSupport::Concern

REDACTED = N_('[redacted]')

included do
before_save :fix_auditable_type, :ensure_username, :ensure_auditable_and_associated_name, :set_taxonomies
before_save :filter_encrypted, :if => Proc.new { |audit| audit.audited_changes.present? }
before_save :filter_passwords, :if => Proc.new { |audit| audit.audited_changes.try(:has_key?, 'password') }
after_create :log_audit

scope :untaxed, -> { by_auditable_types(untaxable) }
scope :fully_taxable_auditables, -> { by_auditable_types(fully_taxable) }
scope :fully_taxable_auditables_in_taxonomy_scope, -> { with_taxonomy_scope { fully_taxable_auditables } }
scope :by_auditable_types, ->(auditable_types) { where(:auditable_type => auditable_types.map(&:to_s)).readonly(false) }
scope :taxed_and_untaxed, lambda {
untaxed.or(fully_taxable_auditables_in_taxonomy_scope)
.or(taxed_only_by_organization)
.or(taxed_only_by_location)
}

include Authorizable
include Taxonomix
include Foreman::TelemetryHelper

# audits can be created regardless of permissions
def check_permissions_after_save
true
end

def self.humanize_class_name
_("Audit")
end

serialize :audited_changes

# don't check user's permissions when setting the audit's taxonomies
def ensure_taxonomies_not_escalated
true
end

# Audits should never execute what Taxonomix#set_current_taxonomy does
def set_current_taxonomy
end

class << self
def allows_organization_filtering?
false
end

def allows_location_filtering?
false
end

def location_taxable
known_auditable_types.select do |model|
has_any_association_to?(:location, model) &&
!has_any_association_to?(:organization, model)
end
end

def organization_taxable
known_auditable_types.select do |model|
has_any_association_to?(:organization, model) &&
!has_any_association_to?(:location, model)
end
end

def fully_taxable
known_auditable_types.select do |model|
[:location, :organization].map do |taxable|
has_any_association_to?(taxable, model)
end.all?
end
end

def untaxable
untaxed = known_auditable_types.select do |model|
!has_taxonomix?(model)
!has_any_association_to?(:organization, model) &&
!has_any_association_to?(:location, model)
end
untaxed -= Nic::Base.descendants unless User.current.admin?
untaxed
end

def known_auditable_types
unscoped.distinct.pluck(:auditable_type).map do |auditable_type|
auditable_type.constantize
rescue NameError
end.compact
end

def taxed_only_by_location
locations = Location.current || Location.my_locations.reorder(nil)
by_auditable_types(location_taxable).
where(id: TaxableTaxonomy.where(taxonomy: locations, taxable_type: 'Audited::Audit').select(:taxable_id))
end

def taxed_only_by_organization
organizations = Organization.current || Organization.my_organizations.reorder(nil)
by_auditable_types(organization_taxable).
where(id: TaxableTaxonomy.where(taxonomy: organizations, taxable_type: 'Audited::Audit').select(:taxable_id))
end

private

def has_association?(association, model = self)
associations = model.reflect_on_all_associations.map(&:name)
associations.include?(association)
end

def has_any_association_to?(tax_type, model)
[has_association?(tax_type.to_sym, model), has_association?(tax_type.to_s.pluralize.to_sym, model)].any?
end

def has_taxonomix?(model)
model.include?(Taxonomix)
end
end
end

module ClassMethods
def main_objects
main_classes = audited_classes.reject { |cl| cl.audited_options.key?(:associated_with) }
main_classes.concat(non_abstract_parents(main_classes))
end

def main_object_names
main_objects.map(&:name)
end

def non_abstract_parents(classes_list)
parents_list = classes_list.map(&:superclass).uniq
parents_list.select { |cl| cl != ActiveRecord::Base && !cl.abstract_class? && cl.table_exists? }.compact
end
end

private

def log_audit
telemetry_increment_counter(:audit_records_created, 1, type: self.auditable_type)
audit_logger = Foreman::Logging.logger('audit')
return unless (self.audited_changes && audit_logger.info?)
self.audited_changes.each_pair do |attribute, change|
audited_fields = {
audit_action: self.action,
audit_type: self.auditable_type,
audit_id: self.auditable_id,
audit_attribute: attribute,
}
if self.action == 'update'
audited_fields[:audit_field_old] = change[0]
audited_fields[:audit_field_new] = change[1]
log_line = change.join(', ')
else
audited_fields[:audit_field] = change
log_line = change
end
Foreman::Logging.with_fields(audited_fields) do
audit_logger.info "#{self.auditable_type} (#{self.auditable_id}) #{self.action} event on #{attribute} #{log_line}"
end
end
telemetry_increment_counter(:audit_records_logged, self.audited_changes.count, type: self.auditable_type)
end

def filter_encrypted
self.audited_changes.each do |name, change|
next if change.nil? || change.to_s.empty?
if change.is_a? Array
change.map! { |c| c.to_s.start_with?(EncryptValue::ENCRYPTION_PREFIX) ? REDACTED : c }
else
audited_changes[name] = REDACTED if change.to_s.start_with?(EncryptValue::ENCRYPTION_PREFIX)
end
end
end

def filter_passwords
if action == 'update'
audited_changes['password'] = [REDACTED, REDACTED]
else
audited_changes['password'] = REDACTED
end
end

def ensure_username
self.user_as_model = User.current
self.username = User.current.try(:to_label)
end

def fix_auditable_type
# STI Host class should use the stub module instead of Host::Base
self.auditable_type = "Host::Base" if auditable_type =~ /Host::/
self.associated_type = "Host::Base" if associated_type =~ /Host::/
self.auditable_type = auditable.type if ["Taxonomy", "LookupKey"].include?(auditable_type) && auditable
self.associated_type = associated.type if ["Taxonomy", "LookupKey"].include?(associated_type) && associated
self.auditable_type = auditable.type if auditable_type =~ /Nic::/
end

def ensure_auditable_and_associated_name
# If the label changed we want to record the old one, not the new one.
# We need to load old version from db since the auditable in memory is the
# updated version that hasn't been saved yet.
previous_state = auditable.class.find_by(id: auditable_id) if auditable
previous_state ||= auditable
self.auditable_name ||= previous_state.try(:to_label)
self.associated_name ||= self.associated.try(:to_label)
end

def set_taxonomies
set_taxonomy_for(:location)
set_taxonomy_for(:organization)
end

def set_taxonomy_for(taxonomy)
taxonomy_attribute = "#{taxonomy}_id".to_sym
taxonomy_attribute_plural = taxonomy_attribute.to_s.pluralize.to_sym

if auditable.respond_to?(taxonomy_attribute)
self.send("#{taxonomy_attribute_plural}=", [
auditable.send(taxonomy_attribute), audited_changes[taxonomy_attribute.to_s]
].flatten.compact.uniq)
elsif auditable.respond_to?(taxonomy_attribute_plural)
self.send("#{taxonomy_attribute_plural}=", auditable.send(taxonomy_attribute_plural))
elsif associated
set_taxonomies_using_associated(taxonomy.to_s)
end

if auditable.is_a? taxonomy.capitalize.to_s.constantize
self.send("#{taxonomy_attribute_plural}=", [auditable_id])
end
end

def set_taxonomies_using_associated(key_name)
ids_arr = []
if associated.respond_to?(:"#{key_name}_id")
ids_arr = [associated.send("#{key_name}_id")].compact.uniq
elsif associated.respond_to?(:"#{key_name}_ids")
ids_arr = associated.send("#{key_name}_ids")
end
self.send("#{key_name}_ids=", ids_arr)
end
end
(2-2/51)