mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(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
This commit is contained in:
@@ -14,49 +14,66 @@ const USAGE_PERIOD_MS = 1 * 60 * 60 * 1000; // log every 1 hour
|
||||
*/
|
||||
export class Usage {
|
||||
private _interval: NodeJS.Timeout;
|
||||
private _currentOperation?: Promise<void>;
|
||||
|
||||
public constructor(private _dbManager: HomeDBManager) {
|
||||
this._interval = setInterval(() => this.apply().catch(log.warn.bind(log)), USAGE_PERIOD_MS);
|
||||
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().catch(log.warn.bind(log));
|
||||
this.apply();
|
||||
}
|
||||
|
||||
public close() {
|
||||
/**
|
||||
* 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 async apply() {
|
||||
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,
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user