CVE-2022-46159 (Ruby, published 2022-12-02T15:15Z)
Description: value: Discourse is an open-source discussion platform. In version 2.8.13 and prior on the `stable` branch and version 2.9.0.beta14 and prior on the `beta` and `tests-passed` branches, any authenticated user can create an unlisted topic. These topics, which are not readily available to other users, can take up unnecessary site resources. A patch for this issue is available in the `main` branch of Discourse. There are no known workarounds available.

Severity: nan (CVSS v3 base score: 4.3)
Weakness: CWE-770 \u2014 Allocation of Resources Without Limits or Throttling
The product allocates a reusable resource or group of resources on behalf of an actor without imposing any restrictions on the size or number of resources that can be allocated, in violation of the intended security policy for that actor.
Repository: https://github.com/discourse/discourse
Files changed:
config/locales/server.en.yml
lib/guardian/topic_guardian.rb
lib/topic_creator.rb
spec/lib/guardian/topic_guardian_spec.rb
spec/lib/topic_creator_spec.rb
spec/requests/posts_controller_spec.rb
Commit message: SECURITY: Restrict unlisted topic creation (#19259)



Vulnerable code:

Fixed code:
              unable_to_unlist: "Sorry, you cannot create an unlisted topic."
  alias :can_create_unlisted_topic? :can_toggle_topic_visibility?

    validate_visibility(topic)


    validate_visibility!(topic)
  def validate_visibility(topic)
    if !@opts[:skip_validations] && !topic.visible && !guardian.can_create_unlisted_topic?(topic)
      topic.errors.add(:base, :unable_to_unlist)
    end
  end

  def validate_visibility!(topic)
    validate_visibility(topic)

    rollback_from_errors!(topic) if topic.errors.full_messages.present?
  end

  describe "#can_create_unlisted_topic?" do
    it "returns true for moderators" do
      expect(Guardian.new(moderator).can_create_unlisted_topic?(topic)).to eq(true)
    end

    it "returns true for TL4 users" do
      tl4_user = Fabricate(:user, trust_level: TrustLevel[4])

      expect(Guardian.new(tl4_user).can_create_unlisted_topic?(topic)).to eq(true)
    end

    it "returns false for regular users" do
      expect(Guardian.new(user).can_create_unlisted_topic?(topic)).to eq(false)
    end
  end


    context "when invisible/unlisted" do
      let(:unlisted_attrs) { valid_attrs.merge(visible: false) }

      it "throws an exception for a non-staff user" do
        expect do
          TopicCreator.create(user, Guardian.new(user), unlisted_attrs)
        end.to raise_error(ActiveRecord::Rollback)
      end

      it "is invalid for a non-staff user" do
        expect(TopicCreator.new(user, Guardian.new(user), unlisted_attrs).valid?).to eq(false)
      end

      it "creates unlisted topic for an admin" do
        expect(TopicCreator.create(admin, Guardian.new(admin), unlisted_attrs)).to be_valid
      end

      it "is valid for an admin" do
        expect(TopicCreator.new(admin, Guardian.new(admin), unlisted_attrs).valid?).to eq(true)
      end
    end

      it "creates unlisted topic with admin master key" do
        master_key = Fabricate(:api_key).key

        expect do
          post "/posts.json",
            params: { raw: "this is a test title", title: "this is test body", unlist_topic: true },
            headers: { HTTP_API_USERNAME: admin.username, HTTP_API_KEY: master_key }
        end.to change { Topic.count }.by(1)

        expect(response.status).to eq(200)
        expect(Topic.find(response.parsed_body["topic_id"]).visible).to eq(false)
      end

      it "prevents creation of unlisted topic with non-admin key" do
        user_key = ApiKey.create!(user: user).key

        expect do
          post "/posts.json",
            params: { raw: "this is a test title", title: "this is test body", unlist_topic: true },
            headers: { HTTP_API_USERNAME: user.username, HTTP_API_KEY: user_key }
        end.not_to change { Topic.count }

        expect(response.status).to eq(422)
        expect(response.parsed_body["errors"]).to include(
          I18n.t("activerecord.errors.models.topic.attributes.base.unable_to_unlist")
        )
      end
        expect(topic.visible).to eq(true)
    context "with topic unlisting" do
      context "when logged in as staff" do
        before do
          sign_in(admin)
        end

        it "creates an unlisted topic" do
          expect do
            post "/posts.json", params: {
              raw: "this is the test content",
              title: "this is the test title for the topic",
              unlist_topic: true
            }
          end.to change { Topic.count }.by(1)

          expect(response.status).to eq(200)
          expect(Topic.find(response.parsed_body["topic_id"]).visible).to eq(false)
        end
      end

      context "when logged in as a non-staff user" do
        before do
          sign_in(user)
        end

        it "prevents creation of an unlisted topic" do
          expect do
            post "/posts.json", params: {
              raw: "this is the test content",
              title: "this is the test title for the topic",
              unlist_topic: true
            }
          end.not_to change { Topic.count }

          expect(response.status).to eq(422)
          expect(response.parsed_body["errors"]).to include(
            I18n.t("activerecord.errors.models.topic.attributes.base.unable_to_unlist")
          )
        end
      end
    end