FEATURE: New site setting for additional allowed filetypes for staff (#5364)
* FEATURE: New site setting for additional allowed filetypes for staff * Problematic variable name * feedback * small issues * fix indentation * failing tests * Remove message bus and fix minor issues * Missed this message bus
This commit is contained in:
parent
afa2b36842
commit
f3815cd785
|
@ -203,7 +203,7 @@ export function validateUploadedFile(file, opts) {
|
||||||
|
|
||||||
// check that the uploaded file is authorized
|
// check that the uploaded file is authorized
|
||||||
if (opts.allowStaffToUploadAnyFileInPm && opts.isPrivateMessage) {
|
if (opts.allowStaffToUploadAnyFileInPm && opts.isPrivateMessage) {
|
||||||
if (Discourse.User.current("staff")) {
|
if (Discourse.User.currentProp('staff')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,16 +239,28 @@ export function validateUploadedFile(file, opts) {
|
||||||
|
|
||||||
const IMAGES_EXTENSIONS_REGEX = /(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)/i;
|
const IMAGES_EXTENSIONS_REGEX = /(png|jpe?g|gif|bmp|tiff?|svg|webp|ico)/i;
|
||||||
|
|
||||||
|
function extensionsToArray(exts) {
|
||||||
|
return exts.toLowerCase()
|
||||||
|
.replace(/[\s\.]+/g, "")
|
||||||
|
.split("|")
|
||||||
|
.filter(ext => ext.indexOf("*") === -1);
|
||||||
|
}
|
||||||
|
|
||||||
function extensions() {
|
function extensions() {
|
||||||
return Discourse.SiteSettings.authorized_extensions
|
return extensionsToArray(Discourse.SiteSettings.authorized_extensions);
|
||||||
.toLowerCase()
|
}
|
||||||
.replace(/[\s\.]+/g, "")
|
|
||||||
.split("|")
|
function staffExtensions() {
|
||||||
.filter(ext => ext.indexOf("*") === -1);
|
return extensionsToArray(Discourse.SiteSettings.authorized_extensions_for_staff);
|
||||||
}
|
}
|
||||||
|
|
||||||
function imagesExtensions() {
|
function imagesExtensions() {
|
||||||
return extensions().filter(ext => IMAGES_EXTENSIONS_REGEX.test(ext));
|
let exts = extensions().filter(ext => IMAGES_EXTENSIONS_REGEX.test(ext));
|
||||||
|
if (Discourse.User.currentProp('staff')) {
|
||||||
|
const staffExts = staffExtensions().filter(ext => IMAGES_EXTENSIONS_REGEX.test(ext));
|
||||||
|
exts = _.union(exts, staffExts);
|
||||||
|
}
|
||||||
|
return exts;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extensionsRegex() {
|
function extensionsRegex() {
|
||||||
|
@ -259,7 +271,14 @@ function imagesExtensionsRegex() {
|
||||||
return new RegExp("\\.(" + imagesExtensions().join("|") + ")$", "i");
|
return new RegExp("\\.(" + imagesExtensions().join("|") + ")$", "i");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function staffExtensionsRegex() {
|
||||||
|
return new RegExp("\\.(" + staffExtensions().join("|") + ")$", "i");
|
||||||
|
}
|
||||||
|
|
||||||
function isAuthorizedFile(fileName) {
|
function isAuthorizedFile(fileName) {
|
||||||
|
if (Discourse.User.currentProp('staff') && staffExtensionsRegex().test(fileName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return extensionsRegex().test(fileName);
|
return extensionsRegex().test(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,7 +287,8 @@ function isAuthorizedImage(fileName){
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authorizedExtensions() {
|
export function authorizedExtensions() {
|
||||||
return authorizesAllExtensions() ? "*" : extensions().join(", ");
|
const exts = Discourse.User.currentProp('staff') ? [...extensions(), ...staffExtensions()] : extensions();
|
||||||
|
return exts.filter(ext => ext.length > 0).join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authorizedImagesExtensions() {
|
export function authorizedImagesExtensions() {
|
||||||
|
@ -276,7 +296,9 @@ export function authorizedImagesExtensions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authorizesAllExtensions() {
|
export function authorizesAllExtensions() {
|
||||||
return Discourse.SiteSettings.authorized_extensions.indexOf("*") >= 0;
|
return Discourse.SiteSettings.authorized_extensions.indexOf("*") >= 0 || (
|
||||||
|
Discourse.SiteSettings.authorized_extensions_for_staff.indexOf("*") >= 0 &&
|
||||||
|
Discourse.User.currentProp('staff'));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function authorizesOneOrMoreExtensions() {
|
export function authorizesOneOrMoreExtensions() {
|
||||||
|
@ -322,7 +344,7 @@ export function allowsImages() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function allowsAttachments() {
|
export function allowsAttachments() {
|
||||||
return authorizesAllExtensions() || extensions().length > imagesExtensions().length;
|
return authorizesAllExtensions() || authorizedExtensions().split(", ").length > imagesExtensions().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function uploadLocation(url) {
|
export function uploadLocation(url) {
|
||||||
|
|
|
@ -1334,6 +1334,7 @@ en:
|
||||||
max_image_size_kb: "The maximum image upload size in kB. This must be configured in nginx (client_max_body_size) / apache or proxy as well."
|
max_image_size_kb: "The maximum image upload size in kB. This must be configured in nginx (client_max_body_size) / apache or proxy as well."
|
||||||
max_attachment_size_kb: "The maximum attachment files upload size in kB. This must be configured in nginx (client_max_body_size) / apache or proxy as well."
|
max_attachment_size_kb: "The maximum attachment files upload size in kB. This must be configured in nginx (client_max_body_size) / apache or proxy as well."
|
||||||
authorized_extensions: "A list of file extensions allowed for upload (use '*' to enable all file types)"
|
authorized_extensions: "A list of file extensions allowed for upload (use '*' to enable all file types)"
|
||||||
|
authorized_extensions_for_staff: "A list of file extensions allowed for upload for staff users in addition to the list defined in the `authorized_extensions` site setting. (use '*' to enable all file types)"
|
||||||
theme_authorized_extensions: "A list of file extensions allowed for theme uploads (use '*' to enable all file types)"
|
theme_authorized_extensions: "A list of file extensions allowed for theme uploads (use '*' to enable all file types)"
|
||||||
max_similar_results: "How many similar topics to show above the editor when composing a new topic. Comparison is based on title and body."
|
max_similar_results: "How many similar topics to show above the editor when composing a new topic. Comparison is based on title and body."
|
||||||
|
|
||||||
|
|
|
@ -796,6 +796,11 @@ files:
|
||||||
default: 'jpg|jpeg|png|gif'
|
default: 'jpg|jpeg|png|gif'
|
||||||
refresh: true
|
refresh: true
|
||||||
type: list
|
type: list
|
||||||
|
authorized_extensions_for_staff:
|
||||||
|
client: true
|
||||||
|
default: ''
|
||||||
|
refresh: true
|
||||||
|
type: list
|
||||||
crawl_images:
|
crawl_images:
|
||||||
default: true
|
default: true
|
||||||
max_image_width:
|
max_image_width:
|
||||||
|
|
|
@ -29,11 +29,11 @@ class Validators::UploadValidator < ActiveModel::Validator
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_authorized?(upload, extension)
|
def is_authorized?(upload, extension)
|
||||||
authorized_extensions(upload, extension, authorized_uploads(upload))
|
extension_authorized?(upload, extension, authorized_extensions(upload))
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorized_image_extension(upload, extension)
|
def authorized_image_extension(upload, extension)
|
||||||
authorized_extensions(upload, extension, authorized_images(upload))
|
extension_authorized?(upload, extension, authorized_images(upload))
|
||||||
end
|
end
|
||||||
|
|
||||||
def maximum_image_file_size(upload)
|
def maximum_image_file_size(upload)
|
||||||
|
@ -41,7 +41,7 @@ class Validators::UploadValidator < ActiveModel::Validator
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorized_attachment_extension(upload, extension)
|
def authorized_attachment_extension(upload, extension)
|
||||||
authorized_extensions(upload, extension, authorized_attachments(upload))
|
extension_authorized?(upload, extension, authorized_attachments(upload))
|
||||||
end
|
end
|
||||||
|
|
||||||
def maximum_attachment_file_size(upload)
|
def maximum_attachment_file_size(upload)
|
||||||
|
@ -50,38 +50,50 @@ class Validators::UploadValidator < ActiveModel::Validator
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def authorized_uploads(upload)
|
def extensions_to_set(exts)
|
||||||
authorized_uploads = Set.new
|
extensions = Set.new
|
||||||
|
|
||||||
extensions = upload.for_theme ? SiteSetting.theme_authorized_extensions : SiteSetting.authorized_extensions
|
exts
|
||||||
|
|
||||||
extensions
|
|
||||||
.gsub(/[\s\.]+/, "")
|
.gsub(/[\s\.]+/, "")
|
||||||
.downcase
|
.downcase
|
||||||
.split("|")
|
.split("|")
|
||||||
.each { |extension| authorized_uploads << extension unless extension.include?("*") }
|
.each { |extension| extensions << extension unless extension.include?("*") }
|
||||||
|
|
||||||
authorized_uploads
|
extensions
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorized_extensions(upload)
|
||||||
|
extensions = upload.for_theme ? SiteSetting.theme_authorized_extensions : SiteSetting.authorized_extensions
|
||||||
|
extensions_to_set(extensions)
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorized_images(upload)
|
def authorized_images(upload)
|
||||||
authorized_uploads(upload) & FileHelper.images
|
authorized_extensions(upload) & FileHelper.images
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorized_attachments(upload)
|
def authorized_attachments(upload)
|
||||||
authorized_uploads(upload) - FileHelper.images
|
authorized_extensions(upload) - FileHelper.images
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorizes_all_extensions?(upload)
|
def authorizes_all_extensions?(upload)
|
||||||
|
if upload.user&.staff?
|
||||||
|
return true if SiteSetting.authorized_extensions_for_staff.include?("*")
|
||||||
|
end
|
||||||
extensions = upload.for_theme ? SiteSetting.theme_authorized_extensions : SiteSetting.authorized_extensions
|
extensions = upload.for_theme ? SiteSetting.theme_authorized_extensions : SiteSetting.authorized_extensions
|
||||||
extensions.include?("*")
|
extensions.include?("*")
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorized_extensions(upload, extension, extensions)
|
def extension_authorized?(upload, extension, extensions)
|
||||||
return true if authorizes_all_extensions?(upload)
|
return true if authorizes_all_extensions?(upload)
|
||||||
|
|
||||||
|
staff_extensions = Set.new
|
||||||
|
if upload.user&.staff?
|
||||||
|
staff_extensions = extensions_to_set(SiteSetting.authorized_extensions_for_staff)
|
||||||
|
return true if staff_extensions.include?(extension.downcase)
|
||||||
|
end
|
||||||
|
|
||||||
unless authorized = extensions.include?(extension.downcase)
|
unless authorized = extensions.include?(extension.downcase)
|
||||||
message = I18n.t("upload.unauthorized", authorized_extensions: extensions.to_a.join(", "))
|
message = I18n.t("upload.unauthorized", authorized_extensions: (extensions | staff_extensions).to_a.join(", "))
|
||||||
upload.errors.add(:original_filename, message)
|
upload.errors.add(:original_filename, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,36 @@ describe UploadsController do
|
||||||
expect(id).to be
|
expect(id).to be
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'respects `authorized_extensions_for_staff` setting when staff upload file' do
|
||||||
|
SiteSetting.authorized_extensions = ""
|
||||||
|
SiteSetting.authorized_extensions_for_staff = "*"
|
||||||
|
@user.update_columns(moderator: true)
|
||||||
|
|
||||||
|
post :create, params: {
|
||||||
|
file: text_file,
|
||||||
|
type: "composer",
|
||||||
|
format: :json
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response).to be_success
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data["id"]).to be
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'ignores `authorized_extensions_for_staff` setting when non-staff upload file' do
|
||||||
|
SiteSetting.authorized_extensions = ""
|
||||||
|
SiteSetting.authorized_extensions_for_staff = "*"
|
||||||
|
|
||||||
|
post :create, params: {
|
||||||
|
file: text_file,
|
||||||
|
type: "composer",
|
||||||
|
format: :json
|
||||||
|
}
|
||||||
|
|
||||||
|
data = JSON.parse(response.body)
|
||||||
|
expect(data["errors"].first).to eq(I18n.t("upload.unauthorized", authorized_extensions: ''))
|
||||||
|
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
|
||||||
Jobs.expects(:enqueue).with(:create_avatar_thumbnails, anything).never
|
Jobs.expects(:enqueue).with(:create_avatar_thumbnails, anything).never
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@ Discourse.SiteSettingsOriginal = {
|
||||||
"autohighlight_all_code":false,
|
"autohighlight_all_code":false,
|
||||||
"email_in":false,
|
"email_in":false,
|
||||||
"authorized_extensions":".jpg|.jpeg|.png|.gif|.svg|.txt|.ico|.yml",
|
"authorized_extensions":".jpg|.jpeg|.png|.gif|.svg|.txt|.ico|.yml",
|
||||||
|
"authorized_extensions_for_staff": "",
|
||||||
"max_image_width":690,
|
"max_image_width":690,
|
||||||
"max_image_height":500,
|
"max_image_height":500,
|
||||||
"allow_profile_backgrounds":true,
|
"allow_profile_backgrounds":true,
|
||||||
|
|
Loading…
Reference in New Issue