mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(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
This commit is contained in:
parent
d4a7660a21
commit
65966e4cfd
@ -109,6 +109,12 @@ export interface ACLRulesReaderOptions {
|
|||||||
addShareRules?: boolean;
|
addShareRules?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ShareContext {
|
||||||
|
shareRef: number;
|
||||||
|
sections: MetaRowRecord<"_grist_Views_section">[];
|
||||||
|
columns: MetaRowRecord<"_grist_Tables_column">[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class for reading ACL rules from DocData.
|
* 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 tableRefs = new Set(sections.map(section => section.tableRef));
|
||||||
const tables = this.docData.getMetaTable('_grist_Tables').getRecords().filter(
|
const tables = this.docData.getMetaTable('_grist_Tables').getRecords().filter(
|
||||||
table => tableRefs.has(table.id)
|
table => tableRefs.has(table.id)
|
||||||
@ -211,13 +230,12 @@ export class ACLRulesReader {
|
|||||||
|
|
||||||
// For tables associated with forms, allow creation of records,
|
// For tables associated with forms, allow creation of records,
|
||||||
// and reading of referenced columns.
|
// and reading of referenced columns.
|
||||||
// TODO: should probably be limiting to a set of columns associated
|
// TODO: tighten access control on creation since it may be broader
|
||||||
// with section - but for form widget that could potentially be very
|
// than users expect - hidden columns could be written.
|
||||||
// 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.
|
|
||||||
for (const table of tables) {
|
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.
|
* Allow creating records in a table.
|
||||||
*/
|
*/
|
||||||
private _shareTableForForm(table: MetaRowRecord<'_grist_Tables'>,
|
private _shareTableForForm(table: MetaRowRecord<'_grist_Tables'>,
|
||||||
shareRef: number) {
|
shareContext: ShareContext) {
|
||||||
|
const { shareRef } = shareContext;
|
||||||
const resource = this._findOrAddResource({
|
const resource = this._findOrAddResource({
|
||||||
tableId: table.tableId,
|
tableId: table.tableId,
|
||||||
colIds: '*',
|
colIds: '*', // At creation, allow all columns to be
|
||||||
|
// initialized.
|
||||||
});
|
});
|
||||||
let aclFormula = `user.ShareRef == ${shareRef}`;
|
let aclFormula = `user.ShareRef == ${shareRef}`;
|
||||||
let aclFormulaParsed = JSON.stringify([
|
let aclFormulaParsed = JSON.stringify([
|
||||||
@ -277,19 +297,21 @@ export class ACLRulesReader {
|
|||||||
resource, aclFormula, aclFormulaParsed, permissionsText: '+R',
|
resource, aclFormula, aclFormulaParsed, permissionsText: '+R',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._shareTableReferencesForForm(table, shareRef);
|
this._shareTableReferencesForForm(table, shareContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Give read access to referenced columns.
|
* Give read access to referenced columns.
|
||||||
*/
|
*/
|
||||||
private _shareTableReferencesForForm(table: MetaRowRecord<'_grist_Tables'>,
|
private _shareTableReferencesForForm(table: MetaRowRecord<'_grist_Tables'>,
|
||||||
shareRef: number) {
|
shareContext: ShareContext) {
|
||||||
|
const { shareRef } = shareContext;
|
||||||
|
|
||||||
const tables = this.docData.getMetaTable('_grist_Tables');
|
const tables = this.docData.getMetaTable('_grist_Tables');
|
||||||
const columns = this.docData.getMetaTable('_grist_Tables_column');
|
const columns = this.docData.getMetaTable('_grist_Tables_column');
|
||||||
const tableColumns = columns.filterRecords({
|
const tableColumns = shareContext.columns.filter(c =>
|
||||||
parentId: table.id,
|
c.parentId === table.id &&
|
||||||
}).filter(c => c.type.startsWith('Ref:') || c.type.startsWith('RefList:'));
|
(c.type.startsWith('Ref:') || c.type.startsWith('RefList:')));
|
||||||
for (const column of tableColumns) {
|
for (const column of tableColumns) {
|
||||||
const visibleColRef = column.visibleCol;
|
const visibleColRef = column.visibleCol;
|
||||||
// This could be blank in tests, not sure about real life.
|
// This could be blank in tests, not sure about real life.
|
||||||
|
@ -133,8 +133,15 @@ export interface TableRecordValues {
|
|||||||
records: TableRecordValue[];
|
records: TableRecordValue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableRecordValue {
|
export interface TableRecordValuesWithoutIds {
|
||||||
|
records: TableRecordValueWithoutId[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableRecordValue extends TableRecordValueWithoutId {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableRecordValueWithoutId {
|
||||||
fields: {
|
fields: {
|
||||||
[colId: string]: CellValue
|
[colId: string]: CellValue
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,8 @@ import {BaseAPI, IOptions} from 'app/common/BaseAPI';
|
|||||||
import {BillingAPI, BillingAPIImpl} from 'app/common/BillingAPI';
|
import {BillingAPI, BillingAPIImpl} from 'app/common/BillingAPI';
|
||||||
import {BrowserSettings} from 'app/common/BrowserSettings';
|
import {BrowserSettings} from 'app/common/BrowserSettings';
|
||||||
import {ICustomWidget} from 'app/common/CustomWidget';
|
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 {DocCreationInfo, OpenDocMode} from 'app/common/DocListAPI';
|
||||||
import {OrgUsageSummary} from 'app/common/DocUsage';
|
import {OrgUsageSummary} from 'app/common/DocUsage';
|
||||||
import {Product} from 'app/common/Features';
|
import {Product} from 'app/common/Features';
|
||||||
@ -441,6 +442,10 @@ interface GetRowsParams {
|
|||||||
immediate?: boolean;
|
immediate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SqlResult extends TableRecordValuesWithoutIds {
|
||||||
|
statement: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect endpoints related to the content of a single document that we've been thinking
|
* 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
|
* 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.
|
// opening a document are irrelevant.
|
||||||
getRows(tableId: string, options?: GetRowsParams): Promise<TableColValues>;
|
getRows(tableId: string, options?: GetRowsParams): Promise<TableColValues>;
|
||||||
getRecords(tableId: string, options?: GetRowsParams): Promise<TableRecordValue[]>;
|
getRecords(tableId: string, options?: GetRowsParams): Promise<TableRecordValue[]>;
|
||||||
|
sql(sql: string, args?: any[]): Promise<SqlResult>;
|
||||||
updateRows(tableId: string, changes: TableColValues): Promise<number[]>;
|
updateRows(tableId: string, changes: TableColValues): Promise<number[]>;
|
||||||
addRows(tableId: string, additions: BulkColValues): Promise<number[]>;
|
addRows(tableId: string, additions: BulkColValues): Promise<number[]>;
|
||||||
removeRows(tableId: string, removals: number[]): Promise<number[]>;
|
removeRows(tableId: string, removals: number[]): Promise<number[]>;
|
||||||
@ -925,6 +931,16 @@ export class DocAPIImpl extends BaseAPI implements DocAPI {
|
|||||||
return response.records;
|
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[]> {
|
public async updateRows(tableId: string, changes: TableColValues): Promise<number[]> {
|
||||||
return this.requestJson(`${this._url}/tables/${tableId}/data`, {
|
return this.requestJson(`${this._url}/tables/${tableId}/data`, {
|
||||||
body: JSON.stringify(changes),
|
body: JSON.stringify(changes),
|
||||||
|
BIN
test/fixtures/docs/FilmsWithImages.grist
vendored
BIN
test/fixtures/docs/FilmsWithImages.grist
vendored
Binary file not shown.
Loading…
Reference in New Issue
Block a user