diff --git a/server/.env.sample b/server/.env.sample index c5ac1288..242c08c7 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -42,6 +42,15 @@ SECRET_KEY=notsecretkey # will be sent through this proxy if set. # OUTGOING_PROXY=http://proxy:3128 +# Apprise notifications configuration. +# Set to false to prevent users from configuring and sending Apprise notifications. +# APPRISE_ENABLED=false +# Comma-separated allowlist of Apprise schemas users may use (e.g. slack,telegram). +# When set, only schemas in this list are accepted; APPRISE_BLOCKED_SCHEMAS is ignored. +# APPRISE_ALLOWED_SCHEMAS= +# Comma-separated blocklist of Apprise schemas. Replaces the built-in blocked list. +# APPRISE_BLOCKED_SCHEMAS=syslog,dbus,kde,qt,glib,gnome,macosx,windows + # Set to true to expose the Swagger specification at /swagger.json # SWAGGER_EXPOSED=false diff --git a/server/api/controllers/notification-services/create-in-board.js b/server/api/controllers/notification-services/create-in-board.js index 61e16e0e..6896b43e 100644 --- a/server/api/controllers/notification-services/create-in-board.js +++ b/server/api/controllers/notification-services/create-in-board.js @@ -68,6 +68,12 @@ const Errors = { BOARD_NOT_FOUND: { boardNotFound: 'Board not found', }, + APPRISE_DISABLED: { + appriseDisabled: 'Apprise notifications are disabled', + }, + SCHEMA_NOT_ALLOWED: { + schemaNotAllowed: 'Notification service schema is not allowed', + }, LIMIT_REACHED: { limitReached: 'Limit reached', }, @@ -95,6 +101,12 @@ module.exports = { boardNotFound: { responseType: 'notFound', }, + appriseDisabled: { + responseType: 'forbidden', + }, + schemaNotAllowed: { + responseType: 'unprocessableEntity', + }, limitReached: { responseType: 'conflict', }, @@ -125,6 +137,8 @@ module.exports = { actorUser: currentUser, request: this.req, }) + .intercept('appriseDisabled', () => Errors.APPRISE_DISABLED) + .intercept('schemaNotAllowed', () => Errors.SCHEMA_NOT_ALLOWED) .intercept('limitReached', () => Errors.LIMIT_REACHED); return { diff --git a/server/api/controllers/notification-services/create-in-user.js b/server/api/controllers/notification-services/create-in-user.js index 2d627018..5eaddbfe 100644 --- a/server/api/controllers/notification-services/create-in-user.js +++ b/server/api/controllers/notification-services/create-in-user.js @@ -68,6 +68,12 @@ const Errors = { USER_NOT_FOUND: { userNotFound: 'User not found', }, + APPRISE_DISABLED: { + appriseDisabled: 'Apprise notifications are disabled', + }, + SCHEMA_NOT_ALLOWED: { + schemaNotAllowed: 'Notification service schema is not allowed', + }, LIMIT_REACHED: { limitReached: 'Limit reached', }, @@ -95,6 +101,12 @@ module.exports = { userNotFound: { responseType: 'notFound', }, + appriseDisabled: { + responseType: 'forbidden', + }, + schemaNotAllowed: { + responseType: 'unprocessableEntity', + }, limitReached: { responseType: 'conflict', }, @@ -118,6 +130,8 @@ module.exports = { actorUser: currentUser, request: this.req, }) + .intercept('appriseDisabled', () => Errors.APPRISE_DISABLED) + .intercept('schemaNotAllowed', () => Errors.SCHEMA_NOT_ALLOWED) .intercept('limitReached', () => Errors.LIMIT_REACHED); return { diff --git a/server/api/helpers/notification-services/create-one-in-board.js b/server/api/helpers/notification-services/create-one-in-board.js index b81282f1..ca1b5b2d 100644 --- a/server/api/helpers/notification-services/create-one-in-board.js +++ b/server/api/helpers/notification-services/create-one-in-board.js @@ -23,11 +23,28 @@ module.exports = { }, exits: { + appriseDisabled: {}, + schemaNotAllowed: {}, limitReached: {}, }, async fn(inputs) { const { values } = inputs; + const { appriseEnabled, appriseAllowedSchemas, appriseBlockedSchemas } = sails.config.custom; + + if (!appriseEnabled) { + throw 'appriseDisabled'; + } + + const schema = values.url.split(':')[0]; + + if (appriseAllowedSchemas.length > 0 && !appriseAllowedSchemas.includes(schema)) { + throw 'schemaNotAllowed'; + } + + if (appriseAllowedSchemas.length === 0 && appriseBlockedSchemas.includes(schema)) { + throw 'schemaNotAllowed'; + } const notificationServicesTotal = await sails.helpers.boards.getNotificationServicesTotal( values.board.id, diff --git a/server/api/helpers/notification-services/create-one-in-user.js b/server/api/helpers/notification-services/create-one-in-user.js index 8030fda5..e3171a24 100644 --- a/server/api/helpers/notification-services/create-one-in-user.js +++ b/server/api/helpers/notification-services/create-one-in-user.js @@ -19,11 +19,28 @@ module.exports = { }, exits: { + appriseDisabled: {}, + schemaNotAllowed: {}, limitReached: {}, }, async fn(inputs) { const { values } = inputs; + const { appriseEnabled, appriseAllowedSchemas, appriseBlockedSchemas } = sails.config.custom; + + if (!appriseEnabled) { + throw 'appriseDisabled'; + } + + const schema = values.url.split(':')[0]; + + if (appriseAllowedSchemas.length > 0 && !appriseAllowedSchemas.includes(schema)) { + throw 'schemaNotAllowed'; + } + + if (appriseAllowedSchemas.length === 0 && appriseBlockedSchemas.includes(schema)) { + throw 'schemaNotAllowed'; + } const notificationServicesTotal = await sails.helpers.users.getNotificationServicesTotal( values.user.id, diff --git a/server/api/helpers/utils/send-notifications.js b/server/api/helpers/utils/send-notifications.js index 9d064c52..d1840a83 100644 --- a/server/api/helpers/utils/send-notifications.js +++ b/server/api/helpers/utils/send-notifications.js @@ -31,6 +31,17 @@ module.exports = { }, async fn(inputs) { + const { appriseEnabled, appriseAllowedSchemas, appriseBlockedSchemas } = sails.config.custom; + + if (!appriseEnabled) { + return; + } + + const schemaConfig = JSON.stringify({ + allowedSchemas: appriseAllowedSchemas, + blockedSchemas: appriseBlockedSchemas, + }); + try { await promisifyExecFile( PYTHON_PATH, @@ -39,6 +50,7 @@ module.exports = { JSON.stringify(inputs.services), inputs.title, JSON.stringify(inputs.bodyByFormat), + schemaConfig, ], { env: { diff --git a/server/config/custom.js b/server/config/custom.js index bcd201bb..a121d807 100644 --- a/server/config/custom.js +++ b/server/config/custom.js @@ -101,6 +101,10 @@ module.exports.custom = { smtpPassword: process.env.SMTP_PASSWORD, smtpFrom: process.env.SMTP_FROM, + appriseEnabled: process.env.APPRISE_ENABLED !== 'false', + appriseAllowedSchemas: envToArray(process.env.APPRISE_ALLOWED_SCHEMAS), + appriseBlockedSchemas: envToArray(process.env.APPRISE_BLOCKED_SCHEMAS), + gravatarBaseUrl: process.env.GRAVATAR_BASE_URL, /* Internal */ diff --git a/server/utils/send_notifications.py b/server/utils/send_notifications.py index fe6bef5e..fba14bdb 100644 --- a/server/utils/send_notifications.py +++ b/server/utils/send_notifications.py @@ -7,7 +7,7 @@ import logging import apprise -BLOCKED_SCHEMAS_SET = { +DEFAULT_BLOCKED_SCHEMAS_SET = { 'syslog', 'dbus', 'kde', @@ -45,13 +45,22 @@ if __name__ == '__main__': services = json.loads(sys.argv[1]) title = sys.argv[2] body_by_format = json.loads(sys.argv[3]) + schema_config = json.loads(sys.argv[4]) if len(sys.argv) > 4 else {} + + allowed_schemas = set(schema_config.get('allowedSchemas', [])) + custom_blocked = schema_config.get('blockedSchemas', []) + blocked_schemas = set(custom_blocked) if custom_blocked else DEFAULT_BLOCKED_SCHEMAS_SET errors = [] for service in services: url = service['url'] schema = url.split(':')[0] - if schema in BLOCKED_SCHEMAS_SET: + if allowed_schemas and schema not in allowed_schemas: + errors.append(f'[{schema}] Schema not in allowed list') + continue + + if schema in blocked_schemas: errors.append(f'[{schema}] Blocked service schema') continue