diff --git a/Common/config/default.json b/Common/config/default.json index cf4dfd5f..aaf876c3 100644 --- a/Common/config/default.json +++ b/Common/config/default.json @@ -42,8 +42,9 @@ "email" ], "template": { - "title": "License expiration", - "body": "%s license %s on %s!!!" + "title": "License expiration warning", + "bodyWarn": "Attention! Your license is about to expire on %s.\nUpon reaching this date, you will no longer be entitled to receive personal technical support and install new Docs versions released after this date.", + "bodyError": "Attention! Your license expired on %s.\nYou are no longer entitled to receive personal technical support and install new Docs versions released after this date.\nPlease contact sales@onlyoffice.com to discuss license renewal." }, "policies": { "repeatInterval": "1d" @@ -54,8 +55,8 @@ "email" ], "template": { - "title": "License limit", - "body": "%s limit exceeded!!!" + "title": "License connection limit warning", + "body": "Attention! You have reached %s%% of the %s limit set by your license." }, "policies": { "repeatInterval": "1h" diff --git a/Common/sources/notificationService.js b/Common/sources/notificationService.js index fe264fb4..ad1a327f 100644 --- a/Common/sources/notificationService.js +++ b/Common/sources/notificationService.js @@ -52,7 +52,7 @@ const notificationTypes = { class TransportInterface { async send(ctx, message) {} - contentGeneration(template, messageParams) {} + contentGeneration(template, message) {} } class MailTransport extends TransportInterface { @@ -71,10 +71,10 @@ class MailTransport extends TransportInterface { return mailService.send(this.host, this.auth.user, message); } - contentGeneration(template, messageParams) { + contentGeneration(template, message) { return { subject: template.title, - text: util.format(template.body, ...messageParams) + text: message }; } } @@ -105,7 +105,7 @@ class Transport { } } -async function notify(ctx, notificationType, messageParams) { +async function notify(ctx, notificationType, message) { const tenNotificationEnable = ctx.getCfg('notification.enable', cfgNotificationEnable); if (!tenNotificationEnable) { return; @@ -116,7 +116,7 @@ async function notify(ctx, notificationType, messageParams) { if (tenRule) { let checkRes = await checkRulePolicies(ctx, notificationType, tenRule); if (checkRes) { - await notifyRule(ctx, tenRule, messageParams); + await notifyRule(ctx, tenRule, message); } } } @@ -136,12 +136,12 @@ async function checkRulePolicies(ctx, notificationType, tenRule) { return isLock; } -async function notifyRule(ctx, tenRule, messageParams) { +async function notifyRule(ctx, tenRule, message) { const transportObjects = tenRule.transportType.map(transport => new Transport(ctx, transport)); for (const transportObject of transportObjects) { try { - const message = transportObject.transport.contentGeneration(tenRule.template, messageParams); - await transportObject.transport.send(ctx, message); + const mail = transportObject.transport.contentGeneration(tenRule.template, message); + await transportObject.transport.send(ctx, mail); } catch (error) { ctx.logger.error('Notification service: error: %s', error.stack); } diff --git a/DocService/sources/DocsCoServer.js b/DocService/sources/DocsCoServer.js index 6667161d..ed5b0540 100644 --- a/DocService/sources/DocsCoServer.js +++ b/DocService/sources/DocsCoServer.js @@ -108,6 +108,7 @@ const cfgEditorDataStorage = config.get('services.CoAuthoring.server.editorDataS const cfgEditorStatStorage = config.get('services.CoAuthoring.server.editorStatStorage'); const editorDataStorage = require('./' + cfgEditorDataStorage); const editorStatStorage = require('./' + (cfgEditorStatStorage || cfgEditorDataStorage)); +const util = require("util"); const cfgEditSingleton = config.get('services.CoAuthoring.server.edit_singleton'); const cfgEditor = config.get('services.CoAuthoring.editor'); @@ -137,6 +138,7 @@ const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles const cfgForgottenFilesName = config.get('services.CoAuthoring.server.forgottenfilesname'); const cfgMaxRequestChanges = config.get('services.CoAuthoring.server.maxRequestChanges'); const cfgWarningLimitPercents = config.get('license.warning_limit_percents'); +const cfgNotificationRuleLicenseLimit = config.get('notification.rules.licenseLimit.template.body'); const cfgErrorFiles = config.get('FileConverter.converter.errorfiles'); const cfgOpenProtectedFile = config.get('services.CoAuthoring.server.openProtectedFile'); const cfgIsAnonymousSupport = config.get('services.CoAuthoring.server.isAnonymousSupport'); @@ -2664,11 +2666,7 @@ exports.install = function(server, callbackFunction) { conn.licenseType = c_LR.Success; let isLiveViewer = utils.isLiveViewer(conn); if (!conn.user.view || isLiveViewer) { - let logPrefixTenant = 'License of tenant: '; - let logPrefixServer = 'License: '; - let logPrefix = tenantManager.isMultitenantMode(ctx) ? logPrefixTenant : logPrefixServer; - - let licenseType = yield* _checkLicenseAuth(ctx, licenseInfo, conn.user.idOriginal, isLiveViewer, logPrefix); + let licenseType = yield* _checkLicenseAuth(ctx, licenseInfo, conn.user.idOriginal, isLiveViewer); let aggregationCtx, licenseInfoAggregation; if ((c_LR.Success === licenseType || c_LR.SuccessLimit === licenseType) && tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) { //check server aggregation license @@ -2676,7 +2674,7 @@ exports.install = function(server, callbackFunction) { aggregationCtx.init(tenantManager.getDefautTenant(), ctx.docId, ctx.userId); //yield ctx.initTenantCache(); //no need licenseInfoAggregation = tenantManager.getServerLicense(); - licenseType = yield* _checkLicenseAuth(aggregationCtx, licenseInfoAggregation, `${ctx.tenant}:${ conn.user.idOriginal}`, isLiveViewer, logPrefixServer); + licenseType = yield* _checkLicenseAuth(aggregationCtx, licenseInfoAggregation, `${ctx.tenant}:${ conn.user.idOriginal}`, isLiveViewer); } conn.licenseType = licenseType; if ((c_LR.Success !== licenseType && c_LR.SuccessLimit !== licenseType) || (!tenIsAnonymousSupport && data.IsAnonymousUser)) { @@ -3479,96 +3477,66 @@ exports.install = function(server, callbackFunction) { }); } - function* _checkLicenseAuth(ctx, licenseInfo, userId, isLiveViewer, logPrefix) { + function* _checkLicenseAuth(ctx, licenseInfo, userId, isLiveViewer) { const tenWarningLimitPercents = ctx.getCfg('license.warning_limit_percents', cfgWarningLimitPercents) / 100; - - let licenseWarningLimitUsers = false; - let licenseWarningLimitUsersView = false; - let licenseWarningLimitConnections = false; - let licenseWarningLimitConnectionsLive = false; + const tenNotificationRuleLicenseLimit = ctx.getCfg(`notification.rules.licenseLimit.template.body`, cfgNotificationRuleLicenseLimit); const c_LR = constants.LICENSE_RESULT; let licenseType = licenseInfo.type; if (c_LR.Success === licenseType || c_LR.SuccessLimit === licenseType) { + let notificationLimit; + let notificationPercent = 0; if (licenseInfo.usersCount) { const nowUTC = getLicenseNowUtc(); if(isLiveViewer) { const arrUsers = yield editorStat.getPresenceUniqueViewUser(ctx, nowUTC); if (arrUsers.length >= licenseInfo.usersViewCount && (-1 === arrUsers.findIndex((element) => {return element.userid === userId}))) { - licenseType = c_LR.UsersViewCount; + licenseType = licenseInfo.hasLicense ? c_LR.UsersViewCount : c_LR.UsersViewCountOS; + } else if (licenseInfo.usersViewCount * tenWarningLimitPercents <= arrUsers.length) { + notificationPercent = tenWarningLimitPercents * 100; } - licenseWarningLimitUsersView = licenseInfo.usersViewCount * tenWarningLimitPercents <= arrUsers.length; + notificationLimit = 'live viewer users'; } else { const arrUsers = yield editorStat.getPresenceUniqueUser(ctx, nowUTC); if (arrUsers.length >= licenseInfo.usersCount && (-1 === arrUsers.findIndex((element) => {return element.userid === userId}))) { - licenseType = c_LR.UsersCount; + licenseType = licenseInfo.hasLicense ? c_LR.UsersCount : c_LR.UsersCountOS; + } else if(licenseInfo.usersCount * tenWarningLimitPercents <= arrUsers.length) { + notificationPercent = tenWarningLimitPercents * 100; } - licenseWarningLimitUsers = licenseInfo.usersCount * tenWarningLimitPercents <= arrUsers.length; + notificationLimit = 'users'; } - } else if(isLiveViewer) { - const connectionsLiveCount = licenseInfo.connectionsView; - const liveViewerConnectionsCount = yield editorStat.getLiveViewerConnectionsCount(ctx, connections); - if (liveViewerConnectionsCount >= connectionsLiveCount) { - licenseType = c_LR.ConnectionsLive; - } - licenseWarningLimitConnectionsLive = connectionsLiveCount * tenWarningLimitPercents <= liveViewerConnectionsCount; } else { - const connectionsCount = licenseInfo.connections; - const editConnectionsCount = yield editorStat.getEditorConnectionsCount(ctx, connections); - if (editConnectionsCount >= connectionsCount) { - licenseType = c_LR.Connections; + if (isLiveViewer) { + const connectionsLiveCount = licenseInfo.connectionsView; + const liveViewerConnectionsCount = yield editorStat.getLiveViewerConnectionsCount(ctx, connections); + if (liveViewerConnectionsCount >= connectionsLiveCount) { + licenseType = licenseInfo.hasLicense ? c_LR.ConnectionsLive : c_LR.ConnectionsLiveOS; + } else if(connectionsLiveCount * tenWarningLimitPercents <= liveViewerConnectionsCount){ + notificationPercent = tenWarningLimitPercents * 100; + } + notificationLimit = 'live viewer connections'; + } else { + const connectionsCount = licenseInfo.connections; + const editConnectionsCount = yield editorStat.getEditorConnectionsCount(ctx, connections); + if (editConnectionsCount >= connectionsCount) { + licenseType = licenseInfo.hasLicense ? c_LR.Connections : c_LR.ConnectionsOS; + } else if (connectionsCount * tenWarningLimitPercents <= editConnectionsCount) { + notificationPercent = tenWarningLimitPercents * 100; + } + notificationLimit = 'connections'; } - licenseWarningLimitConnections = connectionsCount * tenWarningLimitPercents <= editConnectionsCount; } - } - - let logPostfix = ' limit exceeded!!!'; - let notificationPrefix; - if (c_LR.UsersCount === licenseType) { - if (!licenseInfo.hasLicense) { - licenseType = c_LR.UsersCountOS; + if ((c_LR.Success !== licenseType && c_LR.SuccessLimit !== licenseType) || notificationPercent > 0) { + let message; + if (notificationPercent > 0) { + message = util.format(tenNotificationRuleLicenseLimit, notificationPercent, notificationLimit); + ctx.logger.warn(tenNotificationRuleLicenseLimit, notificationPercent, notificationLimit); + } else { + message = util.format(tenNotificationRuleLicenseLimit, 100, notificationLimit); + ctx.logger.error(tenNotificationRuleLicenseLimit, 100, notificationLimit); + } + //todo with yield service could throw error + void notificationService.notify(ctx, notificationTypes.LICENSE_LIMIT, message); } - notificationPrefix = logPrefix + 'User'; - ctx.logger.error(notificationPrefix + logPostfix); - } else if (c_LR.UsersViewCount === licenseType) { - if (!licenseInfo.hasLicense) { - licenseType = c_LR.UsersViewCountOS; - } - notificationPrefix = logPrefix + 'User Live Viewer'; - ctx.logger.error(notificationPrefix + logPostfix); - } else if (c_LR.Connections === licenseType) { - if (!licenseInfo.hasLicense) { - licenseType = c_LR.ConnectionsOS; - } - notificationPrefix = logPrefix + 'Connection'; - ctx.logger.error(notificationPrefix + logPostfix); - } else if (c_LR.ConnectionsLive === licenseType) { - if (!licenseInfo.hasLicense) { - licenseType = c_LR.ConnectionsLiveOS; - } - notificationPrefix = logPrefix + 'Connection Live Viewer'; - ctx.logger.error(notificationPrefix + logPostfix); - } else { - if (licenseWarningLimitUsers) { - notificationPrefix = logPrefix + 'Warning User'; - ctx.logger.warn(notificationPrefix + logPostfix); - } - if (licenseWarningLimitUsersView) { - notificationPrefix = logPrefix + 'Warning User Live Viewer'; - ctx.logger.warn(notificationPrefix + logPostfix); - } - if (licenseWarningLimitConnections) { - notificationPrefix = logPrefix + 'Warning Connection'; - ctx.logger.warn(notificationPrefix + logPostfix); - } - if (licenseWarningLimitConnectionsLive) { - notificationPrefix = logPrefix + 'Warning Connection Live Viewer'; - ctx.logger.warn(notificationPrefix + logPostfix); - } - } - - if (notificationPrefix) { - //todo with yield service could throw error - notificationService.notify(ctx, notificationTypes.LICENSE_LIMIT, [notificationPrefix]); } return licenseType; } diff --git a/DocService/sources/utilsDocService.js b/DocService/sources/utilsDocService.js index 9e0ea16e..b794b965 100644 --- a/DocService/sources/utilsDocService.js +++ b/DocService/sources/utilsDocService.js @@ -32,6 +32,7 @@ 'use strict'; +const util = require("util"); const config = require('config'); const exifParser = require('exif-parser'); const Jimp = require('jimp'); @@ -42,6 +43,8 @@ const tenantManager = require('../../Common/sources/tenantManager'); const { notificationTypes, ...notificationService } = require('../../Common/sources/notificationService'); const cfgStartNotifyFrom = ms(config.get('license.warning_license_expiration')); +const cfgNotificationRuleLicenseExpirationWarning = config.get('notification.rules.licenseExpirationWarning.template.bodyWarn'); +const cfgNotificationRuleLicenseExpirationError = config.get('notification.rules.licenseExpirationWarning.template.bodyError'); async function fixImageExifRotation(ctx, buffer) { if (!buffer) { @@ -123,11 +126,15 @@ async function notifyLicenseExpiration(ctx, endDate) { const currentDate = new Date(); if (currentDate.getTime() >= endDate.getTime() - cfgStartNotifyFrom) { const formattedExpirationTime = humanFriendlyExpirationTime(endDate); - const tenant = tenantManager.isDefaultTenant(ctx) ? 'server' : ctx.tenant; - - const state = endDate < currentDate ? 'expired' : 'expires'; - ctx.logger.warn('%s license %s on %s!!!', tenant, state, formattedExpirationTime); - await notificationService.notify(ctx, notificationTypes.LICENSE_EXPIRATION_WARNING, [tenant, state, formattedExpirationTime]); + //todo one body template + let message; + if (endDate < currentDate) { + message = util.format(cfgNotificationRuleLicenseExpirationError, formattedExpirationTime); + } else { + message = util.format(cfgNotificationRuleLicenseExpirationWarning, formattedExpirationTime); + } + ctx.logger.warn(message); + await notificationService.notify(ctx, notificationTypes.LICENSE_EXPIRATION_WARNING, message); } }