gristlabs_grist-core/app/gen-server/lib/Usage.ts
Paul Fitzpatrick d0d3d3d0c9 (core) discount indirect changes for access control purposes
Summary:
This diff discounts indirect changes for access control purposes.  A UserAction that updates a cell A, which in turn causes changes in other dependent cells, will be considered a change to cell A for access control purposes.

The `engine.apply_user_actions` method now returns a `direct` array, with a boolean for each `stored` action, set to `true` if the action is attributed to the user or `false` if it is attributed to the engine.  `GranularAccess` ignores actions attributed to the engine when checking for edit rights.

Subtleties:
 * Removal of references to a removed row are considered direct changes.
 * Doesn't play well with undos as yet.  An action that indirectly modifies a cell the user doesn't have rights to may succeed, but it will not be reversible.

Test Plan: added tests, updated tests

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2806
2021-05-12 11:26:21 -04:00

80 lines
2.6 KiB
TypeScript

import {Document} from 'app/gen-server/entity/Document';
import {Organization} from 'app/gen-server/entity/Organization';
import {User} from 'app/gen-server/entity/User';
import {HomeDBManager} from 'app/gen-server/lib/HomeDBManager';
import * as log from 'app/server/lib/log';
// Frequency of logging usage information. Not something we need
// to track with much granularity.
const USAGE_PERIOD_MS = 1 * 60 * 60 * 1000; // log every 1 hour
/**
* Occasionally log usage information - number of users, orgs,
* docs, etc.
*/
export class Usage {
private _interval: NodeJS.Timeout;
private _currentOperation?: Promise<void>;
public constructor(private _dbManager: HomeDBManager) {
this._interval = setInterval(() => this.apply(), USAGE_PERIOD_MS);
// Log once at beginning, in case we roll over servers faster than
// the logging period for an extended length of time,
// and to raise the visibility of this logging step so if it gets
// slow devs notice.
this.apply();
}
/**
* Remove any scheduled operation, and wait for the current one to complete
* (if one is in progress).
*/
public async close() {
clearInterval(this._interval);
await this._currentOperation;
}
public apply() {
if (!this._currentOperation) {
this._currentOperation = this._apply()
.finally(() => this._currentOperation = undefined);
}
}
private async _apply(): Promise<void> {
try {
const manager = this._dbManager.connection.manager;
// raw count of users
const userCount = await manager.count(User);
// users who have logged in at least once
const userWithLoginCount = await manager.createQueryBuilder()
.from(User, 'users')
.where('first_login_at is not null')
.getCount();
// raw count of organizations (excluding personal orgs)
const orgCount = await manager.createQueryBuilder()
.from(Organization, 'orgs')
.where('owner_id is null')
.getCount();
// organizations with subscriptions that are in a non-terminated state
const orgInGoodStandingCount = await manager.createQueryBuilder()
.from(Organization, 'orgs')
.leftJoin('orgs.billingAccount', 'billing_accounts')
.where('owner_id is null')
.andWhere('billing_accounts.in_good_standing = true')
.getCount();
// raw count of documents
const docCount = await manager.count(Document);
log.rawInfo('activity', {
docCount,
orgCount,
orgInGoodStandingCount,
userCount,
userWithLoginCount,
});
} catch (e) {
log.warn("Error in Usage._apply", e);
}
}
}