feat: Audit log APIs (#6434)

- Adds the appropriate APIs for Audit Logs.

ref: #6015
This commit is contained in:
Vishnu Narayanan
2023-03-01 20:02:58 +05:30
committed by GitHub
parent daf17046e9
commit d870b0815a
21 changed files with 261 additions and 1 deletions

View File

@@ -205,6 +205,8 @@ end
# worked with microsoft refresh token
gem 'omniauth-oauth2'
gem 'audited', '~> 5.2'
# need for google auth
gem 'omniauth'
gem 'omniauth-google-oauth2'

View File

@@ -90,6 +90,8 @@ GEM
rake (>= 10.4, < 14.0)
ast (2.4.2)
attr_extras (6.2.5)
audited (5.2.0)
activerecord (>= 5.0, < 7.1)
aws-eventstream (1.2.0)
aws-partitions (1.605.0)
aws-sdk-core (3.131.2)
@@ -763,6 +765,7 @@ DEPENDENCIES
administrate
annotate
attr_extras
audited (~> 5.2)
aws-sdk-s3
azure-storage-blob
barnes

View File

@@ -155,3 +155,4 @@ class Account < ApplicationRecord
end
Account.prepend_mod_with('Account')
Account.include_mod_with('Audit::Account')

View File

@@ -76,3 +76,5 @@ class AutomationRule < ApplicationRecord
errors.add(:conditions, 'Automation conditions should have query operator.') if operators.length > 1
end
end
AutomationRule.include_mod_with('Audit::Inbox')

View File

@@ -158,3 +158,4 @@ class Inbox < ApplicationRecord
end
Inbox.prepend_mod_with('Inbox')
Inbox.include_mod_with('Audit::Inbox')

View File

@@ -37,3 +37,5 @@ class Webhook < ApplicationRecord
errors.add(:subscriptions, I18n.t('errors.webhook.invalid')) if invalid_subscriptions
end
end
Webhook.include_mod_with('Audit::Inbox')

View File

@@ -0,0 +1,5 @@
# configuration related audited gem : https://github.com/collectiveidea/audited
Audited.config do |config|
config.audit_class = 'Enterprise::AuditLog'
end

View File

@@ -45,6 +45,7 @@ Rails.application.routes.draw do
resources :agents, only: [:index, :create, :update, :destroy]
resources :agent_bots, only: [:index, :create, :show, :update, :destroy]
resources :assignable_agents, only: [:index]
resource :audit_logs, only: [:show]
resources :callbacks, only: [] do
collection do
post :register_facebook_page

View File

@@ -0,0 +1,34 @@
# frozen_string_literal: true
class InstallAudited < ActiveRecord::Migration[6.1]
# rubocop:disable RMetrics/MethodLength
def self.up
create_table :audits, :force => true do |t|
t.bigint :auditable_id
t.string :auditable_type
t.bigint :associated_id
t.string :associated_type
t.bigint :user_id
t.string :user_type
t.string :username
t.string :action
t.jsonb :audited_changes
t.integer :version, :integer, :default => 0
t.string :comment
t.string :remote_address
t.string :request_uuid
t.datetime :created_at
end
# rubocop:enable RMetrics/MethodLength
add_index :audits, [:auditable_type, :auditable_id, :version], :name => 'auditable_index'
add_index :audits, [:associated_type, :associated_id], :name => 'associated_index'
add_index :audits, [: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

View File

@@ -153,6 +153,28 @@ ActiveRecord::Schema.define(version: 2023_02_24_124632) do
t.index ["message_id"], name: "index_attachments_on_message_id"
end
create_table 'audits', force: :cascade do |t|
t.bigint 'auditable_id'
t.string 'auditable_type'
t.bigint 'associated_id'
t.string 'associated_type'
t.bigint 'user_id'
t.string 'user_type'
t.string 'username'
t.string 'action'
t.jsonb 'audited_changes'
t.integer 'version', default: 0
t.string 'comment'
t.string 'remote_address'
t.string 'request_uuid'
t.datetime 'created_at'
t.index %w[associated_type associated_id], name: 'associated_index'
t.index %w[auditable_type auditable_id version], name: 'auditable_index'
t.index ['created_at'], name: 'index_audits_on_created_at'
t.index ['request_uuid'], name: 'index_audits_on_request_uuid'
t.index %w[user_id user_type], name: 'user_index'
end
create_table "automation_rules", force: :cascade do |t|
t.bigint "account_id", null: false
t.string "name", null: false

View File

@@ -0,0 +1,15 @@
# module Enterprise::Api::V1::Accounts::AuditLogsController < Api::V1::Accounts::BaseController
class Api::V1::Accounts::AuditLogsController < Api::V1::Accounts::BaseController
before_action :check_admin_authorization?
before_action :fetch_audit
def show
render json: @audit_logs
end
private
def fetch_audit
@audit_logs = Current.account.associated_audits
end
end

View File

@@ -0,0 +1,7 @@
module Enterprise::Audit::Account
extend ActiveSupport::Concern
included do
has_associated_audits
end
end

View File

@@ -0,0 +1,7 @@
module Enterprise::Audit::AutomationRule
extend ActiveSupport::Concern
included do
audited associated_with: :account
end
end

View File

@@ -0,0 +1,7 @@
module Enterprise::Audit::Inbox
extend ActiveSupport::Concern
included do
audited associated_with: :account
end
end

View File

@@ -0,0 +1,7 @@
module Enterprise::Audit::Webhook
extend ActiveSupport::Concern
included do
audited associated_with: :account
end
end

View File

@@ -0,0 +1,11 @@
class Enterprise::AuditLog < Audited::Audit
after_save :log_additional_information
private
def log_additional_information
# rubocop:disable Rails/SkipsModelValidations
update_columns(username: user&.email)
# rubocop:enable Rails/SkipsModelValidations
end
end

View File

@@ -0,0 +1,44 @@
require 'rails_helper'
RSpec.describe 'Enterprise Audit API', type: :request do
let!(:account) { create(:account) }
let!(:inbox) { create(:inbox, account: account) }
let!(:admin) { create(:user, account: account, role: :administrator) }
describe 'GET /api/v1/accounts/{account.id}/audit_logs' do
context 'when it is an un-authenticated user' do
it 'does not fetch audit logs associated with the account' do
get "/api/v1/accounts/#{account.id}/audit_logs",
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
context 'when it is an authenticated normal user' do
let(:user) { create(:user, account: account) }
it 'fetches audit logs associated with the account' do
get "/api/v1/accounts/#{account.id}/audit_logs",
headers: user.create_new_auth_token,
as: :json
expect(response).to have_http_status(:unauthorized)
end
end
# check for response in parse
context 'when it is an authenticated admin user' do
it 'fetches audit logs associated with the account' do
get "/api/v1/accounts/#{account.id}/audit_logs",
headers: admin.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
json_response = JSON.parse(response.body)
expect(json_response[0]['auditable_type']).to eql('Inbox')
expect(json_response[0]['action']).to eql('create')
expect(json_response[0]['audited_changes']['name']).to eql(inbox.name)
expect(json_response[0]['associated_id']).to eql(account.id)
end
end
end
end

View File

@@ -10,6 +10,13 @@ RSpec.describe Account do
let!(:account) { create(:account) }
describe 'audit logs' do
it 'returns audit logs' do
# checking whether associated_audits method is present
expect(account.associated_audits.present?).to be false
end
end
it 'returns max limits from global config when enterprise version' do
expect(account.usage_limits).to eq(
{

View File

@@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AutomationRule do
let!(:automation_rule) { create(:automation_rule, name: 'automation rule 1') }
describe 'audit log' do
context 'when automation rule is created' do
it 'has associated audit log created' do
expect(Audited::Audit.where(auditable_type: 'AutomationRule', action: 'create').count).to eq 1
end
end
context 'when automation rule is updated' do
it 'has associated audit log created' do
automation_rule.update(name: 'automation rule 2')
expect(Audited::Audit.where(auditable_type: 'AutomationRule', action: 'update').count).to eq 1
end
end
context 'when automation rule is deleted' do
it 'has associated audit log created' do
automation_rule.destroy!
expect(Audited::Audit.where(auditable_type: 'AutomationRule', action: 'destroy').count).to eq 1
end
end
end
end

View File

@@ -3,8 +3,9 @@
require 'rails_helper'
RSpec.describe Inbox do
let!(:inbox) { create(:inbox) }
describe 'member_ids_with_assignment_capacity' do
let!(:inbox) { create(:inbox) }
let!(:inbox_member_1) { create(:inbox_member, inbox: inbox) }
let!(:inbox_member_2) { create(:inbox_member, inbox: inbox) }
let!(:inbox_member_3) { create(:inbox_member, inbox: inbox) }
@@ -35,4 +36,26 @@ RSpec.describe Inbox do
expect(inbox.member_ids_with_assignment_capacity).to eq(inbox.members.ids)
end
end
describe 'audit log' do
context 'when inbox is created' do
it 'has associated audit log created' do
expect(Audited::Audit.where(auditable_type: 'Inbox', action: 'create').count).to eq 1
end
end
context 'when inbox is updated' do
it 'has associated audit log created' do
inbox.update(auto_assignment_config: { max_assignment_limit: 2 })
expect(Audited::Audit.where(auditable_type: 'Inbox', action: 'update').count).to eq 1
end
end
context 'when inbox is deleted' do
it 'has associated audit log created' do
inbox.destroy!
expect(Audited::Audit.where(auditable_type: 'Inbox', action: 'destroy').count).to eq 1
end
end
end
end

View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Webhook do
let(:account) { create(:account) }
let!(:webhook) { create(:webhook, account: account) }
describe 'audit log' do
context 'when webhook is created' do
it 'has associated audit log created' do
expect(Audited::Audit.where(auditable_type: 'Webhook', action: 'create').count).to eq 1
end
end
context 'when webhook is updated' do
it 'has associated audit log created' do
webhook.update(url: 'https://example.com')
expect(Audited::Audit.where(auditable_type: 'Webhook', action: 'update').count).to eq 1
end
end
context 'when webhook is deleted' do
it 'has associated audit log created' do
webhook.destroy!
expect(Audited::Audit.where(auditable_type: 'Webhook', action: 'destroy').count).to eq 1
end
end
end
end