discourse/app/models/user_history.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

355 lines
10 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
2013-09-10 21:21:16 -04:00
# UserHistory stores information about actions that users have taken,
# like deleting users, changing site settings, dismissing notifications, etc.
2013-09-10 21:21:16 -04:00
# Use other classes, like StaffActionLogger, to log records to this table.
class UserHistory < ActiveRecord::Base
belongs_to :acting_user, class_name: "User"
belongs_to :target_user, class_name: "User"
belongs_to :post
belongs_to :topic
belongs_to :category
# Each value in the context should be shorter than this
MAX_CONTEXT_LENGTH = 50_000
# We often store multiple values in details, particularly during post edits
# Let's allow space for 2 values + a little extra for padding
MAX_DETAILS_LENGTH = 110_000
MAX_JSON_LENGTH = 300_000
validates :details, length: { maximum: MAX_DETAILS_LENGTH }
validates :context, length: { maximum: MAX_CONTEXT_LENGTH }
validates :subject, length: { maximum: MAX_CONTEXT_LENGTH }
validates :ip_address, length: { maximum: MAX_CONTEXT_LENGTH }
validates :email, length: { maximum: MAX_CONTEXT_LENGTH }
validates :previous_value, length: { maximum: MAX_JSON_LENGTH }
validates :new_value, length: { maximum: MAX_JSON_LENGTH }
validates_presence_of :action
2013-09-10 21:21:16 -04:00
scope :only_staff_actions, -> { where("action IN (?)", UserHistory.staff_action_ids) }
before_save :set_admin_only
def self.actions
@actions ||=
Enum.new(
delete_user: 1,
change_trust_level: 2,
change_site_setting: 3,
change_theme: 4,
delete_theme: 5,
checked_for_custom_avatar: 6, # not used anymore
notified_about_avatar: 7,
notified_about_sequential_replies: 8,
notified_about_dominating_topic: 9,
suspend_user: 10,
unsuspend_user: 11,
facebook_no_email: 12, # not used anymore
grant_badge: 13,
revoke_badge: 14,
auto_trust_level_change: 15,
check_email: 16,
delete_post: 17,
delete_topic: 18,
impersonate: 19,
roll_up: 20,
change_username: 21,
custom: 22,
custom_staff: 23,
anonymize_user: 24,
reviewed_post: 25,
change_category_settings: 26,
delete_category: 27,
create_category: 28,
change_site_text: 29,
silence_user: 30,
unsilence_user: 31,
grant_admin: 32,
revoke_admin: 33,
grant_moderation: 34,
revoke_moderation: 35,
backup_create: 36,
rate_limited_like: 37, # not used anymore
revoke_email: 38,
deactivate_user: 39,
wizard_step: 40,
lock_trust_level: 41,
unlock_trust_level: 42,
activate_user: 43,
change_readonly_mode: 44,
backup_download: 45,
backup_destroy: 46,
notified_about_get_a_room: 47,
change_name: 48,
post_locked: 49,
post_unlocked: 50,
check_personal_message: 51,
disabled_second_factor: 52,
post_edit: 53,
topic_published: 54,
recover_topic: 55,
post_approved: 56,
create_badge: 57,
change_badge: 58,
delete_badge: 59,
removed_silence_user: 60,
removed_suspend_user: 61,
removed_unsilence_user: 62,
removed_unsuspend_user: 63,
2018-06-08 10:48:26 -04:00
post_rejected: 64,
merge_user: 65,
entity_export: 66,
change_password: 67,
2019-03-12 04:16:56 -04:00
topic_timestamps_changed: 68,
approve_user: 69,
web_hook_create: 70,
web_hook_update: 71,
web_hook_destroy: 72,
embeddable_host_create: 73,
embeddable_host_update: 74,
embeddable_host_destroy: 75,
web_hook_deactivate: 76,
change_theme_setting: 77,
disable_theme_component: 78,
enable_theme_component: 79,
api_key_create: 80,
api_key_update: 81,
api_key_destroy: 82,
FIX: Badge and user title interaction fixes (#8282) * Fix user title logic when badge name customized * Fix an issue where a user's title was not considered a badge granted title when the user used a badge for their title and the badge name was customized. this affected the effectiveness of revoke_ungranted_titles! which only operates on badge_granted_titles. * When a user's title is set now it is considered a badge_granted_title if the badge name OR the badge custom name from TranslationOverride is the same as the title * When a user's badge is revoked we now also revoke their title if the user's title matches the badge name OR the badge custom name from TranslationOverride * Add a user history log when the title is revoked to remove confusion about why titles are revoked * Add granted_title_badge_id to user_profile, now when we set badge_granted_title on a user profile when updating a user's title based on a badge, we also remember which badge matched the title * When badge name (or custom text) changes update titles of users in a background job * When the name of a badge changes, or in the case of system badges when their custom translation text changes, then we need to update the title of all corresponding users who have a badge_granted_title and matching granted_title_badge_id. In the case of system badges we need to first get the proper badge ID based on the translation key e.g. badges.regular.name * Add migration to backfill all granted_title_badge_ids for both normal badge name titles and titles using custom badge text.
2019-11-08 00:34:24 -05:00
revoke_title: 83,
change_title: 84,
override_upload_secure_status: 85,
page_published: 86,
page_unpublished: 87,
add_email: 88,
update_email: 89,
destroy_email: 90,
topic_closed: 91,
topic_opened: 92,
topic_archived: 93,
topic_unarchived: 94,
post_staff_note_create: 95,
post_staff_note_destroy: 96,
watched_word_create: 97,
watched_word_destroy: 98,
delete_group: 99,
FEATURE: Allow admins to permanently delete revisions (#19913) # Context This PR introduces the ability to permanently delete revisions from a post while maintaining the changes implemented by the revisions. Additional Context: /t/90301 # Functionality In the case a staff member wants to _remove the visual cue_ that a post has been edited eg. <img width="86" alt="Screenshot 2023-01-18 at 2 59 12 PM" src="https://user-images.githubusercontent.com/50783505/213293333-9c881229-ab18-4591-b39b-e3419a67907d.png"> while maintaining the changes made in the edits, they can enable the (hidden) site setting of `can_permanently_delete`. When this is enabled, after _hiding_ the revisions <img width="149" alt="Screenshot 2023-01-19 at 1 53 35 PM" src="https://user-images.githubusercontent.com/50783505/213546080-2a9e9c55-b3ef-428e-a93d-1b6ba287dfae.png"> there will be an additional button in the history modal to <kbd>Delete revisions</kbd> on a post. <img width="997" alt="Screenshot 2023-01-19 at 1 49 51 PM" src="https://user-images.githubusercontent.com/50783505/213546333-49042558-50ab-4724-9da7-08bacc68d38d.png"> Since this action is permanent, we display a confirmation dialog prior to triggering the destroy call <img width="722" alt="Screenshot 2023-01-19 at 1 55 59 PM" src="https://user-images.githubusercontent.com/50783505/213546487-96ea6e89-ac49-4892-b4b0-28996e3c867f.png"> Once confirmed the history modal will close and the post will `rebake` to display an _unedited_ post. <img width="868" alt="Screenshot 2023-01-19 at 1 56 35 PM" src="https://user-images.githubusercontent.com/50783505/213546608-d6436717-8484-4132-a1a8-b7a348d92728.png"> see that there is not a visual que for _revision have been made on this post_ for a post that **HAS** been edited. In addition to this, a user history log for `purge_post_revisions` will be added for each action completed. # Limits - Admins are rate limited to 20 posts per minute
2023-01-19 16:09:01 -05:00
permanently_delete_post_revisions: 100,
create_public_sidebar_section: 101,
update_public_sidebar_section: 102,
destroy_public_sidebar_section: 103,
reset_bounce_score: 104,
)
end
2013-09-10 21:21:16 -04:00
# Staff actions is a subset of all actions, used to audit actions taken by staff users.
def self.staff_actions
@staff_actions ||= %i[
delete_user
change_trust_level
change_site_setting
change_theme
delete_theme
change_site_text
suspend_user
unsuspend_user
removed_suspend_user
removed_unsuspend_user
grant_badge
revoke_badge
check_email
delete_post
delete_topic
impersonate
roll_up
change_username
custom_staff
anonymize_user
reviewed_post
change_category_settings
delete_category
create_category
silence_user
unsilence_user
removed_silence_user
removed_unsilence_user
grant_admin
revoke_admin
grant_moderation
revoke_moderation
backup_create
revoke_email
deactivate_user
lock_trust_level
unlock_trust_level
activate_user
change_readonly_mode
backup_download
backup_destroy
post_locked
post_unlocked
check_personal_message
disabled_second_factor
post_edit
topic_published
recover_topic
post_approved
create_badge
change_badge
delete_badge
2018-06-08 10:48:26 -04:00
post_rejected
merge_user
entity_export
change_name
2019-03-12 04:16:56 -04:00
topic_timestamps_changed
approve_user
web_hook_create
web_hook_update
web_hook_destroy
web_hook_deactivate
embeddable_host_create
embeddable_host_update
embeddable_host_destroy
change_theme_setting
disable_theme_component
enable_theme_component
FIX: Badge and user title interaction fixes (#8282) * Fix user title logic when badge name customized * Fix an issue where a user's title was not considered a badge granted title when the user used a badge for their title and the badge name was customized. this affected the effectiveness of revoke_ungranted_titles! which only operates on badge_granted_titles. * When a user's title is set now it is considered a badge_granted_title if the badge name OR the badge custom name from TranslationOverride is the same as the title * When a user's badge is revoked we now also revoke their title if the user's title matches the badge name OR the badge custom name from TranslationOverride * Add a user history log when the title is revoked to remove confusion about why titles are revoked * Add granted_title_badge_id to user_profile, now when we set badge_granted_title on a user profile when updating a user's title based on a badge, we also remember which badge matched the title * When badge name (or custom text) changes update titles of users in a background job * When the name of a badge changes, or in the case of system badges when their custom translation text changes, then we need to update the title of all corresponding users who have a badge_granted_title and matching granted_title_badge_id. In the case of system badges we need to first get the proper badge ID based on the translation key e.g. badges.regular.name * Add migration to backfill all granted_title_badge_ids for both normal badge name titles and titles using custom badge text.
2019-11-08 00:34:24 -05:00
revoke_title
change_title
api_key_create
api_key_update
api_key_destroy
override_upload_secure_status
page_published
page_unpublished
add_email
update_email
destroy_email
topic_closed
topic_opened
topic_archived
topic_unarchived
post_staff_note_create
post_staff_note_destroy
watched_word_create
watched_word_destroy
delete_group
FEATURE: Allow admins to permanently delete revisions (#19913) # Context This PR introduces the ability to permanently delete revisions from a post while maintaining the changes implemented by the revisions. Additional Context: /t/90301 # Functionality In the case a staff member wants to _remove the visual cue_ that a post has been edited eg. <img width="86" alt="Screenshot 2023-01-18 at 2 59 12 PM" src="https://user-images.githubusercontent.com/50783505/213293333-9c881229-ab18-4591-b39b-e3419a67907d.png"> while maintaining the changes made in the edits, they can enable the (hidden) site setting of `can_permanently_delete`. When this is enabled, after _hiding_ the revisions <img width="149" alt="Screenshot 2023-01-19 at 1 53 35 PM" src="https://user-images.githubusercontent.com/50783505/213546080-2a9e9c55-b3ef-428e-a93d-1b6ba287dfae.png"> there will be an additional button in the history modal to <kbd>Delete revisions</kbd> on a post. <img width="997" alt="Screenshot 2023-01-19 at 1 49 51 PM" src="https://user-images.githubusercontent.com/50783505/213546333-49042558-50ab-4724-9da7-08bacc68d38d.png"> Since this action is permanent, we display a confirmation dialog prior to triggering the destroy call <img width="722" alt="Screenshot 2023-01-19 at 1 55 59 PM" src="https://user-images.githubusercontent.com/50783505/213546487-96ea6e89-ac49-4892-b4b0-28996e3c867f.png"> Once confirmed the history modal will close and the post will `rebake` to display an _unedited_ post. <img width="868" alt="Screenshot 2023-01-19 at 1 56 35 PM" src="https://user-images.githubusercontent.com/50783505/213546608-d6436717-8484-4132-a1a8-b7a348d92728.png"> see that there is not a visual que for _revision have been made on this post_ for a post that **HAS** been edited. In addition to this, a user history log for `purge_post_revisions` will be added for each action completed. # Limits - Admins are rate limited to 20 posts per minute
2023-01-19 16:09:01 -05:00
permanently_delete_post_revisions
create_public_sidebar_section
update_public_sidebar_section
destroy_public_sidebar_section
reset_bounce_score
update_directory_columns
deleted_unused_tags
renamed_tag
deleted_tag
chat_channel_status_change
chat_auto_remove_membership
]
2013-09-10 21:21:16 -04:00
end
def self.staff_action_ids
@staff_action_ids ||= staff_actions.map { |a| actions[a] }
end
def self.admin_only_action_ids
@admin_only_action_ids ||= [actions[:change_site_setting]]
end
def self.with_filters(filters)
query = self
query = query.where(action: filters[:action_id]) if filters[:action_id].present?
query = query.where(custom_type: filters[:custom_type]) if filters[:custom_type].present?
2013-09-10 21:21:16 -04:00
%i[acting_user target_user].each do |key|
2013-08-09 16:58:57 -04:00
if filters[key] && (obj_id = User.where(username_lower: filters[key].downcase).pluck(:id))
query = query.where("#{key}_id = ?", obj_id)
2013-08-09 16:58:57 -04:00
end
end
query = query.where("subject = ?", filters[:subject]) if filters[:subject]
query
end
def self.for(user, action_type)
self.where(target_user_id: user.id, action: UserHistory.actions[action_type])
end
def self.exists_for_user?(user, action_type, opts = nil)
opts = opts || {}
result = self.where(target_user_id: user.id, action: UserHistory.actions[action_type])
result = result.where(topic_id: opts[:topic_id]) if opts[:topic_id]
result.exists?
end
def self.staff_filters
%i[action_id custom_type acting_user target_user subject action_name]
end
def self.staff_action_records(viewer, opts = nil)
opts ||= {}
custom_staff = opts[:action_id].to_i == actions[:custom_staff]
if custom_staff
opts[:custom_type] = opts[:action_name]
else
opts[:action_id] = self.actions[opts[:action_name].to_sym] if opts[:action_name]
end
query =
self
.with_filters(opts.slice(*staff_filters))
.only_staff_actions
.order("id DESC")
.includes(:acting_user, :target_user)
query = query.where(admin_only: false) unless viewer && viewer.admin?
query
end
def set_admin_only
self.admin_only = UserHistory.admin_only_action_ids.include?(self.action)
self
end
def new_value_is_json?
[UserHistory.actions[:change_theme], UserHistory.actions[:delete_theme]].include?(action)
end
def previous_value_is_json?
new_value_is_json?
end
end
# == Schema Information
#
# Table name: user_histories
#
# id :integer not null, primary key
# action :integer not null
# acting_user_id :integer
# target_user_id :integer
# details :text
# created_at :datetime not null
# updated_at :datetime not null
2019-01-11 14:29:56 -05:00
# context :string
# ip_address :string
# email :string
2013-08-27 20:42:58 -04:00
# subject :text
# previous_value :text
# new_value :text
# topic_id :integer
2014-03-20 00:35:51 -04:00
# admin_only :boolean default(FALSE)
# post_id :integer
2019-01-11 14:29:56 -05:00
# custom_type :string
# category_id :integer
2013-08-13 16:09:27 -04:00
#
# Indexes
#
2019-01-11 12:19:23 -05:00
# 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)
2019-01-11 12:19:23 -05:00
# 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)
2019-01-11 12:19:23 -05:00
# index_user_histories_on_topic_id_and_target_user_id_and_action (topic_id,target_user_id,action)
#