mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) allow a doc owner to test access as a different user
Summary: This adds back-end support for query parameters `aclAsUser_` and `aclAsUserId_` which, when either is present, direct Grist to process granular access control rules from the point of view of that user (specified by email or id respectively). Some front end support is added, in the form of a tag that shows up when in this mode, and a way to cancel the mode. No friendly way to initiate the mode is offered yet. Test Plan: added test Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2704
This commit is contained in:
parent
d8e742aa0d
commit
3ad9b18ddf
@ -16,7 +16,7 @@ import {menu, menuDivider, menuIcon, menuItem, menuText} from 'app/client/ui2018
|
|||||||
import {confirmModal} from 'app/client/ui2018/modals';
|
import {confirmModal} from 'app/client/ui2018/modals';
|
||||||
import {AsyncFlow, CancelledError, FlowRunner} from 'app/common/AsyncFlow';
|
import {AsyncFlow, CancelledError, FlowRunner} from 'app/common/AsyncFlow';
|
||||||
import {delay} from 'app/common/delay';
|
import {delay} from 'app/common/delay';
|
||||||
import {OpenDocMode} from 'app/common/DocListAPI';
|
import {OpenDocMode, UserOverride} from 'app/common/DocListAPI';
|
||||||
import {IGristUrlState, parseUrlId, UrlIdParts} from 'app/common/gristUrls';
|
import {IGristUrlState, parseUrlId, UrlIdParts} from 'app/common/gristUrls';
|
||||||
import {getReconnectTimeout} from 'app/common/gutil';
|
import {getReconnectTimeout} from 'app/common/gutil';
|
||||||
import {canEdit} from 'app/common/roles';
|
import {canEdit} from 'app/common/roles';
|
||||||
@ -32,6 +32,7 @@ export interface DocInfo extends Document {
|
|||||||
isPreFork: boolean;
|
isPreFork: boolean;
|
||||||
isFork: boolean;
|
isFork: boolean;
|
||||||
isRecoveryMode: boolean;
|
isRecoveryMode: boolean;
|
||||||
|
userOverride: UserOverride|null;
|
||||||
isBareFork: boolean; // a document created without logging in, which is treated as a
|
isBareFork: boolean; // a document created without logging in, which is treated as a
|
||||||
// fork without an original.
|
// fork without an original.
|
||||||
idParts: UrlIdParts;
|
idParts: UrlIdParts;
|
||||||
@ -56,6 +57,7 @@ export interface DocPageModel {
|
|||||||
isPrefork: Observable<boolean>;
|
isPrefork: Observable<boolean>;
|
||||||
isFork: Observable<boolean>;
|
isFork: Observable<boolean>;
|
||||||
isRecoveryMode: Observable<boolean>;
|
isRecoveryMode: Observable<boolean>;
|
||||||
|
userOverride: Observable<UserOverride|null>;
|
||||||
isBareFork: Observable<boolean>;
|
isBareFork: Observable<boolean>;
|
||||||
isSample: Observable<boolean>;
|
isSample: Observable<boolean>;
|
||||||
|
|
||||||
@ -93,6 +95,7 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
|
|||||||
public readonly isPrefork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isPreFork : false);
|
public readonly isPrefork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isPreFork : false);
|
||||||
public readonly isFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isFork : false);
|
public readonly isFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isFork : false);
|
||||||
public readonly isRecoveryMode = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isRecoveryMode : false);
|
public readonly isRecoveryMode = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isRecoveryMode : false);
|
||||||
|
public readonly userOverride = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.userOverride : null);
|
||||||
public readonly isBareFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isBareFork : false);
|
public readonly isBareFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isBareFork : false);
|
||||||
public readonly isSample = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isSample : false);
|
public readonly isSample = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isSample : false);
|
||||||
|
|
||||||
@ -245,8 +248,9 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
|
|||||||
flow.onDispose(() => comm.releaseDocConnection(doc.id));
|
flow.onDispose(() => comm.releaseDocConnection(doc.id));
|
||||||
|
|
||||||
const openDocResponse = await comm.openDoc(doc.id, doc.openMode, linkParameters);
|
const openDocResponse = await comm.openDoc(doc.id, doc.openMode, linkParameters);
|
||||||
if (openDocResponse.recoveryMode) {
|
if (openDocResponse.recoveryMode || openDocResponse.userOverride) {
|
||||||
doc.isRecoveryMode = true;
|
doc.isRecoveryMode = Boolean(openDocResponse.recoveryMode);
|
||||||
|
doc.userOverride = openDocResponse.userOverride || null;
|
||||||
this.currentDoc.set({...doc});
|
this.currentDoc.set({...doc});
|
||||||
}
|
}
|
||||||
const gdModule = await gristDocModulePromise;
|
const gdModule = await gristDocModulePromise;
|
||||||
@ -329,6 +333,7 @@ function buildDocInfo(doc: Document, mode: OpenDocMode): DocInfo {
|
|||||||
...doc,
|
...doc,
|
||||||
isFork,
|
isFork,
|
||||||
isRecoveryMode: false, // we don't know yet, will learn when doc is opened.
|
isRecoveryMode: false, // we don't know yet, will learn when doc is opened.
|
||||||
|
userOverride: null, // ditto.
|
||||||
isSample,
|
isSample,
|
||||||
isPreFork,
|
isPreFork,
|
||||||
isBareFork,
|
isBareFork,
|
||||||
|
@ -44,10 +44,12 @@ export function createTopBarDoc(owner: MultiHolder, appModel: AppModel, pageMode
|
|||||||
docNameSave: renameDoc,
|
docNameSave: renameDoc,
|
||||||
pageNameSave: getRenamePageFn(gristDoc),
|
pageNameSave: getRenamePageFn(gristDoc),
|
||||||
cancelRecoveryMode: getCancelRecoveryModeFn(gristDoc),
|
cancelRecoveryMode: getCancelRecoveryModeFn(gristDoc),
|
||||||
|
cancelUserOverride: getCancelUserOverrideFn(gristDoc),
|
||||||
isPageNameReadOnly: (use) => use(gristDoc.isReadonly) || typeof use(gristDoc.activeViewId) !== 'number',
|
isPageNameReadOnly: (use) => use(gristDoc.isReadonly) || typeof use(gristDoc.activeViewId) !== 'number',
|
||||||
isDocNameReadOnly: (use) => use(gristDoc.isReadonly) || use(pageModel.isFork),
|
isDocNameReadOnly: (use) => use(gristDoc.isReadonly) || use(pageModel.isFork),
|
||||||
isFork: pageModel.isFork,
|
isFork: pageModel.isFork,
|
||||||
isRecoveryMode: pageModel.isRecoveryMode,
|
isRecoveryMode: pageModel.isRecoveryMode,
|
||||||
|
userOverride: pageModel.userOverride,
|
||||||
isFiddle: Computed.create(owner, (use) => use(pageModel.isPrefork) && !use(pageModel.isSample)),
|
isFiddle: Computed.create(owner, (use) => use(pageModel.isPrefork) && !use(pageModel.isSample)),
|
||||||
isSnapshot: Computed.create(owner, doc, (use, _doc) => Boolean(_doc && _doc.idParts.snapshotId)),
|
isSnapshot: Computed.create(owner, doc, (use, _doc) => Boolean(_doc && _doc.idParts.snapshotId)),
|
||||||
isPublic: Computed.create(owner, doc, (use, _doc) => Boolean(_doc && _doc.public)),
|
isPublic: Computed.create(owner, doc, (use, _doc) => Boolean(_doc && _doc.public)),
|
||||||
@ -100,6 +102,15 @@ function getCancelRecoveryModeFn(gristDoc: GristDoc): () => Promise<void> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCancelUserOverrideFn(gristDoc: GristDoc): () => Promise<void> {
|
||||||
|
return async () => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.delete('aclAsUser_');
|
||||||
|
url.searchParams.delete('aclAsUserId_');
|
||||||
|
window.location.assign(url.href);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function topBarUndoBtn(iconName: IconName, ...domArgs: DomElementArg[]): Element {
|
function topBarUndoBtn(iconName: IconName, ...domArgs: DomElementArg[]): Element {
|
||||||
return cssHoverCircle(
|
return cssHoverCircle(
|
||||||
cssTopBarUndoBtn(iconName),
|
cssTopBarUndoBtn(iconName),
|
||||||
|
@ -9,6 +9,7 @@ import { urlState } from 'app/client/models/gristUrlState';
|
|||||||
import { colors, testId } from 'app/client/ui2018/cssVars';
|
import { colors, testId } from 'app/client/ui2018/cssVars';
|
||||||
import { editableLabel } from 'app/client/ui2018/editableLabel';
|
import { editableLabel } from 'app/client/ui2018/editableLabel';
|
||||||
import { icon } from 'app/client/ui2018/icons';
|
import { icon } from 'app/client/ui2018/icons';
|
||||||
|
import { UserOverride } from 'app/common/DocListAPI';
|
||||||
import { BindableValue, dom, Observable, styled } from 'grainjs';
|
import { BindableValue, dom, Observable, styled } from 'grainjs';
|
||||||
import { tooltip } from 'popweasel';
|
import { tooltip } from 'popweasel';
|
||||||
|
|
||||||
@ -85,11 +86,13 @@ export function docBreadcrumbs(
|
|||||||
docNameSave: (val: string) => Promise<void>,
|
docNameSave: (val: string) => Promise<void>,
|
||||||
pageNameSave: (val: string) => Promise<void>,
|
pageNameSave: (val: string) => Promise<void>,
|
||||||
cancelRecoveryMode: () => Promise<void>,
|
cancelRecoveryMode: () => Promise<void>,
|
||||||
|
cancelUserOverride: () => Promise<void>,
|
||||||
isDocNameReadOnly?: BindableValue<boolean>,
|
isDocNameReadOnly?: BindableValue<boolean>,
|
||||||
isPageNameReadOnly?: BindableValue<boolean>,
|
isPageNameReadOnly?: BindableValue<boolean>,
|
||||||
isFork: Observable<boolean>,
|
isFork: Observable<boolean>,
|
||||||
isFiddle: Observable<boolean>,
|
isFiddle: Observable<boolean>,
|
||||||
isRecoveryMode: Observable<boolean>,
|
isRecoveryMode: Observable<boolean>,
|
||||||
|
userOverride: Observable<UserOverride|null>,
|
||||||
isSnapshot?: Observable<boolean>,
|
isSnapshot?: Observable<boolean>,
|
||||||
isPublic?: Observable<boolean>,
|
isPublic?: Observable<boolean>,
|
||||||
}
|
}
|
||||||
@ -118,11 +121,17 @@ export function docBreadcrumbs(
|
|||||||
}
|
}
|
||||||
if (use(options.isRecoveryMode)) {
|
if (use(options.isRecoveryMode)) {
|
||||||
return cssAlertTag('recovery mode',
|
return cssAlertTag('recovery mode',
|
||||||
dom('a', dom.on('click', async () => {
|
dom('a', dom.on('click', () => options.cancelRecoveryMode()),
|
||||||
await options.cancelRecoveryMode()
|
icon('CrossSmall')),
|
||||||
}), icon('CrossSmall')),
|
|
||||||
testId('recovery-mode-tag'));
|
testId('recovery-mode-tag'));
|
||||||
}
|
}
|
||||||
|
const userOverride = use(options.userOverride);
|
||||||
|
if (userOverride) {
|
||||||
|
return cssAlertTag(userOverride.user?.email || 'override',
|
||||||
|
dom('a', dom.on('click', () => options.cancelUserOverride()),
|
||||||
|
icon('CrossSmall')),
|
||||||
|
testId('user-override-tag'));
|
||||||
|
}
|
||||||
if (use(options.isFiddle)) {
|
if (use(options.isFiddle)) {
|
||||||
return cssTag('fiddle', tooltip({title: fiddleExplanation}), testId('fiddle-tag'));
|
return cssTag('fiddle', tooltip({title: fiddleExplanation}), testId('fiddle-tag'));
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {ActionGroup} from 'app/common/ActionGroup';
|
import {ActionGroup} from 'app/common/ActionGroup';
|
||||||
import {TableDataAction} from 'app/common/DocActions';
|
import {TableDataAction} from 'app/common/DocActions';
|
||||||
import {LocalPlugin} from 'app/common/plugin';
|
import {LocalPlugin} from 'app/common/plugin';
|
||||||
|
import {Role} from 'app/common/roles';
|
||||||
import {StringUnion} from 'app/common/StringUnion';
|
import {StringUnion} from 'app/common/StringUnion';
|
||||||
|
import {FullUser} from 'app/common/UserAPI';
|
||||||
|
|
||||||
// Possible flavors of items in a list of documents.
|
// Possible flavors of items in a list of documents.
|
||||||
export type DocEntryTag = ''|'sample'|'invite'|'shared';
|
export type DocEntryTag = ''|'sample'|'invite'|'shared';
|
||||||
@ -43,6 +45,12 @@ export interface OpenLocalDocResult {
|
|||||||
log: ActionGroup[];
|
log: ActionGroup[];
|
||||||
plugins: LocalPlugin[];
|
plugins: LocalPlugin[];
|
||||||
recoveryMode?: boolean;
|
recoveryMode?: boolean;
|
||||||
|
userOverride?: UserOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserOverride {
|
||||||
|
user: FullUser|null;
|
||||||
|
access: Role|null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocListAPI {
|
export interface DocListAPI {
|
||||||
|
@ -359,7 +359,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getUser(userId: number): Promise<User|undefined> {
|
public getUser(userId: number): Promise<User|undefined> {
|
||||||
return User.findOne(userId);
|
return User.findOne(userId, {relations: ["logins"]});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getFullUser(userId: number): Promise<FullUser> {
|
public async getFullUser(userId: number): Promise<FullUser> {
|
||||||
|
@ -160,6 +160,10 @@ export class ActiveDoc extends EventEmitter {
|
|||||||
|
|
||||||
public get recoveryMode(): boolean { return this._recoveryMode; }
|
public get recoveryMode(): boolean { return this._recoveryMode; }
|
||||||
|
|
||||||
|
public async getUserOverride(docSession: OptDocSession) {
|
||||||
|
return this._granularAccess.getUserOverride(docSession);
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers to log a message along with metadata about the request.
|
// Helpers to log a message along with metadata about the request.
|
||||||
public logDebug(s: OptDocSession, msg: string, ...args: any[]) { this._log('debug', s, msg, ...args); }
|
public logDebug(s: OptDocSession, msg: string, ...args: any[]) { this._log('debug', s, msg, ...args); }
|
||||||
public logInfo(s: OptDocSession, msg: string, ...args: any[]) { this._log('info', s, msg, ...args); }
|
public logInfo(s: OptDocSession, msg: string, ...args: any[]) { this._log('info', s, msg, ...args); }
|
||||||
@ -417,7 +421,7 @@ export class ActiveDoc extends EventEmitter {
|
|||||||
await this._actionHistory.initialize();
|
await this._actionHistory.initialize();
|
||||||
this._granularAccess = new GranularAccess(this.docData, (query) => {
|
this._granularAccess = new GranularAccess(this.docData, (query) => {
|
||||||
return this._fetchQueryFromDB(query, false);
|
return this._fetchQueryFromDB(query, false);
|
||||||
}, this.recoveryMode);
|
}, this.recoveryMode, this._docManager.getHomeDbManager(), this.docName);
|
||||||
await this._granularAccess.update();
|
await this._granularAccess.update();
|
||||||
this._sharing = new Sharing(this, this._actionHistory, this._modificationLock);
|
this._sharing = new Sharing(this, this._actionHistory, this._modificationLock);
|
||||||
|
|
||||||
|
@ -54,6 +54,10 @@ export class DocManager extends EventEmitter {
|
|||||||
this._homeDbManager = dbManager;
|
this._homeDbManager = dbManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getHomeDbManager() {
|
||||||
|
return this._homeDbManager;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an implementation of the DocListAPI for the given Client object.
|
* Returns an implementation of the DocListAPI for the given Client object.
|
||||||
*/
|
*/
|
||||||
@ -303,6 +307,7 @@ export class DocManager extends EventEmitter {
|
|||||||
log: recentActions,
|
log: recentActions,
|
||||||
plugins: activeDoc.docPluginManager.getPlugins(),
|
plugins: activeDoc.docPluginManager.getPlugins(),
|
||||||
recoveryMode: activeDoc.recoveryMode,
|
recoveryMode: activeDoc.recoveryMode,
|
||||||
|
userOverride: await activeDoc.getUserOverride(docSession),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,14 +12,18 @@ import { RemoveRecord, ReplaceTableData, UpdateRecord } from 'app/common/DocActi
|
|||||||
import { CellValue, ColValues, DocAction, getTableId, isSchemaAction } from 'app/common/DocActions';
|
import { CellValue, ColValues, DocAction, getTableId, isSchemaAction } from 'app/common/DocActions';
|
||||||
import { TableDataAction, UserAction } from 'app/common/DocActions';
|
import { TableDataAction, UserAction } from 'app/common/DocActions';
|
||||||
import { DocData } from 'app/common/DocData';
|
import { DocData } from 'app/common/DocData';
|
||||||
|
import { UserOverride } from 'app/common/DocListAPI';
|
||||||
import { ErrorWithCode } from 'app/common/ErrorWithCode';
|
import { ErrorWithCode } from 'app/common/ErrorWithCode';
|
||||||
import { AclMatchInput, InfoView } from 'app/common/GranularAccessClause';
|
import { AclMatchInput, InfoView } from 'app/common/GranularAccessClause';
|
||||||
import { RuleSet, UserInfo } from 'app/common/GranularAccessClause';
|
import { RuleSet, UserInfo } from 'app/common/GranularAccessClause';
|
||||||
import { getSetMapValue, isObject } from 'app/common/gutil';
|
import { getSetMapValue, isObject } from 'app/common/gutil';
|
||||||
import { canView } from 'app/common/roles';
|
import { canView, Role } from 'app/common/roles';
|
||||||
|
import { FullUser } from 'app/common/UserAPI';
|
||||||
|
import { HomeDBManager } from 'app/gen-server/lib/HomeDBManager';
|
||||||
import { compileAclFormula } from 'app/server/lib/ACLFormula';
|
import { compileAclFormula } from 'app/server/lib/ACLFormula';
|
||||||
import { getDocSessionAccess, getDocSessionUser, OptDocSession } from 'app/server/lib/DocSession';
|
import { getDocSessionAccess, getDocSessionUser, OptDocSession } from 'app/server/lib/DocSession';
|
||||||
import * as log from 'app/server/lib/log';
|
import * as log from 'app/server/lib/log';
|
||||||
|
import { integerParam } from 'app/server/lib/requestUtils';
|
||||||
import { getRelatedRows, getRowIdsFromDocAction } from 'app/server/lib/RowAccess';
|
import { getRelatedRows, getRowIdsFromDocAction } from 'app/server/lib/RowAccess';
|
||||||
import cloneDeep = require('lodash/cloneDeep');
|
import cloneDeep = require('lodash/cloneDeep');
|
||||||
import get = require('lodash/get');
|
import get = require('lodash/get');
|
||||||
@ -114,7 +118,9 @@ export class GranularAccess {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private _docData: DocData,
|
private _docData: DocData,
|
||||||
private _fetchQueryFromDB: (query: Query) => Promise<TableDataAction>,
|
private _fetchQueryFromDB: (query: Query) => Promise<TableDataAction>,
|
||||||
private _recoveryMode: boolean) {
|
private _recoveryMode: boolean,
|
||||||
|
private _homeDbManager: HomeDBManager | null,
|
||||||
|
private _docId: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -488,6 +494,11 @@ export class GranularAccess {
|
|||||||
this._filterColumns(data[3], (colId) => permInfo.getColumnAccess(tableId, colId).read !== 'deny');
|
this._filterColumns(data[3], (colId) => permInfo.getColumnAccess(tableId, colId).read !== 'deny');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getUserOverride(docSession: OptDocSession): Promise<UserOverride|undefined> {
|
||||||
|
await this._getUser(docSession);
|
||||||
|
return this._getUserAttributes(docSession).override;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strip out any denied columns from an action. Returns null if nothing is left.
|
* Strip out any denied columns from an action. Returns null if nothing is left.
|
||||||
* accessFn may throw if denials are fatal.
|
* accessFn may throw if denials are fatal.
|
||||||
@ -799,9 +810,36 @@ export class GranularAccess {
|
|||||||
* created by user-attribute rules.
|
* created by user-attribute rules.
|
||||||
*/
|
*/
|
||||||
private async _getUser(docSession: OptDocSession): Promise<UserInfo> {
|
private async _getUser(docSession: OptDocSession): Promise<UserInfo> {
|
||||||
const access = getDocSessionAccess(docSession);
|
const linkParameters = docSession.authorizer?.getLinkParameters() || {};
|
||||||
const fullUser = getDocSessionUser(docSession);
|
let access: Role | null;
|
||||||
|
let fullUser: FullUser | null;
|
||||||
const attrs = this._getUserAttributes(docSession);
|
const attrs = this._getUserAttributes(docSession);
|
||||||
|
access = getDocSessionAccess(docSession);
|
||||||
|
|
||||||
|
// If aclAsUserId/aclAsUser is set, then override user for acl purposes.
|
||||||
|
if (linkParameters.aclAsUserId || linkParameters.aclAsUser) {
|
||||||
|
if (!this.isOwner(docSession)) { throw new Error('only an owner can override user'); }
|
||||||
|
if (attrs.override) {
|
||||||
|
// Used cached properties.
|
||||||
|
access = attrs.override.access;
|
||||||
|
fullUser = attrs.override.user;
|
||||||
|
} else {
|
||||||
|
// Look up user information in database.
|
||||||
|
if (!this._homeDbManager) { throw new Error('database required'); }
|
||||||
|
const user = linkParameters.aclAsUserId ?
|
||||||
|
(await this._homeDbManager.getUser(integerParam(linkParameters.aclAsUserId))) :
|
||||||
|
(await this._homeDbManager.getUserByLogin(linkParameters.aclAsUser));
|
||||||
|
const docAuth = user && await this._homeDbManager.getDocAuthCached({
|
||||||
|
urlId: this._docId,
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
access = docAuth?.access || null;
|
||||||
|
fullUser = user && this._homeDbManager.makeFullUser(user) || null;
|
||||||
|
attrs.override = { access, user: fullUser };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fullUser = getDocSessionUser(docSession);
|
||||||
|
}
|
||||||
const user: UserInfo = {};
|
const user: UserInfo = {};
|
||||||
user.Access = access;
|
user.Access = access;
|
||||||
user.UserID = fullUser?.id || null;
|
user.UserID = fullUser?.id || null;
|
||||||
@ -809,7 +847,7 @@ export class GranularAccess {
|
|||||||
user.Name = fullUser?.name || null;
|
user.Name = fullUser?.name || null;
|
||||||
// If viewed from a websocket, collect any link parameters included.
|
// If viewed from a websocket, collect any link parameters included.
|
||||||
// TODO: could also get this from rest api access, just via a different route.
|
// TODO: could also get this from rest api access, just via a different route.
|
||||||
user.Link = docSession.authorizer?.getLinkParameters() || {};
|
user.Link = linkParameters;
|
||||||
// Include origin info if accessed via the rest api.
|
// Include origin info if accessed via the rest api.
|
||||||
// TODO: could also get this for websocket access, just via a different route.
|
// TODO: could also get this for websocket access, just via a different route.
|
||||||
user.Origin = docSession.req?.get('origin') || null;
|
user.Origin = docSession.req?.get('origin') || null;
|
||||||
@ -1114,6 +1152,7 @@ class EmptyRecordView implements InfoView {
|
|||||||
*/
|
*/
|
||||||
class UserAttributes {
|
class UserAttributes {
|
||||||
public rows: {[clauseName: string]: InfoView} = {};
|
public rows: {[clauseName: string]: InfoView} = {};
|
||||||
|
public override?: UserOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A function for extracting one of the create/read/update/delete/schemaEdit permissions
|
// A function for extracting one of the create/read/update/delete/schemaEdit permissions
|
||||||
|
Loading…
Reference in New Issue
Block a user