mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 15:48:43 -05:00
FEATURE: Log staff actions for Category changes.
This commit is contained in:
parent
c29b7ce498
commit
f39b9124b6
13 changed files with 228 additions and 7 deletions
|
@ -11,6 +11,7 @@ Discourse.StaffActionLog = Discourse.Model.extend({
|
|||
formatted += this.format('admin.logs.ip_address', 'ip_address');
|
||||
formatted += this.format('admin.logs.topic_id', 'topic_id');
|
||||
formatted += this.format('admin.logs.post_id', 'post_id');
|
||||
formatted += this.format('admin.logs.category_id', 'category_id');
|
||||
if (!this.get('useCustomModalForDetails')) {
|
||||
formatted += this.format('admin.logs.staff_actions.new_value', 'new_value');
|
||||
formatted += this.format('admin.logs.staff_actions.previous_value', 'previous_value');
|
||||
|
@ -19,7 +20,7 @@ Discourse.StaffActionLog = Discourse.Model.extend({
|
|||
if (this.get('details')) formatted += Handlebars.Utils.escapeExpression(this.get('details')) + '<br/>';
|
||||
}
|
||||
return formatted;
|
||||
}.property('ip_address', 'email', 'topic_id', 'post_id'),
|
||||
}.property('ip_address', 'email', 'topic_id', 'post_id', 'category_id'),
|
||||
|
||||
format: function(label, propertyName) {
|
||||
if (this.get(propertyName)) {
|
||||
|
|
|
@ -4,6 +4,7 @@ class CategoriesController < ApplicationController
|
|||
|
||||
before_filter :ensure_logged_in, except: [:index, :show, :redirect]
|
||||
before_filter :fetch_category, only: [:show, :update, :destroy]
|
||||
before_filter :initialize_staff_action_logger, only: [:create, :update, :destroy]
|
||||
skip_before_filter :check_xhr, only: [:index, :redirect]
|
||||
|
||||
def redirect
|
||||
|
@ -81,10 +82,18 @@ class CategoriesController < ApplicationController
|
|||
position = category_params.delete(:position)
|
||||
|
||||
@category = Category.create(category_params.merge(user: current_user))
|
||||
return render_json_error(@category) unless @category.save
|
||||
|
||||
if @category.save
|
||||
@category.move_to(position.to_i) if position
|
||||
|
||||
Scheduler::Defer.later "Log staff action create category" do
|
||||
@staff_action_logger.log_category_creation(@category)
|
||||
end
|
||||
|
||||
render_serialized(@category, CategorySerializer)
|
||||
else
|
||||
return render_json_error(@category) unless @category.save
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
|
@ -103,8 +112,15 @@ class CategoriesController < ApplicationController
|
|||
end
|
||||
|
||||
category_params.delete(:position)
|
||||
old_permissions = Category.find(@category.id).permissions_params
|
||||
|
||||
cat.update_attributes(category_params)
|
||||
if result = cat.update_attributes(category_params)
|
||||
Scheduler::Defer.later "Log staff action change category settings" do
|
||||
@staff_action_logger.log_category_settings_change(@category, category_params, old_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -133,6 +149,10 @@ class CategoriesController < ApplicationController
|
|||
guardian.ensure_can_delete!(@category)
|
||||
@category.destroy
|
||||
|
||||
Scheduler::Defer.later "Log staff action delete category" do
|
||||
@staff_action_logger.log_category_deletion(@category)
|
||||
end
|
||||
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
|
@ -175,4 +195,8 @@ class CategoriesController < ApplicationController
|
|||
def fetch_category
|
||||
@category = Category.find_by(slug: params[:id]) || Category.find_by(id: params[:id].to_i)
|
||||
end
|
||||
|
||||
def initialize_staff_action_logger
|
||||
@staff_action_logger = StaffActionLogger.new(current_user)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -283,6 +283,14 @@ SQL
|
|||
set_permissions(permissions)
|
||||
end
|
||||
|
||||
def permissions_params
|
||||
hash = {}
|
||||
category_groups.includes(:group).each do |category_group|
|
||||
hash[category_group.group_name] = category_group.permission_type
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
def apply_permissions
|
||||
if @permissions
|
||||
category_groups.destroy_all
|
||||
|
|
|
@ -2,6 +2,8 @@ class CategoryGroup < ActiveRecord::Base
|
|||
belongs_to :category
|
||||
belongs_to :group
|
||||
|
||||
delegate :name, to: :group, prefix: true
|
||||
|
||||
def self.permission_types
|
||||
@permission_types ||= Enum.new(:full, :create_post, :readonly)
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ class UserHistory < ActiveRecord::Base
|
|||
|
||||
belongs_to :post
|
||||
belongs_to :topic
|
||||
belongs_to :category
|
||||
|
||||
validates_presence_of :action
|
||||
|
||||
|
@ -39,7 +40,10 @@ class UserHistory < ActiveRecord::Base
|
|||
:custom,
|
||||
:custom_staff,
|
||||
:anonymize_user,
|
||||
:reviewed_post)
|
||||
:reviewed_post,
|
||||
:change_category_settings,
|
||||
:delete_category,
|
||||
:create_category)
|
||||
end
|
||||
|
||||
# Staff actions is a subset of all actions, used to audit actions taken by staff users.
|
||||
|
@ -61,7 +65,10 @@ class UserHistory < ActiveRecord::Base
|
|||
:change_username,
|
||||
:custom_staff,
|
||||
:anonymize_user,
|
||||
:reviewed_post]
|
||||
:reviewed_post,
|
||||
:change_category_settings,
|
||||
:delete_category,
|
||||
:create_category]
|
||||
end
|
||||
|
||||
def self.staff_action_ids
|
||||
|
@ -144,11 +151,13 @@ end
|
|||
# admin_only :boolean default(FALSE)
|
||||
# post_id :integer
|
||||
# custom_type :string(255)
|
||||
# category_id :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_user_histories_on_acting_user_id_and_action_and_id (acting_user_id,action,id)
|
||||
# index_user_histories_on_action_and_id (action,id)
|
||||
# index_user_histories_on_category_id (category_id)
|
||||
# index_user_histories_on_subject_and_id (subject,id)
|
||||
# index_user_histories_on_target_user_id_and_id (target_user_id,id)
|
||||
#
|
||||
|
|
|
@ -10,6 +10,7 @@ class UserHistorySerializer < ApplicationSerializer
|
|||
:new_value,
|
||||
:topic_id,
|
||||
:post_id,
|
||||
:category_id,
|
||||
:action,
|
||||
:custom_type
|
||||
|
||||
|
|
|
@ -210,6 +210,64 @@ class StaffActionLogger
|
|||
}))
|
||||
end
|
||||
|
||||
def log_category_settings_change(category, category_params, old_permissions=nil)
|
||||
validate_category(category)
|
||||
|
||||
changed_attributes = category.previous_changes.slice(*category_params.keys)
|
||||
|
||||
if old_permissions != category_params[:permissions]
|
||||
changed_attributes.merge!({ permissions: [old_permissions.to_json, category_params[:permissions].to_json] })
|
||||
end
|
||||
|
||||
changed_attributes.each do |key, value|
|
||||
UserHistory.create(params.merge({
|
||||
action: UserHistory.actions[:change_category_settings],
|
||||
category_id: category.id,
|
||||
context: category.url,
|
||||
subject: key,
|
||||
previous_value: value[0],
|
||||
new_value: value[1]
|
||||
}))
|
||||
end
|
||||
end
|
||||
|
||||
def log_category_deletion(category)
|
||||
validate_category(category)
|
||||
|
||||
details = [
|
||||
"created_at: #{category.created_at}",
|
||||
"name: #{category.name}",
|
||||
"permissions: #{category.permissions_params}"
|
||||
]
|
||||
|
||||
if parent_category = category.parent_category
|
||||
details << "parent_category: #{parent_category.name}"
|
||||
end
|
||||
|
||||
UserHistory.create(params.merge({
|
||||
action: UserHistory.actions[:delete_category],
|
||||
category_id: category.id,
|
||||
details: details.join("\n"),
|
||||
context: category.url
|
||||
}))
|
||||
end
|
||||
|
||||
def log_category_creation(category)
|
||||
validate_category(category)
|
||||
|
||||
details = [
|
||||
"created_at: #{category.created_at}",
|
||||
"name: #{category.name}"
|
||||
]
|
||||
|
||||
UserHistory.create(params.merge({
|
||||
action: UserHistory.actions[:create_category],
|
||||
details: details.join("\n"),
|
||||
category_id: category.id,
|
||||
context: category.url
|
||||
}))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def params(opts=nil)
|
||||
|
@ -217,4 +275,8 @@ class StaffActionLogger
|
|||
{ acting_user_id: @admin.id, context: opts[:context] }
|
||||
end
|
||||
|
||||
def validate_category(category)
|
||||
raise Discourse::InvalidParameters.new(:category) unless category && category.is_a?(Category)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -2163,6 +2163,7 @@ en:
|
|||
ip_address: "IP"
|
||||
topic_id: "Topic ID"
|
||||
post_id: "Post ID"
|
||||
category_id: "Category ID"
|
||||
delete: 'Delete'
|
||||
edit: 'Edit'
|
||||
save: 'Save'
|
||||
|
@ -2203,6 +2204,9 @@ en:
|
|||
impersonate: "impersonate"
|
||||
anonymize_user: "anonymize user"
|
||||
roll_up: "roll up IP blocks"
|
||||
change_category_settings: "change category settings"
|
||||
delete_category: "delete category"
|
||||
create_category: "create category"
|
||||
screened_emails:
|
||||
title: "Screened Emails"
|
||||
description: "When someone tries to create a new account, the following email addresses will be checked and the registration will be blocked, or some other action performed."
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class AddCategoryIdToUserHistories < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :user_histories, :category_id, :integer
|
||||
add_index :user_histories, :category_id
|
||||
end
|
||||
end
|
|
@ -64,6 +64,7 @@ describe CategoriesController do
|
|||
expect(category.slug).to eq("hello-cat")
|
||||
expect(category.color).to eq("ff0")
|
||||
expect(category.auto_close_hours).to eq(72)
|
||||
expect(UserHistory.count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -90,6 +91,7 @@ describe CategoriesController do
|
|||
it "deletes the record" do
|
||||
Guardian.any_instance.expects(:can_delete_category?).returns(true)
|
||||
expect { xhr :delete, :destroy, id: @category.slug}.to change(Category, :count).by(-1)
|
||||
expect(UserHistory.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -215,6 +217,17 @@ describe CategoriesController do
|
|||
expect(@category.auto_close_hours).to eq(72)
|
||||
expect(@category.custom_fields).to eq({"dancing" => "frogs"})
|
||||
end
|
||||
|
||||
it 'logs the changes correctly' do
|
||||
xhr :put , :update, id: @category.id, name: 'new name',
|
||||
color: @category.color, text_color: @category.text_color,
|
||||
slug: @category.slug,
|
||||
permissions: {
|
||||
"everyone" => CategoryGroup.permission_types[:create_post]
|
||||
}
|
||||
|
||||
expect(UserHistory.count).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
5
spec/fabricators/category_group_fabricator.rb
Normal file
5
spec/fabricators/category_group_fabricator.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
Fabricator(:category_group) do
|
||||
category
|
||||
group
|
||||
permission_type 1
|
||||
end
|
|
@ -36,6 +36,15 @@ describe Category do
|
|||
end
|
||||
end
|
||||
|
||||
describe "permissions_params" do
|
||||
it "returns the right group names and permission type" do
|
||||
category = Fabricate(:category)
|
||||
group = Fabricate(:group)
|
||||
category_group = Fabricate(:category_group, category: category, group: group)
|
||||
expect(category.permissions_params).to eq({ "#{group.name}" => category_group.permission_type })
|
||||
end
|
||||
end
|
||||
|
||||
describe "topic_create_allowed and post_create_allowed" do
|
||||
it "works" do
|
||||
|
||||
|
|
|
@ -271,4 +271,81 @@ describe StaffActionLogger do
|
|||
expect(logged.topic_id).to be === 1234
|
||||
end
|
||||
end
|
||||
|
||||
describe 'log_category_settings_change' do
|
||||
let(:category) { Fabricate(:category, name: 'haha') }
|
||||
let(:category_group) { Fabricate(:category_group, category: category, permission_type: 1) }
|
||||
|
||||
it "raises an error when category is missing" do
|
||||
expect { logger.log_category_settings_change(nil, nil) }.to raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it "creates new UserHistory records" do
|
||||
attributes = {
|
||||
name: 'new_name',
|
||||
permissions: { category_group.group_name => 2 }
|
||||
}
|
||||
|
||||
category.update!(attributes)
|
||||
|
||||
logger.log_category_settings_change(category, attributes,
|
||||
{ category_group.group_name => category_group.permission_type }
|
||||
)
|
||||
|
||||
expect(UserHistory.count).to eq(2)
|
||||
|
||||
permission_user_history = UserHistory.find_by_subject('permissions')
|
||||
expect(permission_user_history.category_id).to eq(category.id)
|
||||
expect(permission_user_history.previous_value).to eq({ category_group.group_name => 1 }.to_json)
|
||||
expect(permission_user_history.new_value).to eq({ category_group.group_name => 2 }.to_json)
|
||||
expect(permission_user_history.action).to eq(UserHistory.actions[:change_category_settings])
|
||||
expect(permission_user_history.context).to eq(category.url)
|
||||
|
||||
name_user_history = UserHistory.find_by_subject('name')
|
||||
expect(name_user_history.category).to eq(category)
|
||||
expect(name_user_history.previous_value).to eq('haha')
|
||||
expect(name_user_history.new_value).to eq('new_name')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'log_category_deletion' do
|
||||
let(:parent_category) { Fabricate(:category) }
|
||||
let(:category) { Fabricate(:category, parent_category: parent_category) }
|
||||
|
||||
it "raises an error when category is missing" do
|
||||
expect { logger.log_category_deletion(nil) }.to raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it "creates a new UserHistory record" do
|
||||
logger.log_category_deletion(category)
|
||||
|
||||
expect(UserHistory.count).to eq(1)
|
||||
user_history = UserHistory.last
|
||||
|
||||
expect(user_history.subject).to eq(nil)
|
||||
expect(user_history.category).to eq(category)
|
||||
expect(user_history.details).to include("parent_category: #{parent_category.name}")
|
||||
expect(user_history.context).to eq(category.url)
|
||||
expect(user_history.action).to eq(UserHistory.actions[:delete_category])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'log_category_creation' do
|
||||
let(:category) { Fabricate(:category) }
|
||||
|
||||
it "raises an error when category is missing" do
|
||||
expect { logger.log_category_deletion(nil) }.to raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it "creates a new UserHistory record" do
|
||||
logger.log_category_creation(category)
|
||||
|
||||
expect(UserHistory.count).to eq(1)
|
||||
user_history = UserHistory.last
|
||||
|
||||
expect(user_history.category).to eq(category)
|
||||
expect(user_history.context).to eq(category.url)
|
||||
expect(user_history.action).to eq(UserHistory.actions[:create_category])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue