FIX: Pass upload type correctly to uploads#create (#29600)

Prior to Uppy, the `uploads#create` endpoint used to receive a `type` param that indicated the purpose/target of the upload, such as `avatar`, `site_setting` and so on. With the introduction of Uppy, the `type` param became the MIME type of the file being uploaded, and the purpose/target of the upload became a new param called `upload_type`, however the backend could still use the `type` param (which now contains MIME type) as the purpose/target of the upload if `upload_type` is absent.

We technically don't need to send the MIME type over the network, but it seems like it's done by Uppy and we have no control over the `type` param that Uppy includes:

758de8167b/app/assets/javascripts/discourse/app/lib/uppy/uppy-upload.js (L146-L151)

This commit does a couple of things:

1. It amends the `uploads#create` endpoint so it always requires the `upload_type` param and doesn't fallback to `type` if `upload_type` is absent
2. It forces consumers of the `UppyUpload` class (and by extension `UppyImageUploader`) to specify `type` of the upload

Internal topic: t/140945.
This commit is contained in:
Osama Sayegh
2024-11-06 07:00:35 +03:00
committed by GitHub
parent ae721bd0f1
commit 6f8f6a7726
5 changed files with 50 additions and 35 deletions

View File

@ -73,7 +73,7 @@ function lazyMergeConfig(config) {
return mergedConfig; return mergedConfig;
} }
const REQUIRED_CONFIG_KEYS = ["id", "uploadDone"]; const REQUIRED_CONFIG_KEYS = ["id", "uploadDone", "type"];
function validateConfig(config) { function validateConfig(config) {
for (const key of REQUIRED_CONFIG_KEYS) { for (const key of REQUIRED_CONFIG_KEYS) {
if (!config[key]) { if (!config[key]) {

View File

@ -36,7 +36,7 @@ module(
await render(<template> await render(<template>
<Form @mutable={{true}} @data={{data}} as |form|> <Form @mutable={{true}} @data={{data}} as |form|>
<form.Field @name="image_url" @title="Foo" as |field|> <form.Field @name="image_url" @title="Foo" as |field|>
<field.Image /> <field.Image @type="site_setting" />
</form.Field> </form.Field>
</Form> </Form>
</template>); </template>);

View File

@ -9,7 +9,7 @@ module("Integration | Component | uppy-image-uploader", function (hooks) {
test("with image", async function (assert) { test("with image", async function (assert) {
await render(hbs` await render(hbs`
<UppyImageUploader @id="test-uppy-image-uploader" @imageUrl="/images/avatar.png" @placeholderUrl="/not/used.png" /> <UppyImageUploader @type="avatar" @id="test-uppy-image-uploader" @imageUrl="/images/avatar.png" @placeholderUrl="/not/used.png" />
`); `);
assert.strictEqual( assert.strictEqual(
@ -38,7 +38,9 @@ module("Integration | Component | uppy-image-uploader", function (hooks) {
}); });
test("without image", async function (assert) { test("without image", async function (assert) {
await render(hbs`<UppyImageUploader @id="test-uppy-image-uploader" />`); await render(
hbs`<UppyImageUploader @type="site_setting" @id="test-uppy-image-uploader" />`
);
assert.strictEqual( assert.strictEqual(
count(".d-icon-far-image"), count(".d-icon-far-image"),
@ -55,7 +57,7 @@ module("Integration | Component | uppy-image-uploader", function (hooks) {
test("with placeholder", async function (assert) { test("with placeholder", async function (assert) {
await render( await render(
hbs`<UppyImageUploader @id="test-uppy-image-uploader" @placeholderUrl="/images/avatar.png" />` hbs`<UppyImageUploader @type="composer" @id="test-uppy-image-uploader" @placeholderUrl="/images/avatar.png" />`
); );
assert.strictEqual( assert.strictEqual(

View File

@ -32,11 +32,9 @@ class UploadsController < ApplicationController
1.minute.to_i, 1.minute.to_i,
).performed! ).performed!
params.permit(:type, :upload_type) params.require(:upload_type)
raise Discourse::InvalidParameters if params[:type].blank? && params[:upload_type].blank?
# 50 characters ought to be enough for the upload type # 50 characters ought to be enough for the upload type
type = type = params[:upload_type].parameterize(separator: "_")[0..50]
(params[:upload_type].presence || params[:type].presence).parameterize(separator: "_")[0..50]
if type == "avatar" && if type == "avatar" &&
( (

View File

@ -29,7 +29,7 @@ RSpec.describe UploadsController do
post "/uploads.json", post "/uploads.json",
params: { params: {
file: Rack::Test::UploadedFile.new(logo_file), file: Rack::Test::UploadedFile.new(logo_file),
type: "avatar", upload_type: "avatar",
} }
expect(response.status).to eq(200) expect(response.status).to eq(200)
@ -37,20 +37,20 @@ RSpec.describe UploadsController do
post "/uploads.json", post "/uploads.json",
params: { params: {
file: Rack::Test::UploadedFile.new(logo_file), file: Rack::Test::UploadedFile.new(logo_file),
type: "avatar", upload_type: "avatar",
} }
expect(response.status).to eq(429) expect(response.status).to eq(429)
end end
end end
it "expects a type or upload_type" do it "expects upload_type" do
post "/uploads.json", params: { file: logo } post "/uploads.json", params: { file: logo }
expect(response.status).to eq(400) expect(response.status).to eq(400)
post "/uploads.json", post "/uploads.json",
params: { params: {
file: Rack::Test::UploadedFile.new(logo_file), file: Rack::Test::UploadedFile.new(logo_file),
type: "avatar", upload_type: "avatar",
} }
expect(response.status).to eq 200 expect(response.status).to eq 200
post "/uploads.json", post "/uploads.json",
@ -62,7 +62,7 @@ RSpec.describe UploadsController do
end end
it "is successful with an image" do it "is successful with an image" do
post "/uploads.json", params: { file: logo, type: "avatar" } post "/uploads.json", params: { file: logo, upload_type: "avatar" }
expect(response.status).to eq 200 expect(response.status).to eq 200
expect(response.parsed_body["id"]).to be_present expect(response.parsed_body["id"]).to be_present
expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(1) expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(1)
@ -74,14 +74,19 @@ RSpec.describe UploadsController do
upload = UploadCreator.new(logo_file, "logo.png").create_for(-1) upload = UploadCreator.new(logo_file, "logo.png").create_for(-1)
logo = Rack::Test::UploadedFile.new(file_from_fixtures("logo.png")) logo = Rack::Test::UploadedFile.new(file_from_fixtures("logo.png"))
post "/uploads.json", params: { file: logo, type: "site_setting", for_site_setting: "true" } post "/uploads.json",
params: {
file: logo,
upload_type: "site_setting",
for_site_setting: "true",
}
expect(response.status).to eq 200 expect(response.status).to eq 200
expect(response.parsed_body["url"]).to eq(upload.url) expect(response.parsed_body["url"]).to eq(upload.url)
end end
it "returns cdn url" do it "returns cdn url" do
set_cdn_url "https://awesome.com" set_cdn_url "https://awesome.com"
post "/uploads.json", params: { file: logo, type: "composer" } post "/uploads.json", params: { file: logo, upload_type: "composer" }
expect(response.status).to eq 200 expect(response.status).to eq 200
expect(response.parsed_body["url"]).to start_with("https://awesome.com/uploads/default/") expect(response.parsed_body["url"]).to start_with("https://awesome.com/uploads/default/")
end end
@ -89,7 +94,7 @@ RSpec.describe UploadsController do
it "is successful with an attachment" do it "is successful with an attachment" do
SiteSetting.authorized_extensions = "*" SiteSetting.authorized_extensions = "*"
post "/uploads.json", params: { file: text_file, type: "composer" } post "/uploads.json", params: { file: text_file, upload_type: "composer" }
expect(response.status).to eq 200 expect(response.status).to eq 200
expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0) expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0)
@ -109,7 +114,7 @@ RSpec.describe UploadsController do
post "/uploads.json", post "/uploads.json",
params: { params: {
url: url, url: url,
type: "avatar", upload_type: "avatar",
}, },
headers: { headers: {
HTTP_API_KEY: api_key, HTTP_API_KEY: api_key,
@ -127,7 +132,12 @@ RSpec.describe UploadsController do
it "correctly sets retain_hours for admins" do it "correctly sets retain_hours for admins" do
sign_in(Fabricate(:admin)) sign_in(Fabricate(:admin))
post "/uploads.json", params: { file: logo, retain_hours: 100, type: "profile_background" } post "/uploads.json",
params: {
file: logo,
retain_hours: 100,
upload_type: "profile_background",
}
id = response.parsed_body["id"] id = response.parsed_body["id"]
expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0) expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0)
@ -135,7 +145,7 @@ RSpec.describe UploadsController do
end end
it "requires a file" do it "requires a file" do
post "/uploads.json", params: { type: "composer" } post "/uploads.json", params: { upload_type: "composer" }
expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0) expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0)
message = response.parsed_body message = response.parsed_body
@ -147,7 +157,7 @@ RSpec.describe UploadsController do
SiteSetting.authorized_extensions = "*" SiteSetting.authorized_extensions = "*"
SiteSetting.max_attachment_size_kb = 1 SiteSetting.max_attachment_size_kb = 1
post "/uploads.json", params: { file: text_file, type: "avatar" } post "/uploads.json", params: { file: text_file, upload_type: "avatar" }
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0) expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0)
@ -159,18 +169,18 @@ RSpec.describe UploadsController do
it "ensures user belongs to uploaded_avatars_allowed_groups when uploading an avatar" do it "ensures user belongs to uploaded_avatars_allowed_groups when uploading an avatar" do
SiteSetting.uploaded_avatars_allowed_groups = "13" SiteSetting.uploaded_avatars_allowed_groups = "13"
post "/uploads.json", params: { file: logo, type: "avatar" } post "/uploads.json", params: { file: logo, upload_type: "avatar" }
expect(response.status).to eq(422) expect(response.status).to eq(422)
user.change_trust_level!(TrustLevel[3]) user.change_trust_level!(TrustLevel[3])
post "/uploads.json", params: { file: logo, type: "avatar" } post "/uploads.json", params: { file: logo, upload_type: "avatar" }
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
it "ensures discourse_connect_overrides_avatar is not enabled when uploading an avatar" do it "ensures discourse_connect_overrides_avatar is not enabled when uploading an avatar" do
SiteSetting.discourse_connect_overrides_avatar = true SiteSetting.discourse_connect_overrides_avatar = true
post "/uploads.json", params: { file: logo, type: "avatar" } post "/uploads.json", params: { file: logo, upload_type: "avatar" }
expect(response.status).to eq(422) expect(response.status).to eq(422)
end end
@ -182,7 +192,7 @@ RSpec.describe UploadsController do
post "/uploads.json", post "/uploads.json",
params: { params: {
file: text_file, file: text_file,
type: "composer", upload_type: "composer",
for_private_message: "true", for_private_message: "true",
} }
@ -195,7 +205,12 @@ RSpec.describe UploadsController do
SiteSetting.authorized_extensions = "" SiteSetting.authorized_extensions = ""
user.update!(admin: true) user.update!(admin: true)
post "/uploads.json", params: { file: logo, type: "site_setting", for_site_setting: "true" } post "/uploads.json",
params: {
file: logo,
upload_type: "site_setting",
for_site_setting: "true",
}
expect(response.status).to eq(200) expect(response.status).to eq(200)
id = response.parsed_body["id"] id = response.parsed_body["id"]
@ -211,7 +226,7 @@ RSpec.describe UploadsController do
SiteSetting.authorized_extensions_for_staff = "*" SiteSetting.authorized_extensions_for_staff = "*"
user.update_columns(moderator: true) user.update_columns(moderator: true)
post "/uploads.json", params: { file: text_file, type: "composer" } post "/uploads.json", params: { file: text_file, upload_type: "composer" }
expect(response.status).to eq(200) expect(response.status).to eq(200)
data = response.parsed_body data = response.parsed_body
@ -222,14 +237,14 @@ RSpec.describe UploadsController do
SiteSetting.authorized_extensions = "" SiteSetting.authorized_extensions = ""
SiteSetting.authorized_extensions_for_staff = "*" SiteSetting.authorized_extensions_for_staff = "*"
post "/uploads.json", params: { file: text_file, type: "composer" } post "/uploads.json", params: { file: text_file, upload_type: "composer" }
data = response.parsed_body data = response.parsed_body
expect(data["errors"].first).to eq(I18n.t("upload.unauthorized", authorized_extensions: "")) expect(data["errors"].first).to eq(I18n.t("upload.unauthorized", authorized_extensions: ""))
end end
it "returns an error when it could not determine the dimensions of an image" do it "returns an error when it could not determine the dimensions of an image" do
post "/uploads.json", params: { file: fake_jpg, type: "composer" } post "/uploads.json", params: { file: fake_jpg, upload_type: "composer" }
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0) expect(Jobs::CreateAvatarThumbnails.jobs.size).to eq(0)
@ -247,7 +262,7 @@ RSpec.describe UploadsController do
SiteSetting.authorized_extensions = "*" SiteSetting.authorized_extensions = "*"
SiteSetting.max_attachment_size_kb = 1 SiteSetting.max_attachment_size_kb = 1
post "/uploads.json", params: { file: text_file, type: "composer" } post "/uploads.json", params: { file: text_file, upload_type: "composer" }
expect(response.status).to eq(422) expect(response.status).to eq(422)
errors = response.parsed_body["errors"] errors = response.parsed_body["errors"]
@ -260,7 +275,7 @@ RSpec.describe UploadsController do
SiteSetting.authorized_extensions = "*" SiteSetting.authorized_extensions = "*"
SiteSetting.system_user_max_attachment_size_kb = 421_730 SiteSetting.system_user_max_attachment_size_kb = 421_730
post "/uploads.json", params: { file: text_file, type: "composer" } post "/uploads.json", params: { file: text_file, upload_type: "composer" }
expect(response.status).to eq(200) expect(response.status).to eq(200)
end end
@ -269,7 +284,7 @@ RSpec.describe UploadsController do
SiteSetting.max_attachment_size_kb = 1 SiteSetting.max_attachment_size_kb = 1
SiteSetting.system_user_max_attachment_size_kb = 1 SiteSetting.system_user_max_attachment_size_kb = 1
post "/uploads.json", params: { file: text_file, type: "composer" } post "/uploads.json", params: { file: text_file, upload_type: "composer" }
expect(response.status).to eq(422) expect(response.status).to eq(422)
errors = response.parsed_body["errors"] errors = response.parsed_body["errors"]
@ -283,7 +298,7 @@ RSpec.describe UploadsController do
SiteSetting.max_attachment_size_kb = 10 SiteSetting.max_attachment_size_kb = 10
SiteSetting.system_user_max_attachment_size_kb = 5 SiteSetting.system_user_max_attachment_size_kb = 5
post "/uploads.json", params: { file: text_file, type: "composer" } post "/uploads.json", params: { file: text_file, upload_type: "composer" }
expect(response.status).to eq(422) expect(response.status).to eq(422)
errors = response.parsed_body["errors"] errors = response.parsed_body["errors"]
@ -297,7 +312,7 @@ RSpec.describe UploadsController do
SiteSetting.max_attachment_size_kb = 1 SiteSetting.max_attachment_size_kb = 1
SiteSetting.system_user_max_attachment_size_kb = 10 SiteSetting.system_user_max_attachment_size_kb = 10
post "/uploads.json", params: { file: text_file, type: "composer" } post "/uploads.json", params: { file: text_file, upload_type: "composer" }
expect(response.status).to eq(422) expect(response.status).to eq(422)
errors = response.parsed_body["errors"] errors = response.parsed_body["errors"]
@ -313,7 +328,7 @@ RSpec.describe UploadsController do
SiteSetting.authorized_extensions = "*" SiteSetting.authorized_extensions = "*"
sign_in(user) sign_in(user)
post "/uploads.json", params: { file: fake_logo, type: "composer" } post "/uploads.json", params: { file: fake_logo, upload_type: "composer" }
expect(response.status).to eq(200) expect(response.status).to eq(200)