From 1e478ab1b5b329a7763178b8e8fadefa633737fd Mon Sep 17 00:00:00 2001 From: evazion Date: Sun, 20 Nov 2022 21:35:49 -0600 Subject: [PATCH] favgroups: add stricter favgroup naming rules. Don't allow favgroup names that: * Start or end with underscores. * Contain multiple underscores in a row. * Contain asterisks or non-printable characters. * Consist of only underscores. * Consist of only digits (conflicts with `favgroup:1234` syntax). Add a fix script that fixes favgroups that violate these rules and notifies the user. --- app/models/favorite_group.rb | 33 +++++++++---- script/fixes/129_fix_favgroup_names.rb | 66 ++++++++++++++++++++++++++ test/unit/favorite_group_test.rb | 11 +++++ 3 files changed, 102 insertions(+), 8 deletions(-) create mode 100755 script/fixes/129_fix_favgroup_names.rb diff --git a/app/models/favorite_group.rb b/app/models/favorite_group.rb index a2e605646..9d744b102 100644 --- a/app/models/favorite_group.rb +++ b/app/models/favorite_group.rb @@ -4,12 +4,10 @@ class FavoriteGroup < ApplicationRecord belongs_to :creator, class_name: "User" before_validation :normalize_name - before_validation :strip_name validates :name, presence: true validates :name, uniqueness: { case_sensitive: false, scope: :creator_id } - validates :name, format: { without: /,/, message: "cannot have commas" } - validates :name, exclusion: { in: %w[any none], message: "can't be '%{value}'" } + validate :validate_name, if: :name_changed? validate :creator_can_create_favorite_groups, :on => :create validate :validate_number_of_posts validate :validate_posts @@ -104,8 +102,31 @@ class FavoriteGroup < ApplicationRecord end end + def validate_name + case name + when /\A(any|none)\z/i + errors.add(:name, "cannot be '#{name}'") + when /,/ + errors.add(:name, "cannot contain commas") + when /\*/ + errors.add(:name, "cannot contain asterisks") + when /\A_/ + errors.add(:name, "cannot begin with an underscore") + when /_\z/ + errors.add(:name, "cannot end with an underscore") + when /__/ + errors.add(:name, "cannot contain consecutive underscores") + when /[^[:graph:]]/ + errors.add(:name, "cannot contain non-printable characters") + when "" + errors.add(:name, "cannot be blank") + when /\A[0-9]+\z/ + errors.add(:name, "cannot contain only digits") + end + end + def self.normalize_name(name) - name.gsub(/[[:space:]]+/, "_") + name.gsub(/[_[:space:]]+/, "_").gsub(/\A_|_\z/, "") end def normalize_name @@ -128,10 +149,6 @@ class FavoriteGroup < ApplicationRecord find_by_name_or_id(name, user) or raise ActiveRecord::RecordNotFound end - def strip_name - self.name = name.to_s.strip - end - def pretty_name name&.tr("_", " ") end diff --git a/script/fixes/129_fix_favgroup_names.rb b/script/fixes/129_fix_favgroup_names.rb new file mode 100755 index 000000000..7540c6106 --- /dev/null +++ b/script/fixes/129_fix_favgroup_names.rb @@ -0,0 +1,66 @@ +#!/usr/bin/env ruby + +require_relative "base" + +with_confirmation do + fix = ENV.fetch("FIX", "false").truthy? + + users = User.where.associated(:favorite_groups).distinct + users.find_each do |user| + changed_favgroups = [] + + user.favorite_groups.each do |favgroup| + if favgroup.name.match?(/[^[:graph:]]/) + favgroup.name.gsub!(/[^[:graph:]]/, "_") + end + + if favgroup.name.include?("*") + favgroup.name.tr!("*", "?") + end + + if favgroup.name.include?("__") + favgroup.name.gsub!(/_+/, "_") + end + + if favgroup.name.starts_with?("_") + favgroup.name.delete_prefix!("_") + end + + if favgroup.name.ends_with?("_") + favgroup.name.delete_suffix!("_") + end + + if favgroup.name.match?(/\A[0-9]+\z/) + favgroup.name = "favgroup_#{favgroup.name}" + end + + if favgroup.name.blank? || user.favorite_groups.without(favgroup).exists?(name: favgroup.name) + favgroup.name = "favgroup_#{favgroup.id}" + end + + if favgroup.changed? + changed_favgroups << favgroup + puts ({ id: favgroup.id, creator: favgroup.creator.name, changes: favgroup.changes }).to_json + favgroup.save! if fix + end + end + + if fix && changed_favgroups.present? + Dmail.create_automated(to: user, title: "Your favorite groups have been renamed", disable_email_notifications: true, body: <<~EOS) + The following #{"favgroup".pluralize(changed_favgroups.size)} #{changed_favgroups.one? ? "has" : "have"} been renamed: + + #{changed_favgroups.map { |favgroup| "* favgroup ##{favgroup.id}: \"#{favgroup.name_was}\" -> \"#{favgroup.name}\"" }.join("\n")} + + Your #{"favgroup".pluralize(changed_favgroups.size)} had to be renamed because #{changed_favgroups.one? ? "it" : "they"} didn't follow one of our new naming rules: + + * Names can't consist of numbers only. + * Names can't start or end with an underscore (_). + * Names can't contain multiple underscores in a row (__). + * Names can't contain commas or asterisks (*). + * Names can't be blank. + + You can change the name to something else by going to your "Favgroups":[/favorite_groups] page and clicking "Edit" to change the name. + EOS + end + end +end diff --git a/test/unit/favorite_group_test.rb b/test/unit/favorite_group_test.rb index dc3c6b739..a5f9e8d89 100644 --- a/test/unit/favorite_group_test.rb +++ b/test/unit/favorite_group_test.rb @@ -58,4 +58,15 @@ class FavoriteGroupTest < ActiveSupport::TestCase assert_equal([], @fav_group.reload.post_ids) end end + + context "when validating names" do + subject { build(:favorite_group) } + + should_not allow_value("foo,bar").for(:name) + should_not allow_value("foo*bar").for(:name) + should_not allow_value("123").for(:name) + should_not allow_value("_").for(:name) + should_not allow_value("any").for(:name) + should_not allow_value("none").for(:name) + end end