Skip to content

Commit

Permalink
Add papertrail / Audit log (#435)
Browse files Browse the repository at this point in the history
* feat: install papertrail

* feat: add papertrail to models

* fix: skip attributes set only by system

* feat: Add audit cleaner worker to periodically clean DB

* fix: structure.sql for tests
  • Loading branch information
bbucsy authored Apr 19, 2024
1 parent 33e70ae commit 05eba9d
Show file tree
Hide file tree
Showing 27 changed files with 180 additions and 5 deletions.
1 change: 1 addition & 0 deletions .ruby-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
2.5.7
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ gem 'activity_notification', '~> 2.2', '>= 2.2.1'
# use Pundit for authorization
gem 'pundit', '~> 2.1'
gem "bootsnap", ">= 1.1.0", require: false

gem 'paper_trail'

gem 'listen'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ GEM
omniauth-oauth2 (1.6.0)
oauth2 (~> 1.1)
omniauth (~> 1.9)
paper_trail (12.1.0)
activerecord (>= 5.2)
request_store (~> 1.1)
pdfkit (0.8.4.1)
pg (0.21.0)
public_suffix (4.0.6)
Expand Down Expand Up @@ -340,6 +343,7 @@ DEPENDENCIES
minitest (~> 5.10.0)
omniauth
omniauth-oauth2
paper_trail
pdfkit
pg
puma
Expand Down
6 changes: 6 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class ApplicationController < ActionController::Base
include ApplicationHelper
include Pundit

before_action :set_paper_trail_whodunnit
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
Expand Down Expand Up @@ -112,4 +114,8 @@ def impersonate_user
def forbidden_page
render 'application/403', status: :forbidden
end

def user_for_paper_trail
session[:user_id].present? ? current_user.id : 'system' # or whatever
end
end
2 changes: 2 additions & 0 deletions app/models/entry_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#

class EntryRequest < ApplicationRecord
has_paper_trail

belongs_to :evaluation
belongs_to :user

Expand Down
1 change: 1 addition & 0 deletions app/models/evaluation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

class Evaluation < ApplicationRecord
alias_attribute :date, :semester
has_paper_trail skip: [:last_modification]

belongs_to :group
has_many :point_requests
Expand Down
2 changes: 2 additions & 0 deletions app/models/evaluation_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#

class EvaluationMessage < ApplicationRecord
has_paper_trail

belongs_to :group
belongs_to :sender_user, class_name: 'User', foreign_key: :sender_user_id
end
1 change: 1 addition & 0 deletions app/models/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

class Group < ApplicationRecord
self.inheritance_column = nil
has_paper_trail

alias_attribute :parent, :group

Expand Down
1 change: 1 addition & 0 deletions app/models/membership.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

class Membership < ApplicationRecord
include Notifications::MembershipNotifier
has_paper_trail

belongs_to :group
belongs_to :user
Expand Down
1 change: 1 addition & 0 deletions app/models/point_detail.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#

class PointDetail < ApplicationRecord
has_paper_trail
belongs_to :principle
belongs_to :point_request
has_many :point_detail_comments, dependent: :destroy
Expand Down
1 change: 1 addition & 0 deletions app/models/point_detail_comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#

class PointDetailComment < ApplicationRecord
has_paper_trail
belongs_to :user
belongs_to :point_detail

Expand Down
2 changes: 2 additions & 0 deletions app/models/point_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#

class PointRequest < ApplicationRecord
has_paper_trail

belongs_to :evaluation
belongs_to :user
has_many :point_details
Expand Down
1 change: 1 addition & 0 deletions app/models/post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#

class Post < ApplicationRecord
has_paper_trail
belongs_to :post_type
belongs_to :membership

Expand Down
2 changes: 2 additions & 0 deletions app/models/post_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#

class PostType < ApplicationRecord
has_paper_trail

belongs_to :group, optional: true

has_many :posts
Expand Down
1 change: 1 addition & 0 deletions app/models/principle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

class Principle < ApplicationRecord
self.inheritance_column = nil # So it won't try to interpret the type column as STI
has_paper_trail

belongs_to :evaluation
belongs_to :sub_group, optional: true
Expand Down
1 change: 1 addition & 0 deletions app/models/sub_group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#

class SubGroup < ApplicationRecord
has_paper_trail
belongs_to :group
has_many :sub_group_memberships
has_many :memberships, through: :sub_group_memberships
Expand Down
1 change: 1 addition & 0 deletions app/models/sub_group_membership.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#

class SubGroupMembership < ApplicationRecord
has_paper_trail
belongs_to :sub_group
belongs_to :membership

Expand Down
2 changes: 2 additions & 0 deletions app/models/svie_post_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#

class SviePostRequest < ApplicationRecord
has_paper_trail

belongs_to :user

def inside_member?
Expand Down
2 changes: 2 additions & 0 deletions app/models/system_attribute.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#

class SystemAttribute < ApplicationRecord
has_paper_trail

def self.semester
Semester.new(find_by(name: 'szemeszter').value)
end
Expand Down
1 change: 1 addition & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class User < ApplicationRecord
scope :primary_svie_members, -> { where.not(svie_primary_membership: nil) }

acts_as_target
has_paper_trail skip: [:last_login, :metascore]

has_many :memberships
has_many :groups, through: :memberships
Expand Down
8 changes: 8 additions & 0 deletions app/workers/audit_cleaner_worker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class AuditCleanerWorker
include Sidekiq::Worker

def perform(*args)
# Audit logs can blow up database so we only keep the last three months worth of logs
PaperTrail::Version.where('created_at < ?', 3.months.ago).delete_all
end
end
14 changes: 10 additions & 4 deletions config/initializers/sidekiq.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
schedule_file = "config/schedule.yml"


Sidekiq.configure_server do |config|
config.redis = { url: ENV.fetch('REDIS_URL') }
end

if File.exists?(schedule_file) && Sidekiq.server?
Sidekiq::Cron::Job.load_from_hash YAML.load_file(schedule_file)
config.on(:startup) do
schedule_file = "config/schedule.yml"

if File.exist?(schedule_file)
schedule = YAML.load_file(schedule_file)

Sidekiq::Cron::Job.load_from_hash!(schedule)
end
end
end
6 changes: 6 additions & 0 deletions config/schedule.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
metascore:
cron: "00 04 * * *"
class: "MetascoreEveryone"

audit_clean:
cron: "0 0 * * *"
class: "AuditCleanerWorker"
description: "Cleans up audit log table so its not bloating the database"
enabled: true
31 changes: 31 additions & 0 deletions db/migrate/20240419155504_create_versions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This migration creates the `versions` table, the only schema PT requires.
# All other migrations PT provides are optional.
class CreateVersions < ActiveRecord::Migration[6.0]

def change
create_table :versions do |t|
t.string :item_type, { null: false }
t.bigint :item_id, null: false
t.string :event, null: false
t.string :whodunnit
t.json :object
t.json :object_changes

# Known issue in MySQL: fractional second precision
# -------------------------------------------------
#
# MySQL timestamp columns do not support fractional seconds unless
# defined with "fractional seconds precision". MySQL users should manually
# add fractional seconds precision to this migration, specifically, to
# the `created_at` column.
# (https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html)
#
# MySQL users should also upgrade to at least rails 4.2, which is the first
# version of ActiveRecord with support for fractional seconds in MySQL.
# (https://github.com/rails/rails/pull/14359)
#
t.datetime :created_at
end
add_index :versions, %i(item_type item_id)
end
end
61 changes: 60 additions & 1 deletion db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ SET default_with_oids = false;
-- Name: ar_internal_metadata; Type: TABLE; Schema: public; Owner: -
--


CREATE TABLE public.ar_internal_metadata (
key character varying NOT NULL,
value character varying,
Expand Down Expand Up @@ -790,6 +791,41 @@ CREATE TABLE public.users (
);


--
-- Name: versions; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.versions (
id bigint NOT NULL,
item_type character varying NOT NULL,
item_id bigint NOT NULL,
event character varying NOT NULL,
whodunnit character varying,
object json,
object_changes json,
created_at timestamp without time zone
);


--
-- Name: versions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE public.versions_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;


--
-- Name: versions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE public.versions_id_seq OWNED BY public.versions.id;


--
-- Name: view_settings; Type: TABLE; Schema: public; Owner: -
--
Expand Down Expand Up @@ -878,6 +914,13 @@ ALTER TABLE ONLY public.subscriptions ALTER COLUMN id SET DEFAULT nextval('publi
ALTER TABLE ONLY public.svie_post_requests ALTER COLUMN id SET DEFAULT nextval('public.svie_post_requests_id_seq'::regclass);


--
-- Name: versions id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.versions ALTER COLUMN id SET DEFAULT nextval('public.versions_id_seq'::regclass);


--
-- Name: view_settings id; Type: DEFAULT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -1133,6 +1176,14 @@ ALTER TABLE ONLY public.users
ADD CONSTRAINT users_usr_screen_name_key UNIQUE (screen_name);


--
-- Name: versions versions_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.versions
ADD CONSTRAINT versions_pkey PRIMARY KEY (id);


--
-- Name: view_settings view_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -1330,6 +1381,13 @@ CREATE INDEX index_subscriptions_on_target_type_and_target_id ON public.subscrip
CREATE UNIQUE INDEX index_subscriptions_on_target_type_and_target_id_and_key ON public.subscriptions USING btree (target_type, target_id, key);


--
-- Name: index_versions_on_item_type_and_item_id; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX index_versions_on_item_type_and_item_id ON public.versions USING btree (item_type, item_id);


--
-- Name: membership_usr_fk_idx; Type: INDEX; Schema: public; Owner: -
--
Expand Down Expand Up @@ -1695,6 +1753,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20221206072407'),
('20221206081834'),
('20221209072919'),
('20221209100356');
('20221209100356'),
('20240419155504');


2 changes: 2 additions & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
require 'paper_trail/frameworks/rspec'

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
Expand Down Expand Up @@ -70,6 +71,7 @@
Group.delete_all
User.delete_all
ActivityNotification::Notification.delete_all
PaperTrail::Version.delete_all
end

# RSpec Rails can automatically mix in different behaviours to your tests
Expand Down
27 changes: 27 additions & 0 deletions spec/workers/audit_cleaner_worker_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require 'rails_helper'
RSpec.describe AuditCleanerWorker, type: :worker do
it 'deletes old versions' do
with_versioning do
Timecop.travel '2024-04-19'
user = create(:user)
user.update(nickname: 'asd')
expect(PaperTrail::Version.count).to be > 2

Timecop.travel '2024-08-19'
AuditCleanerWorker.new.perform
expect(PaperTrail::Version.count).to be 0
end
end

it 'keeps versions younger than 3 months' do
with_versioning do
Timecop.travel '2024-04-19'
user = create(:user)
user.update(nickname: 'asd')
expect(PaperTrail::Version.count).to be > 2

Timecop.travel '2024-05-19'
expect { AuditCleanerWorker.new.perform }.not_to change { PaperTrail::Version.count }
end
end
end

0 comments on commit 05eba9d

Please sign in to comment.