mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Add audit logging machinery
Summary: Adds machinery to support audit logging in the backend. Logging is currently implemented by streaming events to external HTTP endpoints. All flavors of Grist support a default "grist" payload format, and Grist Enterprise additionally supports an HEC-compatible payload format. Logging of all audit events will be added at a later date. Test Plan: Server tests. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D4331
This commit is contained in:
@@ -34,6 +34,7 @@ async function activateServer(home: FlexServer, docManager: DocManager) {
|
||||
home.addJsonSupport();
|
||||
await home.addLandingPages();
|
||||
home.addHomeApi();
|
||||
home.addAuditLogger();
|
||||
await home.addTelemetry();
|
||||
await home.addDoc();
|
||||
home.addApiErrorHandlers();
|
||||
|
||||
117
test/server/lib/GristAuditLogger.ts
Normal file
117
test/server/lib/GristAuditLogger.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import {IAuditLogger} from 'app/server/lib/AuditLogger';
|
||||
import {LogMethods} from 'app/server/lib/LogMethods';
|
||||
import {assert} from 'chai';
|
||||
import moment from 'moment-timezone';
|
||||
import nock from 'nock';
|
||||
import * as sinon from 'sinon';
|
||||
import {TestServer} from 'test/gen-server/apiUtils';
|
||||
import * as testUtils from 'test/server/testUtils';
|
||||
|
||||
describe('GristAuditLogger', function() {
|
||||
let auditLogger: IAuditLogger;
|
||||
let oldEnv: testUtils.EnvironmentSnapshot;
|
||||
let server: TestServer;
|
||||
|
||||
before(async function() {
|
||||
oldEnv = new testUtils.EnvironmentSnapshot();
|
||||
process.env.GRIST_AUDIT_HTTP_ENDPOINT = 'https://api.getgrist.com/events';
|
||||
process.env.GRIST_AUDIT_HTTP_AUTHORIZATION_HEADER = 'Grist bb48d1f8-8f6c-4065-8951-8543a8e70597';
|
||||
server = new TestServer(this);
|
||||
await server.start();
|
||||
auditLogger = server.server.getAuditLogger();
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
await server.stop();
|
||||
oldEnv.restore();
|
||||
});
|
||||
|
||||
describe('logEventAsync', function() {
|
||||
const sandbox: sinon.SinonSandbox = sinon.createSandbox();
|
||||
let logErrorCallArguments: any[] = [];
|
||||
|
||||
before(async function() {
|
||||
sandbox
|
||||
.stub(LogMethods.prototype, 'error')
|
||||
.callsFake((...args) => logErrorCallArguments.push([...args]));
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
logErrorCallArguments = [];
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
it('logs audit events', async function() {
|
||||
const timestamp = moment().toISOString();
|
||||
const scope = nock('https://api.getgrist.com')
|
||||
.matchHeader('Authorization', 'Grist bb48d1f8-8f6c-4065-8951-8543a8e70597')
|
||||
.post('/events', {
|
||||
event: {
|
||||
name: 'createDocument',
|
||||
user: null,
|
||||
details: {
|
||||
id: 'docId',
|
||||
},
|
||||
},
|
||||
timestamp,
|
||||
})
|
||||
.reply(200);
|
||||
await assert.isFulfilled(
|
||||
auditLogger.logEventAsync(null, {
|
||||
event: {
|
||||
name: 'createDocument',
|
||||
details: {id: 'docId'},
|
||||
},
|
||||
timestamp,
|
||||
})
|
||||
);
|
||||
assert.isTrue(scope.isDone());
|
||||
});
|
||||
|
||||
it('throws on failure to log', async function() {
|
||||
nock('https://api.getgrist.com')
|
||||
.post('/events')
|
||||
.reply(404, 'Not found');
|
||||
await assert.isRejected(
|
||||
auditLogger.logEventAsync(null, {
|
||||
event: {
|
||||
name: 'createDocument',
|
||||
details: {id: 'docId'},
|
||||
},
|
||||
}),
|
||||
'received a non-200 response from https://api.getgrist.com/events: 404 Not found'
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if max pending requests exceeded', async function() {
|
||||
nock('https://api.getgrist.com')
|
||||
.persist()
|
||||
.post('/events')
|
||||
.delay(2000)
|
||||
.reply(200);
|
||||
// Queue up enough pending requests to reach the limit (25).
|
||||
for (let i = 0; i < 25; i++) {
|
||||
void auditLogger.logEvent(null, {
|
||||
event: {
|
||||
name: 'createDocument',
|
||||
details: {id: 'docId'},
|
||||
},
|
||||
});
|
||||
}
|
||||
await assert.isRejected(
|
||||
auditLogger.logEventAsync(null, {
|
||||
event: {
|
||||
name: 'createDocument',
|
||||
details: {id: 'docId'},
|
||||
},
|
||||
}),
|
||||
'exceeded the maximum number of pending audit event calls (25)'
|
||||
);
|
||||
nock.abortPendingRequests();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user