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

Add papertrail / Audit log #435

Merged
merged 5 commits into from
Apr 19, 2024
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
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
Loading