(core) use visibility information when sharing referenced columns with forms

Summary:
This tightens down the set of referenced columns made available to
forms for dropdowns. Previous access to columns was computed at the
level of shared tables. Now it is calculated at the level of shared
sections. That means that we can now refrain from following hidden
references, and make the referred data unavailable to forms, since
they should not need it.

Test Plan: extended test

Reviewers: jarek, dsagal

Reviewed By: jarek, dsagal

Subscribers: dsagal

Differential Revision: https://phab.getgrist.com/D4234
pull/945/head
Paul Fitzpatrick 3 weeks ago
parent d4a7660a21
commit 65966e4cfd

@ -109,6 +109,12 @@ export interface ACLRulesReaderOptions {
addShareRules?: boolean;
}
interface ShareContext {
shareRef: number;
sections: MetaRowRecord<"_grist_Views_section">[];
columns: MetaRowRecord<"_grist_Tables_column">[];
}
/**
* Helper class for reading ACL rules from DocData.
*/
@ -204,6 +210,19 @@ export class ACLRulesReader {
}
);
const sectionIds = new Set(sections.map(section => section.id));
const fields = this.docData.getMetaTable('_grist_Views_section_field').getRecords().filter(
field => {
return sectionIds.has(field.parentId);
}
);
const columnIds = new Set(fields.map(field => field.colRef));
const columns = this.docData.getMetaTable('_grist_Tables_column').getRecords().filter(
column => {
return columnIds.has(column.id);
}
);
const tableRefs = new Set(sections.map(section => section.tableRef));
const tables = this.docData.getMetaTable('_grist_Tables').getRecords().filter(
table => tableRefs.has(table.id)
@ -211,13 +230,12 @@ export class ACLRulesReader {
// For tables associated with forms, allow creation of records,
// and reading of referenced columns.
// TODO: should probably be limiting to a set of columns associated
// with section - but for form widget that could potentially be very
// confusing since it may not be easy to see that certain columns
// haven't been made visible for it? For now, just working at table
// level.
// TODO: tighten access control on creation since it may be broader
// than users expect - hidden columns could be written.
for (const table of tables) {
this._shareTableForForm(table, share.id);
this._shareTableForForm(table, {
shareRef: share.id, sections, columns,
});
}
}
@ -248,10 +266,12 @@ export class ACLRulesReader {
* Allow creating records in a table.
*/
private _shareTableForForm(table: MetaRowRecord<'_grist_Tables'>,
shareRef: number) {
shareContext: ShareContext) {
const { shareRef } = shareContext;
const resource = this._findOrAddResource({
tableId: table.tableId,
colIds: '*',
colIds: '*', // At creation, allow all columns to be
// initialized.
});
let aclFormula = `user.ShareRef == ${shareRef}`;
let aclFormulaParsed = JSON.stringify([
@ -277,19 +297,21 @@ export class ACLRulesReader {
resource, aclFormula, aclFormulaParsed, permissionsText: '+R',
}));
this._shareTableReferencesForForm(table, shareRef);
this._shareTableReferencesForForm(table, shareContext);
}
/**
* Give read access to referenced columns.
*/
private _shareTableReferencesForForm(table: MetaRowRecord<'_grist_Tables'>,
shareRef: number) {
shareContext: ShareContext) {
const { shareRef } = shareContext;
const tables = this.docData.getMetaTable('_grist_Tables');
const columns = this.docData.getMetaTable('_grist_Tables_column');
const tableColumns = columns.filterRecords({
parentId: table.id,
}).filter(c => c.type.startsWith('Ref:') || c.type.startsWith('RefList:'));
const tableColumns = shareContext.columns.filter(c =>
c.parentId === table.id &&
(c.type.startsWith('Ref:') || c.type.startsWith('RefList:')));
for (const column of tableColumns) {
const visibleColRef = column.visibleCol;
// This could be blank in tests, not sure about real life.

@ -133,8 +133,15 @@ export interface TableRecordValues {
records: TableRecordValue[];
}
export interface TableRecordValue {
export interface TableRecordValuesWithoutIds {
records: TableRecordValueWithoutId[];
}
export interface TableRecordValue extends TableRecordValueWithoutId {
id: number | string;
}
export interface TableRecordValueWithoutId {
fields: {
[colId: string]: CellValue
};

@ -5,7 +5,8 @@ import {BaseAPI, IOptions} from 'app/common/BaseAPI';
import {BillingAPI, BillingAPIImpl} from 'app/common/BillingAPI';
import {BrowserSettings} from 'app/common/BrowserSettings';
import {ICustomWidget} from 'app/common/CustomWidget';
import {BulkColValues, TableColValues, TableRecordValue, TableRecordValues, UserAction} from 'app/common/DocActions';
import {BulkColValues, TableColValues, TableRecordValue, TableRecordValues,
TableRecordValuesWithoutIds, UserAction} from 'app/common/DocActions';
import {DocCreationInfo, OpenDocMode} from 'app/common/DocListAPI';
import {OrgUsageSummary} from 'app/common/DocUsage';
import {Product} from 'app/common/Features';
@ -441,6 +442,10 @@ interface GetRowsParams {
immediate?: boolean;
}
interface SqlResult extends TableRecordValuesWithoutIds {
statement: string;
}
/**
* Collect endpoints related to the content of a single document that we've been thinking
* of as the (restful) "Doc API". A few endpoints that could be here are not, for historical
@ -452,6 +457,7 @@ export interface DocAPI {
// opening a document are irrelevant.
getRows(tableId: string, options?: GetRowsParams): Promise<TableColValues>;
getRecords(tableId: string, options?: GetRowsParams): Promise<TableRecordValue[]>;
sql(sql: string, args?: any[]): Promise<SqlResult>;
updateRows(tableId: string, changes: TableColValues): Promise<number[]>;
addRows(tableId: string, additions: BulkColValues): Promise<number[]>;
removeRows(tableId: string, removals: number[]): Promise<number[]>;
@ -925,6 +931,16 @@ export class DocAPIImpl extends BaseAPI implements DocAPI {
return response.records;
}
public async sql(sql: string, args?: any[]): Promise<SqlResult> {
return this.requestJson(`${this._url}/sql`, {
body: JSON.stringify({
sql,
...(args ? { args } : {}),
}),
method: 'POST',
});
}
public async updateRows(tableId: string, changes: TableColValues): Promise<number[]> {
return this.requestJson(`${this._url}/tables/${tableId}/data`, {
body: JSON.stringify(changes),

Binary file not shown.
Loading…
Cancel
Save