mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Add 'user' variable to trigger formulas
Summary: The 'user' variable has a similar API to the one from access rules: it contains properties about a user, such as their full name and email address, as well as optional, user-defined attributes that are populated via user attribute tables. Test Plan: Python unit tests. Reviewers: alexmojaki, paulfitz, dsagal Reviewed By: alexmojaki, dsagal Subscribers: paulfitz, dsagal, alexmojaki Differential Revision: https://phab.getgrist.com/D2898
This commit is contained in:
@@ -835,7 +835,8 @@ export class ActiveDoc extends EventEmitter {
|
||||
// Autocompletion can leak names of tables and columns.
|
||||
if (!await this._granularAccess.canScanData(docSession)) { return []; }
|
||||
await this.waitForInitialization();
|
||||
return this._pyCall('autocomplete', txt, tableId, columnId);
|
||||
const user = await this._granularAccess.getCachedUser(docSession);
|
||||
return this._pyCall('autocomplete', txt, tableId, columnId, user.toJSON());
|
||||
}
|
||||
|
||||
public fetchURL(docSession: DocSession, url: string): Promise<UploadResult> {
|
||||
@@ -980,7 +981,10 @@ export class ActiveDoc extends EventEmitter {
|
||||
* Should only be called by a Sharing object, with this._modificationLock held, since the
|
||||
* actions may need to be rolled back if final access control checks fail.
|
||||
*/
|
||||
public async applyActionsToDataEngine(userActions: UserAction[]): Promise<SandboxActionBundle> {
|
||||
public async applyActionsToDataEngine(
|
||||
docSession: OptDocSession|null,
|
||||
userActions: UserAction[]
|
||||
): Promise<SandboxActionBundle> {
|
||||
const [normalActions, onDemandActions] = this._onDemandActions.splitByOnDemand(userActions);
|
||||
|
||||
let sandboxActionBundle: SandboxActionBundle;
|
||||
@@ -989,7 +993,8 @@ export class ActiveDoc extends EventEmitter {
|
||||
if (normalActions[0][0] !== 'Calculate') {
|
||||
await this.waitForInitialization();
|
||||
}
|
||||
sandboxActionBundle = await this._rawPyCall('apply_user_actions', normalActions);
|
||||
const user = docSession ? await this._granularAccess.getCachedUser(docSession) : undefined;
|
||||
sandboxActionBundle = await this._rawPyCall('apply_user_actions', normalActions, user?.toJSON());
|
||||
await this._reportDataEngineMemory();
|
||||
} else {
|
||||
// Create default SandboxActionBundle to use if the data engine is not called.
|
||||
|
||||
@@ -210,6 +210,11 @@ export class GranularAccess implements GranularAccessForBundle {
|
||||
return this._getUser(docSession);
|
||||
}
|
||||
|
||||
public async getCachedUser(docSession: OptDocSession): Promise<UserInfo> {
|
||||
const access = await this._getAccess(docSession);
|
||||
return access.getUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether user has any access to table.
|
||||
*/
|
||||
@@ -1182,7 +1187,7 @@ export class GranularAccess implements GranularAccessForBundle {
|
||||
} else {
|
||||
fullUser = getDocSessionUser(docSession);
|
||||
}
|
||||
const user: UserInfo = {};
|
||||
const user = new User();
|
||||
user.Access = access;
|
||||
user.UserID = fullUser?.id || null;
|
||||
user.Email = fullUser?.email || null;
|
||||
@@ -2061,3 +2066,41 @@ export function filterColValues(action: DataAction,
|
||||
// Return all actions, in a consistent order for test purposes.
|
||||
return [action, ...[...parts.keys()].sort().map(key => parts.get(key)!)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a user, including any user attributes.
|
||||
*
|
||||
* Serializes into a more compact JSON form that excludes full
|
||||
* row data, only keeping user info and table/row ids for any
|
||||
* user attributes.
|
||||
*
|
||||
* See `user.py` for the sandbox equivalent that deserializes objects of this class.
|
||||
*/
|
||||
export class User implements UserInfo {
|
||||
public Name: string | null = null;
|
||||
public UserID: number | null = null;
|
||||
public Access: Role | null = null;
|
||||
public Origin: string | null = null;
|
||||
public LinkKey: Record<string, string | undefined> = {};
|
||||
public Email: string | null = null;
|
||||
[attribute: string]: any;
|
||||
|
||||
constructor(_info: Record<string, unknown> = {}) {
|
||||
Object.assign(this, _info);
|
||||
}
|
||||
|
||||
public toJSON() {
|
||||
const results: {[key: string]: any} = {};
|
||||
for (const [key, value] of Object.entries(this)) {
|
||||
if (value instanceof RecordView) {
|
||||
// Only include the table id and first matching row id.
|
||||
results[key] = [getTableId(value.data), value.get('id')];
|
||||
} else if (value instanceof EmptyRecordView) {
|
||||
results[key] = null;
|
||||
} else {
|
||||
results[key] = value;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ALL_PERMISSION_PROPS, emptyPermissionSet,
|
||||
MixedPermissionSet, PartialPermissionSet, PermissionSet, TablePermissionSet,
|
||||
toMixed } from 'app/common/ACLPermissions';
|
||||
import { ACLRuleCollection } from 'app/common/ACLRuleCollection';
|
||||
import { AclMatchInput, RuleSet } from 'app/common/GranularAccessClause';
|
||||
import { AclMatchInput, RuleSet, UserInfo } from 'app/common/GranularAccessClause';
|
||||
import { getSetMapValue } from 'app/common/gutil';
|
||||
import * as log from 'app/server/lib/log';
|
||||
import { mapValues } from 'lodash';
|
||||
@@ -79,6 +79,10 @@ abstract class RuleInfo<MixedT extends TableT, TableT> {
|
||||
return this._mergeFullAccess(tableAccess);
|
||||
}
|
||||
|
||||
public getUser(): UserInfo {
|
||||
return this._input.user;
|
||||
}
|
||||
|
||||
protected abstract _processRule(ruleSet: RuleSet, defaultAccess?: () => MixedT): MixedT;
|
||||
protected abstract _mergeTableAccess(access: MixedT[]): TableT;
|
||||
protected abstract _mergeFullAccess(access: TableT[]): MixedT;
|
||||
|
||||
@@ -361,7 +361,7 @@ export class Sharing {
|
||||
}
|
||||
|
||||
private async _applyActionsToDataEngine(docSession: OptDocSession|null, userActions: UserAction[]) {
|
||||
const sandboxActionBundle = await this._activeDoc.applyActionsToDataEngine(userActions);
|
||||
const sandboxActionBundle = await this._activeDoc.applyActionsToDataEngine(docSession, userActions);
|
||||
const undo = getEnvContent(sandboxActionBundle.undo);
|
||||
const docActions = getEnvContent(sandboxActionBundle.stored).concat(
|
||||
getEnvContent(sandboxActionBundle.calc));
|
||||
@@ -377,7 +377,7 @@ export class Sharing {
|
||||
} catch (e) {
|
||||
// should not commit. Don't write to db. Remove changes from sandbox.
|
||||
try {
|
||||
await this._activeDoc.applyActionsToDataEngine([['ApplyUndoActions', undo]]);
|
||||
await this._activeDoc.applyActionsToDataEngine(docSession, [['ApplyUndoActions', undo]]);
|
||||
} finally {
|
||||
await accessControl.finishedBundle();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user