FEATURE: Log staff actions for Category changes.
This commit is contained in:
parent
c29b7ce498
commit
f39b9124b6
|
@ -11,6 +11,7 @@ Discourse.StaffActionLog = Discourse.Model.extend({
|
||||||
formatted += this.format('admin.logs.ip_address', 'ip_address');
|
formatted += this.format('admin.logs.ip_address', 'ip_address');
|
||||||
formatted += this.format('admin.logs.topic_id', 'topic_id');
|
formatted += this.format('admin.logs.topic_id', 'topic_id');
|
||||||
formatted += this.format('admin.logs.post_id', 'post_id');
|
formatted += this.format('admin.logs.post_id', 'post_id');
|
||||||
|
formatted += this.format('admin.logs.category_id', 'category_id');
|
||||||
if (!this.get('useCustomModalForDetails')) {
|
if (!this.get('useCustomModalForDetails')) {
|
||||||
formatted += this.format('admin.logs.staff_actions.new_value', 'new_value');
|
formatted += this.format('admin.logs.staff_actions.new_value', 'new_value');
|
||||||
formatted += this.format('admin.logs.staff_actions.previous_value', 'previous_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/>';
|
if (this.get('details')) formatted += Handlebars.Utils.escapeExpression(this.get('details')) + '<br/>';
|
||||||
}
|
}
|
||||||
return formatted;
|
return formatted;
|
||||||
}.property('ip_address', 'email', 'topic_id', 'post_id'),
|
}.property('ip_address', 'email', 'topic_id', 'post_id', 'category_id'),
|
||||||
|
|
||||||
format: function(label, propertyName) {
|
format: function(label, propertyName) {
|
||||||
if (this.get(propertyName)) {
|
if (this.get(propertyName)) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ class CategoriesController < ApplicationController
|
||||||
|
|
||||||
before_filter :ensure_logged_in, except: [:index, :show, :redirect]
|
before_filter :ensure_logged_in, except: [:index, :show, :redirect]
|
||||||
before_filter :fetch_category, only: [:show, :update, :destroy]
|
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]
|
skip_before_filter :check_xhr, only: [:index, :redirect]
|
||||||
|
|
||||||
def redirect
|
def redirect
|
||||||
|
@ -81,10 +82,18 @@ class CategoriesController < ApplicationController
|
||||||
position = category_params.delete(:position)
|
position = category_params.delete(:position)
|
||||||
|
|
||||||
@category = Category.create(category_params.merge(user: current_user))
|
@category = Category.create(category_params.merge(user: current_user))
|
||||||
return render_json_error(@category) unless @category.save
|
|
||||||
|
|
||||||
@category.move_to(position.to_i) if position
|
if @category.save
|
||||||
render_serialized(@category, CategorySerializer)
|
@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
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
@ -103,8 +112,15 @@ class CategoriesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
category_params.delete(:position)
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -133,6 +149,10 @@ class CategoriesController < ApplicationController
|
||||||
guardian.ensure_can_delete!(@category)
|
guardian.ensure_can_delete!(@category)
|
||||||
@category.destroy
|
@category.destroy
|
||||||
|
|
||||||
|
Scheduler::Defer.later "Log staff action delete category" do
|
||||||
|
@staff_action_logger.log_category_deletion(@category)
|
||||||
|
end
|
||||||
|
|
||||||
render json: success_json
|
render json: success_json
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -175,4 +195,8 @@ class CategoriesController < ApplicationController
|
||||||
def fetch_category
|
def fetch_category
|
||||||
@category = Category.find_by(slug: params[:id]) || Category.find_by(id: params[:id].to_i)
|
@category = Category.find_by(slug: params[:id]) || Category.find_by(id: params[:id].to_i)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def initialize_staff_action_logger
|
||||||
|
@staff_action_logger = StaffActionLogger.new(current_user)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -283,6 +283,14 @@ SQL
|
||||||
set_permissions(permissions)
|
set_permissions(permissions)
|
||||||
end
|
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
|
def apply_permissions
|
||||||
if @permissions
|
if @permissions
|
||||||
category_groups.destroy_all
|
category_groups.destroy_all
|
||||||
|
|
|
@ -2,6 +2,8 @@ class CategoryGroup < ActiveRecord::Base
|
||||||
belongs_to :category
|
belongs_to :category
|
||||||
belongs_to :group
|
belongs_to :group
|
||||||
|
|
||||||
|
delegate :name, to: :group, prefix: true
|
||||||
|
|
||||||
def self.permission_types
|
def self.permission_types
|
||||||
@permission_types ||= Enum.new(:full, :create_post, :readonly)
|
@permission_types ||= Enum.new(:full, :create_post, :readonly)
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ class UserHistory < ActiveRecord::Base
|
||||||
|
|
||||||
belongs_to :post
|
belongs_to :post
|
||||||
belongs_to :topic
|
belongs_to :topic
|
||||||
|
belongs_to :category
|
||||||
|
|
||||||
validates_presence_of :action
|
validates_presence_of :action
|
||||||
|
|
||||||
|
@ -39,7 +40,10 @@ class UserHistory < ActiveRecord::Base
|
||||||
:custom,
|
:custom,
|
||||||
:custom_staff,
|
:custom_staff,
|
||||||
:anonymize_user,
|
:anonymize_user,
|
||||||
:reviewed_post)
|
:reviewed_post,
|
||||||
|
:change_category_settings,
|
||||||
|
:delete_category,
|
||||||
|
:create_category)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Staff actions is a subset of all actions, used to audit actions taken by staff users.
|
# 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,
|
:change_username,
|
||||||
:custom_staff,
|
:custom_staff,
|
||||||
:anonymize_user,
|
:anonymize_user,
|
||||||
:reviewed_post]
|
:reviewed_post,
|
||||||
|
:change_category_settings,
|
||||||
|
:delete_category,
|
||||||
|
:create_category]
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.staff_action_ids
|
def self.staff_action_ids
|
||||||
|
@ -144,11 +151,13 @@ end
|
||||||
# admin_only :boolean default(FALSE)
|
# admin_only :boolean default(FALSE)
|
||||||
# post_id :integer
|
# post_id :integer
|
||||||
# custom_type :string(255)
|
# custom_type :string(255)
|
||||||
|
# category_id :integer
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
# index_user_histories_on_acting_user_id_and_action_and_id (acting_user_id,action,id)
|
# 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_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_subject_and_id (subject,id)
|
||||||
# index_user_histories_on_target_user_id_and_id (target_user_id,id)
|
# index_user_histories_on_target_user_id_and_id (target_user_id,id)
|
||||||
#
|
#
|
||||||
|
|
|
@ -10,6 +10,7 @@ class UserHistorySerializer < ApplicationSerializer
|
||||||
:new_value,
|
:new_value,
|
||||||
:topic_id,
|
:topic_id,
|
||||||
:post_id,
|
:post_id,
|
||||||
|
:category_id,
|
||||||
:action,
|
:action,
|
||||||
:custom_type
|
:custom_type
|
||||||
|
|
||||||
|
|
|
@ -210,6 +210,64 @@ class StaffActionLogger
|
||||||
}))
|
}))
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def params(opts=nil)
|
def params(opts=nil)
|
||||||
|
@ -217,4 +275,8 @@ class StaffActionLogger
|
||||||
{ acting_user_id: @admin.id, context: opts[:context] }
|
{ acting_user_id: @admin.id, context: opts[:context] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_category(category)
|
||||||
|
raise Discourse::InvalidParameters.new(:category) unless category && category.is_a?(Category)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -2163,6 +2163,7 @@ en:
|
||||||
ip_address: "IP"
|
ip_address: "IP"
|
||||||
topic_id: "Topic ID"
|
topic_id: "Topic ID"
|
||||||
post_id: "Post ID"
|
post_id: "Post ID"
|
||||||
|
category_id: "Category ID"
|
||||||
delete: 'Delete'
|
delete: 'Delete'
|
||||||
edit: 'Edit'
|
edit: 'Edit'
|
||||||
save: 'Save'
|
save: 'Save'
|
||||||
|
@ -2203,6 +2204,9 @@ en:
|
||||||
impersonate: "impersonate"
|
impersonate: "impersonate"
|
||||||
anonymize_user: "anonymize user"
|
anonymize_user: "anonymize user"
|
||||||
roll_up: "roll up IP blocks"
|
roll_up: "roll up IP blocks"
|
||||||
|
change_category_settings: "change category settings"
|
||||||
|
delete_category: "delete category"
|
||||||
|
create_category: "create category"
|
||||||
screened_emails:
|
screened_emails:
|
||||||
title: "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."
|
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.slug).to eq("hello-cat")
|
||||||
expect(category.color).to eq("ff0")
|
expect(category.color).to eq("ff0")
|
||||||
expect(category.auto_close_hours).to eq(72)
|
expect(category.auto_close_hours).to eq(72)
|
||||||
|
expect(UserHistory.count).to eq(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -90,6 +91,7 @@ describe CategoriesController do
|
||||||
it "deletes the record" do
|
it "deletes the record" do
|
||||||
Guardian.any_instance.expects(:can_delete_category?).returns(true)
|
Guardian.any_instance.expects(:can_delete_category?).returns(true)
|
||||||
expect { xhr :delete, :destroy, id: @category.slug}.to change(Category, :count).by(-1)
|
expect { xhr :delete, :destroy, id: @category.slug}.to change(Category, :count).by(-1)
|
||||||
|
expect(UserHistory.count).to eq(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -215,6 +217,17 @@ describe CategoriesController do
|
||||||
expect(@category.auto_close_hours).to eq(72)
|
expect(@category.auto_close_hours).to eq(72)
|
||||||
expect(@category.custom_fields).to eq({"dancing" => "frogs"})
|
expect(@category.custom_fields).to eq({"dancing" => "frogs"})
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
Fabricator(:category_group) do
|
||||||
|
category
|
||||||
|
group
|
||||||
|
permission_type 1
|
||||||
|
end
|
|
@ -36,6 +36,15 @@ describe Category do
|
||||||
end
|
end
|
||||||
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
|
describe "topic_create_allowed and post_create_allowed" do
|
||||||
it "works" do
|
it "works" do
|
||||||
|
|
||||||
|
|
|
@ -271,4 +271,81 @@ describe StaffActionLogger do
|
||||||
expect(logged.topic_id).to be === 1234
|
expect(logged.topic_id).to be === 1234
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
Loading…
Reference in New Issue