Audit logs react to license updates (with logs)

This commit is contained in:
Rémi de Juvigny 2023-02-24 15:48:36 +01:00
parent 0dcfbde89f
commit 43ceb02092
4 changed files with 117 additions and 13 deletions

View File

@ -1,15 +1,13 @@
'use strict'; 'use strict';
const { features } = require('@strapi/strapi/lib/utils/ee');
const executeCERegister = require('../../server/register'); const executeCERegister = require('../../server/register');
const migrateAuditLogsTable = require('./migrations/audit-logs-table'); const migrateAuditLogsTable = require('./migrations/audit-logs-table');
const createAuditLogsService = require('./services/audit-logs'); const createAuditLogsService = require('./services/audit-logs');
module.exports = async ({ strapi }) => { module.exports = async ({ strapi }) => {
const auditLogsIsAllowed = features.isEnabled('audit-logs'); const auditLogsIsEnabled = strapi.config.get('admin.auditLogs.enabled', true);
const auditLogsIsEnabled = strapi.config.get('server.auditLogs.enabled', true);
if (auditLogsIsAllowed && auditLogsIsEnabled) { if (auditLogsIsEnabled) {
strapi.hook('strapi::content-types.beforeSync').register(migrateAuditLogsTable); strapi.hook('strapi::content-types.beforeSync').register(migrateAuditLogsTable);
const auditLogsService = createAuditLogsService(strapi); const auditLogsService = createAuditLogsService(strapi);
strapi.container.register('audit-logs', auditLogsService); strapi.container.register('audit-logs', auditLogsService);

View File

@ -53,6 +53,24 @@ const getEventMap = (defaultEvents) => {
}, {}); }, {});
}; };
const getRetentionDays = (strapi) => {
const licenseRetentionDays = features.get('audit-logs')?.options.retentionDays;
const userRetentionDays = strapi.config.get('admin.auditLogs.retentionDays');
// For enterprise plans, use 90 days by default, but allow users to override it
if (licenseRetentionDays == null) {
return userRetentionDays ?? DEFAULT_RETENTION_DAYS;
}
// Allow users to override the license retention days, but not to increase it
if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
return userRetentionDays;
}
// User didn't provide a retention days value, use the license one
return licenseRetentionDays;
};
const createAuditLogsService = (strapi) => { const createAuditLogsService = (strapi) => {
// NOTE: providers should be able to replace getEventMap to add or remove events // NOTE: providers should be able to replace getEventMap to add or remove events
const eventMap = getEventMap(defaultEvents); const eventMap = getEventMap(defaultEvents);
@ -99,10 +117,45 @@ const createAuditLogsService = (strapi) => {
return { return {
async register() { async register() {
const retentionDays = console.log('audit logs register');
features.get('audit-logs')?.options.retentionDays ?? DEFAULT_RETENTION_DAYS; // Handle license being enabled
if (!this._eeEnableUnsubscribe) {
this._eeEnableUnsubscribe = strapi.eventHub.once('ee.enable', () => {
console.log('listened to ee.enable');
// Recreate the service to use the new license info
this.destroy();
this.register();
});
}
// Handle license being updated
this._eeUpdateUnsubscribe = strapi.eventHub.on('ee.update', () => {
console.log('listened to ee.update');
// Recreate the service to use the new license info
this.destroy();
this.register();
});
// Handle license being disabled
this._eeDisableUnsubscribe = strapi.eventHub.on('ee.disable', () => {
console.log('listened to ee.disable');
// Turn off service when the license gets disabled
// Only the ee.enable listener remains active to recreate the service
this.destroy();
});
// Check current state of license
if (!features.isEnabled('audit-logs')) {
return this;
}
// Start saving events
this._provider = await localProvider.register({ strapi }); this._provider = await localProvider.register({ strapi });
this._eventHubUnsubscribe = strapi.eventHub.subscribe(handleEvent.bind(this)); this._eventHubUnsubscribe = strapi.eventHub.subscribe(handleEvent.bind(this));
// Manage audit logs auto deletion
const retentionDays = getRetentionDays(strapi);
console.log('registering audit logs service', retentionDays);
this._deleteExpiredJob = scheduleJob('0 0 * * *', () => { this._deleteExpiredJob = scheduleJob('0 0 * * *', () => {
const expirationDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000); const expirationDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
this._provider.deleteExpiredEvents(expirationDate); this._provider.deleteExpiredEvents(expirationDate);
@ -143,6 +196,14 @@ const createAuditLogsService = (strapi) => {
}, },
unsubscribe() { unsubscribe() {
if (this._eeUpdateUnsubscribe) {
this._eeUpdateUnsubscribe();
}
if (this._eeDisableUnsubscribe) {
this._eeDisableUnsubscribe();
}
if (this._eventHubUnsubscribe) { if (this._eventHubUnsubscribe) {
this._eventHubUnsubscribe(); this._eventHubUnsubscribe();
} }
@ -155,6 +216,7 @@ const createAuditLogsService = (strapi) => {
}, },
destroy() { destroy() {
console.log('audit logs destroy');
return this.unsubscribe(); return this.unsubscribe();
}, },
}; };

View File

@ -1,12 +1,12 @@
'use strict'; 'use strict';
const { pick } = require('lodash/fp'); const { pick, isEqual } = require('lodash/fp');
const { readLicense, verifyLicense, fetchLicense, LicenseCheckError } = require('./license'); const { readLicense, verifyLicense, fetchLicense, LicenseCheckError } = require('./license');
const { eeStoreModel } = require('./ee-store'); const { eeStoreModel } = require('./ee-store');
const { shiftCronExpression } = require('../lib/utils/cron'); const { shiftCronExpression } = require('../lib/utils/cron');
const ONE_MINUTE = 1000 * 60; const ONE_MINUTE = 1 * 60;
const ee = { const ee = {
enabled: false, enabled: false,
@ -14,10 +14,33 @@ const ee = {
}; };
const disable = (message) => { const disable = (message) => {
// Prevent emitting ee.disable if it was already disabled
const shouldEmitEvent = ee.enabled !== false;
ee.logger?.warn(`${message} Switching to CE.`); ee.logger?.warn(`${message} Switching to CE.`);
// Only keep the license key for potential re-enabling during a later check // Only keep the license key for potential re-enabling during a later check
ee.licenseInfo = pick('licenseKey', ee.licenseInfo); ee.licenseInfo = pick('licenseKey', ee.licenseInfo);
// Prevent emitting ee.disable if it was already disabled
console.log('disabling EE');
ee.enabled = false; ee.enabled = false;
if (shouldEmitEvent) {
// Notify EE features that they should be disabled
strapi.eventHub.emit('ee.disable');
}
};
const enable = () => {
// Prevent emitting ee.disable if it was already disabled
const shouldEmitEvent = ee.enabled !== true;
ee.enabled = true;
if (shouldEmitEvent) {
// Notify EE features that they should be disabled
strapi.eventHub.emit('ee.enable');
}
}; };
let initialized = false; let initialized = false;
@ -42,7 +65,7 @@ const init = (licenseDir, logger) => {
if (license) { if (license) {
ee.licenseInfo = verifyLicense(license); ee.licenseInfo = verifyLicense(license);
ee.enabled = true; enable();
} }
} catch (error) { } catch (error) {
disable(error.message); disable(error.message);
@ -55,6 +78,7 @@ const init = (licenseDir, logger) => {
* Store the result in database to avoid unecessary requests, and will fallback to that in case of a network failure. * Store the result in database to avoid unecessary requests, and will fallback to that in case of a network failure.
*/ */
const onlineUpdate = async ({ strapi }) => { const onlineUpdate = async ({ strapi }) => {
console.log('onlineUpdate');
const { get, commit, rollback } = await strapi.db.transaction(); const { get, commit, rollback } = await strapi.db.transaction();
const transaction = get(); const transaction = get();
@ -90,8 +114,22 @@ const onlineUpdate = async ({ strapi }) => {
if (license) { if (license) {
try { try {
ee.licenseInfo = verifyLicense(license); // Verify license and check if its info changed
const newLicenseInfo = verifyLicense(license);
const fieldsToCompare = ['licenseKey', 'features', 'plan'];
const licenseInfoChanged = !isEqual(
pick(fieldsToCompare, newLicenseInfo),
pick(fieldsToCompare, ee.licenseInfo)
);
// Store the new license info
ee.licenseInfo = newLicenseInfo;
validateInfo(); validateInfo();
// Notify EE features
if (licenseInfoChanged) {
strapi.eventHub.emit('ee.update');
}
} catch (error) { } catch (error) {
disable(error.message); disable(error.message);
} }
@ -126,7 +164,8 @@ const validateInfo = () => {
return disable('License expired.'); return disable('License expired.');
} }
ee.enabled = true; // Prevent emitting ee.enable if it was already enabled
enable();
}; };
const checkLicense = async ({ strapi }) => { const checkLicense = async ({ strapi }) => {
@ -137,7 +176,9 @@ const checkLicense = async ({ strapi }) => {
if (!shouldStayOffline) { if (!shouldStayOffline) {
await onlineUpdate({ strapi }); await onlineUpdate({ strapi });
strapi.cron.add({ [shiftCronExpression('0 0 */12 * * *')]: onlineUpdate });
// strapi.cron.add({ [shiftCronExpression('0 0 */12 * * *')]: onlineUpdate });
strapi.cron.add({ [shiftCronExpression('*/10 * * * * *')]: onlineUpdate });
} else { } else {
if (!ee.licenseInfo.expireAt) { if (!ee.licenseInfo.expireAt) {
return disable('Your license does not have offline support.'); return disable('Your license does not have offline support.');

View File

@ -4,7 +4,10 @@ const auditLogContentType = require('./content-types/audit-log');
const provider = { const provider = {
async register({ strapi }) { async register({ strapi }) {
const contentTypes = strapi.container.get('content-types');
if (!contentTypes.keys().includes('admin::audit-log')) {
strapi.container.get('content-types').add('admin::', { 'audit-log': auditLogContentType }); strapi.container.get('content-types').add('admin::', { 'audit-log': auditLogContentType });
}
// Return the provider object // Return the provider object
return { return {