Add user based action throttles

This commit is contained in:
Kira 2019-06-27 08:33:02 -07:00
parent 18f0cdf334
commit c819a34443
9 changed files with 160 additions and 47 deletions

View File

@ -7,6 +7,7 @@ class Artist < ApplicationRecord
before_validation :normalize_name
before_validation :normalize_other_names
validate :user_not_limited
after_save :create_version
after_save :categorize_tag
after_save :update_wiki

View File

@ -18,9 +18,12 @@ class Blip < ApplicationRecord
end
def validate_creator_is_not_limited
unless creator.can_comment?
errors.add(:base, "You may not create blips until your account is three days old")
allowed = creator.can_blip_with_reason
if allowed != true
errors.add(:creator, User.throttle_reason(allowed))
false
end
true
end
def validate_parent_exists

View File

@ -112,15 +112,12 @@ class Comment < ApplicationRecord
end
def validate_creator_is_not_limited
if creator.is_comment_limited? && !do_not_bump_post?
errors.add(:base, "You can only post #{Danbooru.config.member_comment_limit} comments per hour")
false
elsif creator.can_comment?
true
else
errors.add(:base, "You can not post comments within 1 week of sign up")
allowed = creator.can_comment_with_reason
if allowed != true
errors.add(:creator, User.throttle_reason(allowed))
false
end
true
end
def update_last_commented_at_on_create

View File

@ -10,6 +10,7 @@ class Dmail < ApplicationRecord
validates_presence_of :title, :body, on: :create
validate :validate_sender_is_not_banned, on: :create
validate :user_not_limited, on: :create
belongs_to :owner, :class_name => "User"
belongs_to :to, :class_name => "User"
@ -211,6 +212,16 @@ class Dmail < ApplicationRecord
include ApiMethods
extend SearchMethods
def user_not_limited
allowed = CurrentUser.can_dmail_with_reason
minute_allowed = CurrentUser.can_dmail_minute_with_reason
if allowed != true || minute_allowed != true
errors.add(:base, "Sender #{User.throttle_reason(allowed != true ? allowed : minute_allowed)}.")
false
end
true
end
def validate_sender_is_not_banned
if from.try(:is_banned?)
errors[:base] << "Sender is banned and cannot send messages"

View File

@ -6,6 +6,7 @@ class Note < ApplicationRecord
belongs_to_creator
has_many :versions, -> {order("note_versions.id ASC")}, :class_name => "NoteVersion", :dependent => :destroy
validates_presence_of :post_id, :creator_id, :x, :y, :width, :height, :body
validate :user_not_limited
validate :post_must_exist
validate :note_within_image
after_save :update_post
@ -68,6 +69,15 @@ class Note < ApplicationRecord
extend SearchMethods
include ApiMethods
def user_not_limited
allowed = CurrentUser.can_not_edit_with_reason
if allowed != true
errors.add(:base, "User #{User.throttle_reason(allowed)}.")
false
end
true
end
def post_must_exist
if !Post.exists?(post_id)
errors.add :post, "must exist"

View File

@ -6,6 +6,8 @@ class Pool < ApplicationRecord
belongs_to_creator
validates_uniqueness_of :name, case_sensitive: false, if: :name_changed?
validate :user_not_create_limited, on: :create
validate :user_not_limited, on: :update
validate :validate_name, if: :name_changed?
validates_inclusion_of :category, :in => %w(series collection)
validate :updater_can_change_category
@ -19,6 +21,10 @@ class Pool < ApplicationRecord
after_create :synchronize!
module SearchMethods
def for_user(id)
where("pools.creator_id = ?", id)
end
def deleted
where("pools.is_deleted = true")
end
@ -99,6 +105,24 @@ class Pool < ApplicationRecord
extend SearchMethods
def user_not_create_limited
allowed = creator.can_pool_with_reason
if allowed != true
errors.add(:creator, User.throttle_reason(allowed))
false
end
true
end
def user_not_limited
allowed = CurrentUser.can_pool_edit_with_reason
if allowed != true
errors.add(:updater, User.throttle_reason(allowed))
false
end
true
end
def self.name_to_id(name)
if name =~ /^\d+$/
name.to_i

View File

@ -426,13 +426,69 @@ class User < ApplicationRecord
end
end
module ThrottleMethods
def throttle_reason(reason)
reasons = {
REJ_NEWBIE: 'can not yet perform this action. User not old enough',
REJ_LIMITED: 'has reached the hourly limit for this action'
}
reasons.fetch(reason, 'unknown throttle reason, please report this as a bug')
end
def upload_reason_string(reason)
reasons = {
REJ_UPLOAD_HOURLY: "have reached your hourly upload limit",
REJ_UPLOAD_EDIT: "have no remaining tag edits available",
REJ_UPLOAD_LIMIT: "have reached your upload limit",
REJ_UPLOAD_NEWBIE: "cannot upload during your first week"
}
reasons.fetch(reason, "unknown upload rejection reason")
end
end
module LimitMethods
extend Memoist
def self.create_user_throttle(name, limiter, checker, newbie_duration)
define_method("#{name}_limit".to_sym, limiter)
memoize "#{name}_limit".to_sym
define_method("can_#{name}_with_reason".to_sym) do
return :REJ_NEWBIE if newbie_duration && created_at > newbie_duration
return send(checker) if checker && send(checker)
return :REJ_LIMITED if send("#{name}_limit") <= 0
true
end
end
def token_bucket
@token_bucket ||= UserThrottle.new({prefix: "thtl:", duration: 1.minute}, self)
end
def general_should_throttle?
!is_platinum?
end
create_user_throttle(:artist_edit, ->{ Danbooru.config.artist_edit_limit - ArtistVersion.for_user(id).where('updated_at > ?', 1.hour.ago).count },
:general_should_throttle?, 7.days.ago)
create_user_throttle(:post_edit, ->{ Danbooru.config.post_edit_limit - PostArchive.for_user(id).where('updated_at > ?', 1.hour.ago).count },
:general_should_throttle?, 3.days.ago)
create_user_throttle(:wiki_edit, ->{ Danbooru.config.wiki_edit_limit - WikiPageVersion.for_user(id).where('updated_at > ?', 1.hour.ago).count },
:general_should_throttle?, 7.days.ago)
create_user_throttle(:pool, ->{ Danbooru.config.pool_limit - Pool.for_user(id).where('created_at > ?', 1.hour.ago).count },
nil, 7.days.ago)
create_user_throttle(:pool_edit, ->{ Danbooru.config.pool_edit_limit - PoolArchive.for_user(id).where('updated_at > ?', 1.hour.ago).count },
nil, 7.days.ago)
create_user_throttle(:note_edit, ->{ Danbooru.config.note_edit_limit - NoteVersion.for_user(id).where('updated_at > ?', 1.hour.ago).count },
:general_should_throttle?, 7.days.ago)
create_user_throttle(:comment, ->{ Danbooru.config.member_comment_limit - Comment.for_creator(id).where('created_at > ?', 1.hour.ago).count },
:general_should_throttle?, 7.days.ago)
create_user_throttle(:blip, ->{ Danbooru.config.blip_limit - Blip.for_creator(id).where('created_at > ?', 1.hour.ago).count },
:general_should_throttle?, 3.days.ago)
create_user_throttle(:dmail, ->{ Danbooru.config.dmail_limit - Dmail.sent_by(id).where('created_at > ?', 1.hour.ago).count },
nil, nil)
create_user_throttle(:dmail_minute, ->{ Danbooru.config.dmail_minute_limit - Dmail.sent_by(id).where('created_at > ?', 1.minute.ago).count },
nil, nil)
def max_saved_searches
if is_platinum?
1_000
@ -445,34 +501,6 @@ class User < ApplicationRecord
true
end
def can_edit_with_reason
return :REJ_EDIT_NEWBIE if created_at > 3.days.ago
return true if is_platinum?
return :REJ_EDIT_LIMIT if post_edit_limit <= 0
true
end
def post_edit_limit
Danbooru.config.post_edit_limit - PostArchive.for_user(id).where('updated_at > ?', 1.hour.ago).count
end
memoize :post_edit_limit
def can_comment?
if is_gold?
true
else
created_at <= Danbooru.config.member_comment_time_threshold
end
end
def is_comment_limited?
if is_gold?
false
else
Comment.where("creator_id = ? and created_at > ?", id, 1.hour.ago).count >= Danbooru.config.member_comment_limit
end
end
def can_comment_vote?
CommentVote.where("user_id = ? and created_at > ?", id, 1.hour.ago).count < 10
end
@ -511,16 +539,6 @@ class User < ApplicationRecord
end
end
def self.upload_reason_string(reason)
reasons = {
REJ_UPLOAD_HOURLY: "have reached your hourly upload limit",
REJ_UPLOAD_EDIT: "have no remaining tag edits available",
REJ_UPLOAD_LIMIT: "have reached your upload limit",
REJ_UPLOAD_NEWBIE: "cannot upload during your first week"
}
reasons.fetch(reason, "unknown upload rejection reason")
end
def upload_limited_reason
User.upload_reason_string(can_upload_with_reason)
end
@ -874,6 +892,7 @@ class User < ApplicationRecord
include ApiMethods
include CountMethods
extend SearchMethods
extend ThrottleMethods
include StatisticsMethods
def as_current(&block)

View File

@ -7,6 +7,7 @@ class WikiPage < ApplicationRecord
validates_uniqueness_of :title, :case_sensitive => false
validates_presence_of :title
validates_presence_of :body, :unless => -> { is_deleted? || other_names.present? }
validate :user_not_limited, on: :save
validate :validate_rename
validate :validate_not_locked
@ -125,6 +126,15 @@ class WikiPage < ApplicationRecord
extend SearchMethods
include ApiMethods
def user_not_limited
allowed = CurrentUser.can_wiki_edit_with_reason
if allowed != true
errors.add(:base, "User #{User.throttle_reason(allowed)}.")
false
end
true
end
def validate_not_locked
if is_locked? && !CurrentUser.is_builder?
errors.add(:is_locked, "and cannot be updated")

View File

@ -167,9 +167,47 @@ module Danbooru
# Members cannot post more than X comments in an hour.
def member_comment_limit
15
end
def dmail_limit
20
end
def dmail_minute_limit
1
end
# Blips created in the last hour
def blip_limit
25
end
# Artists creator or edited in the last hour
def artist_edit_limit
25
end
# Wiki pages created or edited in the last hour
def wiki_edit_limit
60
end
# Notes applied to posts edited or created in the last hour
def note_edit_limit
50
end
# Pools created in the last hour
def pool_limit
2
end
# Pools created or edited in the last hour
def pool_edit_limit
10
end
# Members cannot create more than X post versions in an hour.
def post_edit_limit
150