diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 3a44119f9..6e2705ea6 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -33,6 +33,9 @@ ENV \ ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM="" \ ATTACHMENTS_UPLOAD_MIME_TYPES="" \ ATTACHMENTS_UPLOAD_MAX_SIZE=0 \ + AVATARS_UPLOAD_EXTERNAL_PROGRAM="" \ + AVATARS_UPLOAD_MIME_TYPES="" \ + AVATARS_UPLOAD_MAX_SIZE=0 \ MAX_IMAGE_PIXEL="" \ IMAGE_COMPRESS_RATIO="" \ NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE="" \ diff --git a/Dockerfile b/Dockerfile index 921ac3ee9..09a9ddbe9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,6 +36,9 @@ ENV BUILD_DEPS="apt-utils libarchive-tools gnupg gosu wget curl bzip2 g++ build- ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM="" \ ATTACHMENTS_UPLOAD_MIME_TYPES="" \ ATTACHMENTS_UPLOAD_MAX_SIZE=0 \ + AVATARS_UPLOAD_EXTERNAL_PROGRAM="" \ + AVATARS_UPLOAD_MIME_TYPES="" \ + AVATARS_UPLOAD_MAX_SIZE=0 \ RICHER_CARD_COMMENT_EDITOR=false \ CARD_OPENED_WEBHOOK_ENABLED=false \ MAX_IMAGE_PIXEL="" \ diff --git a/client/components/users/userAvatar.js b/client/components/users/userAvatar.js index 4707bb2ee..6402cdcef 100644 --- a/client/components/users/userAvatar.js +++ b/client/components/users/userAvatar.js @@ -3,7 +3,6 @@ import Avatars from '/models/avatars'; import Users from '/models/users'; import Org from '/models/org'; import Team from '/models/team'; -import { formatFleURL } from 'meteor/ostrio:files/lib'; Template.userAvatar.helpers({ userData() { @@ -218,13 +217,6 @@ BlazeComponent.extendComponent({ }, false, ); - uploader.on('uploaded', (error, fileRef) => { - if (!error) { - self.setAvatar( - `${formatFleURL(fileRef)}?auth=false&brokenIsFine=true`, - ); - } - }); uploader.on('error', (error, fileData) => { self.setError(error.reason); }); @@ -238,8 +230,9 @@ BlazeComponent.extendComponent({ 'click .js-select-initials'() { this.setAvatar(''); }, - 'click .js-delete-avatar'() { + 'click .js-delete-avatar'(event) { Avatars.remove(this.currentData()._id); + event.stopPropagation(); }, }, ]; diff --git a/docker-compose.yml b/docker-compose.yml index 167c64c93..72eb549a6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -271,6 +271,11 @@ services: #-ATTACHMENTS_UPLOAD_MIME_TYPES=image/*,text/* #-ATTACHMENTS_UPLOAD_MAX_SIZE=5000000 #--------------------------------------------------------------- + # ==== Allow configuration to validate uploaded avatars ==== + #-AVATARS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file} + #-AVATARS_UPLOAD_MIME_TYPES=image/* + #-AVATARS_UPLOAD_MAX_SIZE=500000 + #--------------------------------------------------------------- # ==== Allow to shrink attached/pasted image ==== # https://github.com/wekan/wekan/pull/2544 #-MAX_IMAGE_PIXEL=1024 diff --git a/models/attachments.js b/models/attachments.js index 8ae852248..3816a407a 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -1,15 +1,12 @@ import { Meteor } from 'meteor/meteor'; import { FilesCollection } from 'meteor/ostrio:files'; -import { exec } from 'node:child_process'; -import { promisify } from 'node:util'; +import { isFileValid } from './fileValidation'; import { createBucket } from './lib/grid/createBucket'; import fs from 'fs'; -import FileType from 'file-type'; import path from 'path'; import { AttachmentStoreStrategyFilesystem, AttachmentStoreStrategyGridFs} from '/models/lib/attachmentStoreStrategy'; import FileStoreStrategyFactory, {moveToStorage, rename, STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS} from '/models/lib/fileStoreStrategy'; -let asyncExec; let attachmentUploadExternalProgram; let attachmentUploadMimeTypes = []; let attachmentUploadSize = 0; @@ -17,7 +14,6 @@ let attachmentBucket; let storagePath; if (Meteor.isServer) { - asyncExec = promisify(exec); attachmentBucket = createBucket('attachments'); if (process.env.ATTACHMENTS_UPLOAD_MIME_TYPES) { @@ -155,34 +151,7 @@ if (Meteor.isServer) { check(fileObjId, String); const fileObj = Attachments.findOne({_id: fileObjId}); - let isValid = true; - - if (attachmentUploadMimeTypes.length) { - const mimeTypeResult = Promise.await(FileType.fromFile(fileObj.path)); - - const mimeType = (mimeTypeResult ? mimeTypeResult.mime : fileObj.type); - const baseMimeType = mimeType.split('/', 1)[0]; - - isValid = attachmentUploadMimeTypes.includes(mimeType) || attachmentUploadMimeTypes.includes(baseMimeType + '/*') || attachmentUploadMimeTypes.includes('*'); - - if (!isValid) { - console.log("Validation of uploaded file failed: file " + fileObj.path + " - mimetype " + mimeType); - } - } - - if (attachmentUploadSize && fileObj.size > attachmentUploadSize) { - console.log("Validation of uploaded file failed: file " + fileObj.path + " - size " + fileObj.size); - isValid = false; - } - - if (isValid && attachmentUploadExternalProgram) { - Promise.await(asyncExec(attachmentUploadExternalProgram.replace("{file}", '"' + fileObj.path + '"'))); - isValid = fs.existsSync(fileObj.path); - - if (!isValid) { - console.log("Validation of uploaded file failed: file " + fileObj.path + " has been deleted externally"); - } - } + const isValid = Promise.await(isFileValid(fileObj, attachmentUploadMimeTypes, attachmentUploadSize, attachmentUploadExternalProgram)); if (!isValid) { Attachments.remove(fileObjId); @@ -192,12 +161,11 @@ if (Meteor.isServer) { check(fileObjId, String); check(storageDestination, String); - Meteor.defer(() => Meteor.call('validateAttachment', fileObjId)); + Meteor.call('validateAttachment', fileObjId); const fileObj = Attachments.findOne({_id: fileObjId}); if (fileObj) { - console.debug("Validation of uploaded file completed: file " + fileObj.path + " - storage destination " + storageDestination); Meteor.defer(() => Meteor.call('moveAttachmentToStorage', fileObjId, storageDestination)); } }, diff --git a/models/avatars.js b/models/avatars.js index 49fd4c8af..bc95bc1b6 100644 --- a/models/avatars.js +++ b/models/avatars.js @@ -1,13 +1,40 @@ import { Meteor } from 'meteor/meteor'; import { FilesCollection } from 'meteor/ostrio:files'; +import { formatFleURL } from 'meteor/ostrio:files/lib'; +import { isFileValid } from './fileValidation'; import { createBucket } from './lib/grid/createBucket'; import fs from 'fs'; import path from 'path'; -import FileStoreStrategyFactory, { FileStoreStrategyFilesystem, FileStoreStrategyGridFs} from '/models/lib/fileStoreStrategy'; +import FileStoreStrategyFactory, { FileStoreStrategyFilesystem, FileStoreStrategyGridFs, STORAGE_NAME_FILESYSTEM } from '/models/lib/fileStoreStrategy'; +let avatarsUploadExternalProgram; +let avatarsUploadMimeTypes = []; +let avatarsUploadSize = 72000; let avatarsBucket; let storagePath; + if (Meteor.isServer) { + if (process.env.AVATARS_UPLOAD_MIME_TYPES) { + avatarsUploadMimeTypes = process.env.AVATARS_UPLOAD_MIME_TYPES.split(','); + avatarsUploadMimeTypes = avatarsUploadMimeTypes.map(value => value.trim()); + } + + if (process.env.AVATARS_UPLOAD_MAX_SIZE) { + avatarsUploadSize = parseInt(process.env.AVATARS_UPLOAD_MAX_SIZE); + + if (isNaN(avatarsUploadSize)) { + avatarsUploadSize = 0 + } + } + + if (process.env.AVATARS_UPLOAD_EXTERNAL_PROGRAM) { + avatarsUploadExternalProgram = process.env.AVATARS_UPLOAD_EXTERNAL_PROGRAM; + + if (!avatarsUploadExternalProgram.includes("{file}")) { + avatarsUploadExternalProgram = undefined; + } + } + avatarsBucket = createBucket('avatars'); storagePath = path.join(process.env.WRITABLE_PATH, 'avatars'); } @@ -23,7 +50,7 @@ Avatars = new FilesCollection({ return ret; }, onBeforeUpload(file) { - if (file.size <= 72000 && file.type.startsWith('image/')) { + if (file.size <= avatarsUploadSize && file.type.startsWith('image/')) { return true; } return 'avatar-too-big'; @@ -31,14 +58,32 @@ Avatars = new FilesCollection({ onAfterUpload(fileObj) { // current storage is the filesystem, update object and database Object.keys(fileObj.versions).forEach(versionName => { - fileObj.versions[versionName].storage = "fs"; + fileObj.versions[versionName].storage = STORAGE_NAME_FILESYSTEM; }); - Avatars.update({ _id: fileObj._id }, { $set: { "versions" : fileObj.versions } }); + + Avatars.update({ _id: fileObj._id }, { $set: { "versions": fileObj.versions } }); + + const isValid = Promise.await(isFileValid(fileObj, avatarsUploadMimeTypes, avatarsUploadSize, avatarsUploadExternalProgram)); + + if (isValid) { + Users.findOne(fileObj.userId).setAvatarUrl(`${formatFleURL(fileObj)}?auth=false&brokenIsFine=true`); + } else { + Avatars.remove(fileObj._id); + } }, interceptDownload(http, fileObj, versionName) { const ret = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName).interceptDownload(http, this.cacheControl); return ret; }, + onBeforeRemove(files) { + files.forEach(fileObj => { + if (fileObj.userId) { + Users.findOne(fileObj.userId).setAvatarUrl(''); + } + }); + + return true; + }, onAfterRemove(files) { files.forEach(fileObj => { Object.keys(fileObj.versions).forEach(versionName => { diff --git a/models/fileValidation.js b/models/fileValidation.js new file mode 100644 index 000000000..5fa473e7c --- /dev/null +++ b/models/fileValidation.js @@ -0,0 +1,48 @@ +import { Meteor } from 'meteor/meteor'; +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; +import fs from 'fs'; +import FileType from 'file-type'; + +let asyncExec; + +if (Meteor.isServer) { + asyncExec = promisify(exec); +} + +export async function isFileValid(fileObj, mimeTypesAllowed, sizeAllowed, externalCommandLine) { + let isValid = true; + + if (mimeTypesAllowed.length) { + const mimeTypeResult = await FileType.fromFile(fileObj.path); + + const mimeType = (mimeTypeResult ? mimeTypeResult.mime : fileObj.type); + const baseMimeType = mimeType.split('/', 1)[0]; + + isValid = mimeTypesAllowed.includes(mimeType) || mimeTypesAllowed.includes(baseMimeType + '/*') || mimeTypesAllowed.includes('*'); + + if (!isValid) { + console.log("Validation of uploaded file failed: file " + fileObj.path + " - mimetype " + mimeType); + } + } + + if (isValid && sizeAllowed && fileObj.size > sizeAllowed) { + console.log("Validation of uploaded file failed: file " + fileObj.path + " - size " + fileObj.size); + isValid = false; + } + + if (isValid && externalCommandLine) { + await asyncExec(externalCommandLine.replace("{file}", '"' + fileObj.path + '"')); + isValid = fs.existsSync(fileObj.path); + + if (!isValid) { + console.log("Validation of uploaded file failed: file " + fileObj.path + " has been deleted externally"); + } + } + + if (isValid) { + console.debug("Validation of uploaded file successful: file " + fileObj.path); + } + + return isValid; +} diff --git a/releases/virtualbox/start-wekan.sh b/releases/virtualbox/start-wekan.sh index d8a764ebf..185c0f997 100755 --- a/releases/virtualbox/start-wekan.sh +++ b/releases/virtualbox/start-wekan.sh @@ -53,6 +53,11 @@ #export ATTACHMENTS_UPLOAD_MIME_TYPES="image/*,text/*" #export ATTACHMENTS_UPLOAD_MAX_SIZE=5000000 #--------------------------------------------------------------- + # ==== Allow configuration to validate uploaded avatars ==== + #export AVATARS_UPLOAD_EXTERNAL_PROGRAM="/usr/local/bin/avscan {file}" + #export AVATARS_UPLOAD_MIME_TYPES="image/*" + #export AVATARS_UPLOAD_MAX_SIZE=500000 + #--------------------------------------------------------------- # ==== RICH TEXT EDITOR IN CARD COMMENTS ==== # https://github.com/wekan/wekan/pull/2560 export RICHER_CARD_COMMENT_EDITOR=false diff --git a/snap-src/bin/config b/snap-src/bin/config index 76e91660d..aabfe22bd 100755 --- a/snap-src/bin/config +++ b/snap-src/bin/config @@ -3,7 +3,7 @@ # All supported keys are defined here together with descriptions and default values # list of supported keys -keys="DEBUG MONGO_LOG_DESTINATION MONGO_URL MONGODB_BIND_UNIX_SOCKET MONGO_URL MONGODB_BIND_IP MONGODB_PORT MAIL_URL MAIL_FROM MAIL_SERVICE MAIL_SERVICE_USER MAIL_SERVICE_PASSWORD ROOT_URL PORT DISABLE_MONGODB CADDY_ENABLED CADDY_BIND_PORT WITH_API RICHER_CARD_COMMENT_EDITOR CARD_OPENED_WEBHOOK_ENABLED ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM ATTACHMENTS_UPLOAD_MIME_TYPES ATTACHMENTS_UPLOAD_MAX_SIZE MAX_IMAGE_PIXEL IMAGE_COMPRESS_RATIO BIGEVENTS_PATTERN NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE NOTIFY_DUE_DAYS_BEFORE_AND_AFTER NOTIFY_DUE_AT_HOUR_OF_DAY EMAIL_NOTIFICATION_TIMEOUT CORS CORS_ALLOW_HEADERS CORS_EXPOSE_HEADERS MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_ENABLED OIDC_REDIRECTION_ENABLED OAUTH2_CA_CERT OAUTH2_LOGIN_STYLE OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT OAUTH2_ID_MAP OAUTH2_USERNAME_MAP OAUTH2_FULLNAME_MAP OAUTH2_ID_TOKEN_WHITELIST_FIELDS OAUTH2_EMAIL_MAP OAUTH2_REQUEST_PERMISSIONS OAUTH2_ADFS_ENABLED LDAP_ENABLE LDAP_PORT LDAP_HOST LDAP_AD_SIMPLE_AUTH LDAP_BASEDN LDAP_LOGIN_FALLBACK LDAP_RECONNECT LDAP_TIMEOUT LDAP_IDLE_TIMEOUT LDAP_CONNECT_TIMEOUT LDAP_AUTHENTIFICATION LDAP_AUTHENTIFICATION_USERDN LDAP_AUTHENTIFICATION_PASSWORD LDAP_LOG_ENABLED LDAP_BACKGROUND_SYNC LDAP_BACKGROUND_SYNC_INTERVAL LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS LDAP_ENCRYPTION LDAP_CA_CERT LDAP_REJECT_UNAUTHORIZED LDAP_USER_AUTHENTICATION LDAP_USER_AUTHENTICATION_FIELD LDAP_USER_SEARCH_FILTER LDAP_USER_SEARCH_SCOPE LDAP_USER_SEARCH_FIELD LDAP_SEARCH_PAGE_SIZE LDAP_SEARCH_SIZE_LIMIT LDAP_GROUP_FILTER_ENABLE LDAP_GROUP_FILTER_OBJECTCLASS LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT LDAP_GROUP_FILTER_GROUP_NAME LDAP_UNIQUE_IDENTIFIER_FIELD LDAP_UTF8_NAMES_SLUGIFY LDAP_USERNAME_FIELD LDAP_FULLNAME_FIELD LDAP_MERGE_EXISTING_USERS LDAP_SYNC_USER_DATA LDAP_SYNC_USER_DATA_FIELDMAP LDAP_SYNC_GROUP_ROLES LDAP_DEFAULT_DOMAIN LDAP_EMAIL_MATCH_ENABLE LDAP_EMAIL_MATCH_REQUIRE LDAP_EMAIL_MATCH_VERIFIED LDAP_EMAIL_FIELD LDAP_SYNC_ADMIN_STATUS LDAP_SYNC_ADMIN_GROUPS HEADER_LOGIN_ID HEADER_LOGIN_FIRSTNAME HEADER_LOGIN_LASTNAME HEADER_LOGIN_EMAIL LOGOUT_WITH_TIMER LOGOUT_IN LOGOUT_ON_HOURS LOGOUT_ON_MINUTES DEFAULT_AUTHENTICATION_METHOD PASSWORD_LOGIN_ENABLED CAS_ENABLED CAS_BASE_URL CAS_LOGIN_URL CAS_VALIDATE_URL SAML_ENABLED SAML_PROVIDER SAML_ENTRYPOINT SAML_ISSUER SAML_CERT SAML_IDPSLO_REDIRECTURL SAML_PRIVATE_KEYFILE SAML_PUBLIC_CERTFILE SAML_IDENTIFIER_FORMAT SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE SAML_ATTRIBUTES ORACLE_OIM_ENABLED RESULTS_PER_PAGE WAIT_SPINNER NODE_OPTIONS" +keys="DEBUG MONGO_LOG_DESTINATION MONGO_URL MONGODB_BIND_UNIX_SOCKET MONGO_URL MONGODB_BIND_IP MONGODB_PORT MAIL_URL MAIL_FROM MAIL_SERVICE MAIL_SERVICE_USER MAIL_SERVICE_PASSWORD ROOT_URL PORT DISABLE_MONGODB CADDY_ENABLED CADDY_BIND_PORT WITH_API RICHER_CARD_COMMENT_EDITOR CARD_OPENED_WEBHOOK_ENABLED ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM ATTACHMENTS_UPLOAD_MIME_TYPES ATTACHMENTS_UPLOAD_MAX_SIZE AVATARS_UPLOAD_EXTERNAL_PROGRAM AVATARS_UPLOAD_MIME_TYPES AVATARS_UPLOAD_MAX_SIZE MAX_IMAGE_PIXEL IMAGE_COMPRESS_RATIO BIGEVENTS_PATTERN NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE NOTIFY_DUE_DAYS_BEFORE_AND_AFTER NOTIFY_DUE_AT_HOUR_OF_DAY EMAIL_NOTIFICATION_TIMEOUT CORS CORS_ALLOW_HEADERS CORS_EXPOSE_HEADERS MATOMO_ADDRESS MATOMO_SITE_ID MATOMO_DO_NOT_TRACK MATOMO_WITH_USERNAME BROWSER_POLICY_ENABLED TRUSTED_URL WEBHOOKS_ATTRIBUTES OAUTH2_ENABLED OIDC_REDIRECTION_ENABLED OAUTH2_CA_CERT OAUTH2_LOGIN_STYLE OAUTH2_CLIENT_ID OAUTH2_SECRET OAUTH2_SERVER_URL OAUTH2_AUTH_ENDPOINT OAUTH2_USERINFO_ENDPOINT OAUTH2_TOKEN_ENDPOINT OAUTH2_ID_MAP OAUTH2_USERNAME_MAP OAUTH2_FULLNAME_MAP OAUTH2_ID_TOKEN_WHITELIST_FIELDS OAUTH2_EMAIL_MAP OAUTH2_REQUEST_PERMISSIONS OAUTH2_ADFS_ENABLED LDAP_ENABLE LDAP_PORT LDAP_HOST LDAP_AD_SIMPLE_AUTH LDAP_BASEDN LDAP_LOGIN_FALLBACK LDAP_RECONNECT LDAP_TIMEOUT LDAP_IDLE_TIMEOUT LDAP_CONNECT_TIMEOUT LDAP_AUTHENTIFICATION LDAP_AUTHENTIFICATION_USERDN LDAP_AUTHENTIFICATION_PASSWORD LDAP_LOG_ENABLED LDAP_BACKGROUND_SYNC LDAP_BACKGROUND_SYNC_INTERVAL LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS LDAP_ENCRYPTION LDAP_CA_CERT LDAP_REJECT_UNAUTHORIZED LDAP_USER_AUTHENTICATION LDAP_USER_AUTHENTICATION_FIELD LDAP_USER_SEARCH_FILTER LDAP_USER_SEARCH_SCOPE LDAP_USER_SEARCH_FIELD LDAP_SEARCH_PAGE_SIZE LDAP_SEARCH_SIZE_LIMIT LDAP_GROUP_FILTER_ENABLE LDAP_GROUP_FILTER_OBJECTCLASS LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT LDAP_GROUP_FILTER_GROUP_NAME LDAP_UNIQUE_IDENTIFIER_FIELD LDAP_UTF8_NAMES_SLUGIFY LDAP_USERNAME_FIELD LDAP_FULLNAME_FIELD LDAP_MERGE_EXISTING_USERS LDAP_SYNC_USER_DATA LDAP_SYNC_USER_DATA_FIELDMAP LDAP_SYNC_GROUP_ROLES LDAP_DEFAULT_DOMAIN LDAP_EMAIL_MATCH_ENABLE LDAP_EMAIL_MATCH_REQUIRE LDAP_EMAIL_MATCH_VERIFIED LDAP_EMAIL_FIELD LDAP_SYNC_ADMIN_STATUS LDAP_SYNC_ADMIN_GROUPS HEADER_LOGIN_ID HEADER_LOGIN_FIRSTNAME HEADER_LOGIN_LASTNAME HEADER_LOGIN_EMAIL LOGOUT_WITH_TIMER LOGOUT_IN LOGOUT_ON_HOURS LOGOUT_ON_MINUTES DEFAULT_AUTHENTICATION_METHOD PASSWORD_LOGIN_ENABLED CAS_ENABLED CAS_BASE_URL CAS_LOGIN_URL CAS_VALIDATE_URL SAML_ENABLED SAML_PROVIDER SAML_ENTRYPOINT SAML_ISSUER SAML_CERT SAML_IDPSLO_REDIRECTURL SAML_PRIVATE_KEYFILE SAML_PUBLIC_CERTFILE SAML_IDENTIFIER_FORMAT SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE SAML_ATTRIBUTES ORACLE_OIM_ENABLED RESULTS_PER_PAGE WAIT_SPINNER NODE_OPTIONS" #DESCRIPTION_WRITABLE_PATH="Writable path. Default: $SNAP_COMMON/files" #DEFAULT_WRITABLE_PATH="$SNAP_COMMON/files" @@ -141,6 +141,18 @@ DESCRIPTION_ATTACHMENTS_UPLOAD_MAX_SIZE="Attachments upload max size in bytes. E DEFAULT_ATTACHMENTS_UPLOAD_MAX_SIZE="" KEY_ATTACHMENTS_UPLOAD_MAX_SIZE="attachments-upload-max-size" +DESCRIPTION_AVATARS_UPLOAD_EXTERNAL_PROGRAM="Avatars upload validation by an external program. {file} is replaced by the uploaded file. Example: /usr/local/bin/avscan {file}" +DEFAULT_AVATARS_UPLOAD_EXTERNAL_PROGRAM="" +KEY_AVATARS_UPLOAD_EXTERNAL_PROGRAM="avatars-upload-external-program" + +DESCRIPTION_AVATARS_UPLOAD_MIME_TYPES="Avatars upload mime types. Example: image/*" +DEFAULT_AVATARS_UPLOAD_MIME_TYPES="" +KEY_AVATARS_UPLOAD_MIME_TYPES="avatars-upload-mime-types" + +DESCRIPTION_AVATARS_UPLOAD_MAX_SIZE="Avatars upload max size in bytes. Example: 500000" +DEFAULT_AVATARS_UPLOAD_MAX_SIZE="" +KEY_AVATARS_UPLOAD_MAX_SIZE="avatars-upload-max-size" + # Example, not in use: /var/snap/wekan/common/uploads/ DESCRIPTION_MAX_IMAGE_PIXEL="Max image pixel: Allow to shrink attached/pasted image https://github.com/wekan/wekan/pull/2544" diff --git a/start-wekan.bat b/start-wekan.bat index 7c6b5f286..3f410ec81 100644 --- a/start-wekan.bat +++ b/start-wekan.bat @@ -55,9 +55,15 @@ REM # ==== ACCOUNT OPTIONS ==== REM SET ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90 REM # ==== Allow configuration to validate uploaded attachments ==== +REM SET ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM="avscan {file}" REM SET ATTACHMENTS_UPLOAD_MIME_TYPES="image/*,text/*" REM SET ATTACHMENTS_UPLOAD_MAX_SIZE=5000000 +REM # ==== Allow configuration to validate uploaded avatars ==== +REM SET AVATARS_UPLOAD_EXTERNAL_PROGRAM="avscan {file}" +REM SET AVATARS_UPLOAD_MIME_TYPES="image/*" +REM SET AVATARS_UPLOAD_MAX_SIZE=500000 + REM # ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE ===== REM # Number of days after a notification is read before we remove it. REM # Default: 2 diff --git a/start-wekan.sh b/start-wekan.sh index d41995a04..17bf32509 100755 --- a/start-wekan.sh +++ b/start-wekan.sh @@ -58,6 +58,11 @@ #export ATTACHMENTS_UPLOAD_MIME_TYPES="image/*,text/*" #export ATTACHMENTS_UPLOAD_MAX_SIZE=5000000 #--------------------------------------------------------------- + # ==== Allow configuration to validate uploaded avatars ==== + #export AVATARS_UPLOAD_EXTERNAL_PROGRAM="/usr/local/bin/avscan {file}" + #export AVATARS_UPLOAD_MIME_TYPES="image/*" + #export AVATARS_UPLOAD_MAX_SIZE=500000 + #--------------------------------------------------------------- # ==== RICH TEXT EDITOR IN CARD COMMENTS ==== # https://github.com/wekan/wekan/pull/2560 export RICHER_CARD_COMMENT_EDITOR=false diff --git a/torodb-postgresql/docker-compose.yml b/torodb-postgresql/docker-compose.yml index 0d2d5a2d8..a236cafc6 100644 --- a/torodb-postgresql/docker-compose.yml +++ b/torodb-postgresql/docker-compose.yml @@ -281,6 +281,11 @@ services: #-ATTACHMENTS_UPLOAD_MIME_TYPES=image/*,text/* #-ATTACHMENTS_UPLOAD_MAX_SIZE=5000000 #--------------------------------------------------------------- + # ==== Allow configuration to validate uploaded avatars ==== + #-AVATARS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file} + #-AVATARS_UPLOAD_MIME_TYPES=image/* + #-AVATARS_UPLOAD_MAX_SIZE=500000 + #--------------------------------------------------------------- # ==== Allow to shrink attached/pasted image ==== # https://github.com/wekan/wekan/pull/2544 #-MAX_IMAGE_PIXEL=1024