Only permit valid class names as sponsorable type (#14387)

* Only permit valid class names as sponsorable type

https://github.com/forem/forem/issues/14386 identified an error could
arise if input was provide to the sponsorable_type field in the admin
creation form, but it was not a valid constant (since we include the
related model in the index when loading @sponsorships, this permits
creating a sponsorship that can't easily be managed or deleted).

Add a validation to ensure when the sponsorable type is present, it's
a class (really, we probably want to also ensure it's available as an
associated model type, since we'll be looking it up with find(:sponsorable_id), but this
is an initial attempt at adding some guard rails to this form).

A more realistic solution would be to add a `Sponsorship::SPONSORABLE_TYPES` constant
to the class, and validate the supplied sponsorable type is in
`SPONSORABLE_TYPES.map(&:name)`. That requires more domain knowledge
about what kind of things can be sponsored than I have, it would
certainly include at least ActsAsTaggableOn::Tag but may include other
classes (or why would it be polymorphic).

* Explicitly list the allowed types for sponsorship

I see we can sponsor tags, and suspect that's all we can sponsor
meaningfully (the view only shows details when it is a tag).

* Use inclusion validation with options to replicate custom method

And remove the code, we don't need it any more.

* Update app/models/sponsorship.rb

Co-authored-by: Michael Kohl <citizen428@dev.to>

Co-authored-by: Michael Kohl <citizen428@dev.to>
This commit is contained in:
Daniel Uber 2021-11-05 13:25:35 -04:00 committed by GitHub
parent c043339654
commit d48587721a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 12 additions and 0 deletions

View file

@ -2,6 +2,7 @@ class Sponsorship < ApplicationRecord
LEVELS = %w[gold silver bronze tag media devrel].freeze
METAL_LEVELS = %w[gold silver bronze].freeze
STATUSES = %w[none pending live].freeze
SPONSORABLE_TYPES = %w[Tag ActsAsTaggableOn::Tag].freeze
# media has no fixed amount of credits
CREDITS = {
gold: 6_000,
@ -19,6 +20,11 @@ class Sponsorship < ApplicationRecord
validates :status, presence: true, inclusion: { in: STATUSES }
validates :url, url: { allow_blank: true, no_local: true, schemes: %w[http https] }
validates :user, :organization, :featured_number, presence: true
validates :sponsorable_type, inclusion: {
in: SPONSORABLE_TYPES,
allow_blank: true,
message: "is not a sponsorable type"
}
validate :validate_tag_uniqueness, if: proc { level.to_s == "tag" }
validate :validate_level_uniqueness, if: proc { METAL_LEVELS.include?(level) }

View file

@ -111,6 +111,12 @@ RSpec.describe Sponsorship, type: :model do
expect(tag_sponsorship).not_to be_valid
expect(tag_sponsorship.errors[:level]).to be_present
end
it "ensures sponsorable_type is loadable when present" do
sponsorship = build(:sponsorship, user: user, organization: org, sponsorable_type: "Unsponsorable")
expect(sponsorship).not_to be_valid
expect(sponsorship.errors[:sponsorable_type]).to include("is not a sponsorable type")
end
end
end
end