Skip to content

Commit

Permalink
Auditing capabilies (#236)
Browse files Browse the repository at this point in the history
* remove staging db dump

* Adding auditing capabilities

* fixed mispelled word
  • Loading branch information
raul-gracia authored Sep 11, 2023
1 parent d0aae0d commit 5e880b1
Show file tree
Hide file tree
Showing 21 changed files with 376 additions and 224 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ gem "uk_postcode"
gem "phonelib"
###############

gem "audited", "~> 5.3", ">= 5.3.3"
gem "config", "~> 4.2"
gem "devise", "~> 4.9"
gem "httparty", "~> 0.21"
Expand Down
6 changes: 6 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ GEM
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
audited (5.3.3)
activerecord (>= 5.0, < 7.1)
request_store (~> 1.2)
bcrypt (3.1.19)
bindex (0.8.1)
binding_of_caller (1.0.0)
Expand Down Expand Up @@ -300,6 +303,8 @@ GEM
regexp_parser (2.8.1)
reline (0.3.4)
io-console (~> 0.5)
request_store (1.5.1)
rack (>= 1.4)
responders (3.1.0)
actionpack (>= 5.2)
railties (>= 5.2)
Expand Down Expand Up @@ -421,6 +426,7 @@ PLATFORMS

DEPENDENCIES
annotate
audited (~> 5.3, >= 5.3.3)
binding_of_caller
bootsnap
brakeman
Expand Down
4 changes: 4 additions & 0 deletions app/controllers/admin_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ def authenticate_user!
redirect_to(user_azure_activedirectory_v2_omniauth_authorize_path)
end
end

def create_audit(user = current_user, action:)
Audited::Audit.create(action:, user:)
end
end
1 change: 1 addition & 0 deletions app/controllers/system_admin/applicants_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def download_qa_csv
applications.each(&:mark_as_qa!)

report = Reports::QaReport.new(applications, status)
create_audit(action: "Downloaded QA CSV report (#{status.humanize})")
send_data(report.csv, filename: report.name)
end

Expand Down
8 changes: 8 additions & 0 deletions app/controllers/system_admin/audits_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module SystemAdmin
class AuditsController < AdminController
include Pagy::Backend
def index
@pagy, @audits = pagy(Audited::Audit.all.order(created_at: :desc), items: 10)
end
end
end
1 change: 1 addition & 0 deletions app/controllers/system_admin/reports_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ def index; end

def show
report = find_report(params[:id])
create_audit(action: "Downloaded #{report.class.to_s.capitalize} report")

send_data(report.csv, filename: report.name)
end
Expand Down
84 changes: 84 additions & 0 deletions app/helpers/audit_message_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
module AuditMessageHelper
def audit_message(audit)
method = routing_map[audit.auditable_type] || :generic_audit_message
message = send(method, audit)
message += "#{timestamp(audit)} (#{time_ago_in_words(audit.created_at)} ago)"
message.html_safe # rubocop:disable Rails/OutputSafety
end

private

def routing_map
{
"ApplicationProgress" => :application_progress_audit_message,
"User" => :user_audit_message,
"AppSettings" => :app_settings_audit_message,
}
end

def application_progress_audit_message(audit)
email_tag = govuk_tag(text: audit.user&.email || "unknown", colour: "grey")
urn_text = govuk_link_to(audit.auditable&.application&.urn, applicant_path(audit.auditable&.application&.applicant))
urn_tag = govuk_tag(text: urn_text, colour: "orange")
"#{email_tag} #{action_tag(audit)} application progress #{urn_tag}.</br>It changed: <ul>#{audited_changes_summary(audit)}</ul>"
end

def app_settings_audit_message(audit)
email_tag = govuk_tag(text: audit.user&.email || "unknown", colour: "grey")
"#{email_tag} #{action_tag(audit)} application settings.</br>It changed: <ul>#{audited_changes_summary(audit)}</ul>"
end

def user_audit_message(audit)
email_tag = govuk_tag(text: audit.user&.email || "unknown", colour: "grey")
case audit.action
when "create"
"#{email_tag} #{action_tag(audit)} a new User entity #{govuk_tag(text: audit.audited_changes['email'], colour: 'yellow')}"
when "update"
"#{email_tag} #{action_tag(audit)} User entity.</br>It changed: <ul>#{audited_changes_summary(audit)}</ul>"
when "destroy"
"#{email_tag} #{action_tag(audit)} User entity #{govuk_tag(text: audit.audited_changes['email'], colour: 'yellow')}<br />"
end
end

def generic_audit_message(audit)
email_tag = govuk_tag(text: audit.user&.email || "unknown", colour: "grey")
"#{email_tag} #{action_tag(audit)}<br />"
end

def audited_changes_summary(audit)
audit.audited_changes.except("status").map { |key, value|
"<li>#{key} (from: \"#{changed_value(value.first)}\", to: \"#{changed_value(value.last)}\")</li>"
}.join
end

def timestamp(audit)
govuk_tag(text: audit.created_at.strftime("%d/%m/%Y %H:%M:%S"), colour: "grey")
end

def action_tag(audit)
govuk_tag(text: past_tense_action(audit.action), colour: action_colour(audit.action))
end

def past_tense_action(action)
{
"create" => "created",
"update" => "updated",
"destroy" => "destroyed",
}[action] || action
end

def action_colour(action)
{
"create" => "green",
"update" => "blue",
"destroy" => "red",
}[action]
end

def changed_value(value)
return "&lt;empty&gt;" if value.nil?
return "&lt;empty&gt;" if value.blank?

value
end
end
1 change: 1 addition & 0 deletions app/models/app_settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# updated_at :datetime not null
#
class AppSettings < ApplicationRecord
audited
def self.current
AppSettings.first_or_create!
end
Expand Down
1 change: 1 addition & 0 deletions app/models/application_progress.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# application_id :bigint
#
class ApplicationProgress < ApplicationRecord
audited
belongs_to :application

validates :rejection_reason, presence: true, if: :rejection_completed_at?
Expand Down
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
# updated_at :datetime not null
#
class User < ApplicationRecord
audited
devise :omniauthable, omniauth_providers: %i[azure_activedirectory_v2]
end
1 change: 1 addition & 0 deletions app/views/layouts/admin.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<%= header.with_navigation_item(text: "Dashboard", href: dashboard_path, active: request.path.include?('/system-admin/dashboard') ) %>
<%= header.with_navigation_item(text: "Users", href: users_path, active: request.path.include?('/system-admin/users') ) %>
<%= header.with_navigation_item(text: "Settings", href: edit_settings_path, active: request.path.include?('/system-admin/settings')) %>
<%= header.with_navigation_item(text: "Audits", href: audits_path, active: request.path.include?('/system-admin/audits')) %>
<%= header.with_navigation_item(text: "Logout", href: destroy_user_session_path, active: false) %>
<% end %>

Expand Down
12 changes: 12 additions & 0 deletions app/views/system_admin/audits/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<%= govuk_table(classes: "audits-table") do |table|
table.with_caption(size: 'l', text: 'Audits')
table.with_body do |body|
@audits.each do |audit|
body.with_row do |row|
row.with_cell(text: audit_message(audit), colspan: 6)
end
end
end
end %>

<%= govuk_pagination(pagy: @pagy) %>
1 change: 1 addition & 0 deletions config/initializers/audited.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Audited.store_synthesized_enums = true
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@
get "/dashboard", to: "dashboard#show"
resources "reports", only: %i[show index]
get "/duplicates", to: "applicants#duplicates"
get "/audits", to: "audits#index"
end
end
32 changes: 32 additions & 0 deletions db/migrate/20230906030846_install_audited.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

class InstallAudited < ActiveRecord::Migration[7.0]
def self.up
create_table :audits, force: true do |t| # rubocop:disable Rails/CreateTableWithTimestamps
t.column :auditable_id, :integer
t.column :auditable_type, :string
t.column :associated_id, :integer
t.column :associated_type, :string
t.column :user_id, :integer
t.column :user_type, :string
t.column :username, :string
t.column :action, :string
t.column :audited_changes, :jsonb
t.column :version, :integer, default: 0
t.column :comment, :string
t.column :remote_address, :string
t.column :request_uuid, :string
t.column :created_at, :datetime
end

add_index :audits, %i[auditable_type auditable_id version], name: "auditable_index"
add_index :audits, %i[associated_type associated_id], name: "associated_index"
add_index :audits, %i[user_id user_type], name: "user_index"
add_index :audits, :request_uuid
add_index :audits, :created_at
end

def self.down
drop_table :audits
end
end
40 changes: 31 additions & 9 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5e880b1

Please sign in to comment.